feat(relay): multi-platform-per-agent — list identity, N-hello, per-frame egress (Phase 1.5) - #52830
Merged
Merged
Conversation
…, N-hello, per-frame egress (Phase 1.5)
Cut over the agent half of Shape A (D-Q1.5a/b.1/c) to front a SET of platforms on
one relay WS:
- relay_platform_identities() parses GATEWAY_RELAY_PLATFORMS (list) +
GATEWAY_RELAY_BOT_IDS (JSON keyed map {platform:{botId,username?}}). Cut over
from the scalar GATEWAY_RELAY_PLATFORM/_BOT_ID (no fallback, D-Q1.5c).
- self_provision_relay() loops one /relay/provision per platform under one
gatewayId+secret, partial-failure-tolerant.
- WebSocketRelayTransport takes the identity SET, sends one hello per identity
(connector accumulates the advertised set), and stamps the per-frame
OutboundFrame.platform + its matching advertised botId on outbound.
- RelayAdapter remembers each chat's underlying source.platform (mirroring the
existing guild/dm scope capture) and tags the reply's egress platform.
- send_relay_policy() declares one relevance policy per fronted platform (the
connector keys policy by (tenant,platform,instanceId)).
Single-platform deploys are byte-identical on the wire (1-element list, no per-frame
tag -> connector session-default fallback). typecheck/ruff clean; relay unit 221 pass
(+10 new); all 15 cross-repo E2E drivers green vs connector origin/main.
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
1 |
invalid-argument-type |
1 |
invalid-assignment |
1 |
First entries
tests/gateway/relay/test_relay_multiplatform.py:19: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/relay/test_relay_multiplatform.py:226: [invalid-argument-type] invalid-argument-type: Argument to `RelayAdapter.__init__` is incorrect: Expected `RelayTransport | None`, found `StubConnector`
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
✅ Fixed issues (2):
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
run_agent.py:2989: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
Unchanged: 5992 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
tonydwb
approved these changes
Jun 26, 2026
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved
Phase 1.5 multi-platform-per-agent relay feature. Adds the ability for one gateway to front N platforms on a single WS connection, with per-frame egress tagging.
Looks Good
relay_platform_identities()cleanly parsesGATEWAY_RELAY_PLATFORMS+GATEWAY_RELAY_BOT_IDSwith graceful degradation for malformed JSONself_provision_relay()loop is partial-failure-tolerant: one platform failing does not block others- Per-frame egress tagging via
platformkwarg onsend_outbound/send_follow_upis backward-compatible (single-platform gateways emit the same frame shape as before) _bot_id_for()correctly resolves the matching botId for each platform's advertised set- Multi-hello handshake (one
helloper identity) is clean - Comprehensive test suite: identity parsing, provision loop, partial failure, adapter egress tagging, untagged fallback
- Transport stub updated correctly for tests
Reviewed by Hermes Agent
pai-scaffolde
pushed a commit
to pai-scaffolde/hermes-agent
that referenced
this pull request
Jun 28, 2026
…, N-hello, per-frame egress (Phase 1.5) (NousResearch#52830) Cut over the agent half of Shape A (D-Q1.5a/b.1/c) to front a SET of platforms on one relay WS: - relay_platform_identities() parses GATEWAY_RELAY_PLATFORMS (list) + GATEWAY_RELAY_BOT_IDS (JSON keyed map {platform:{botId,username?}}). Cut over from the scalar GATEWAY_RELAY_PLATFORM/_BOT_ID (no fallback, D-Q1.5c). - self_provision_relay() loops one /relay/provision per platform under one gatewayId+secret, partial-failure-tolerant. - WebSocketRelayTransport takes the identity SET, sends one hello per identity (connector accumulates the advertised set), and stamps the per-frame OutboundFrame.platform + its matching advertised botId on outbound. - RelayAdapter remembers each chat's underlying source.platform (mirroring the existing guild/dm scope capture) and tags the reply's egress platform. - send_relay_policy() declares one relevance policy per fronted platform (the connector keys policy by (tenant,platform,instanceId)). Single-platform deploys are byte-identical on the wire (1-element list, no per-frame tag -> connector session-default fallback). typecheck/ruff clean; relay unit 221 pass (+10 new); all 15 cross-repo E2E drivers green vs connector origin/main.
1 task
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
…, N-hello, per-frame egress (Phase 1.5) (NousResearch#52830) Cut over the agent half of Shape A (D-Q1.5a/b.1/c) to front a SET of platforms on one relay WS: - relay_platform_identities() parses GATEWAY_RELAY_PLATFORMS (list) + GATEWAY_RELAY_BOT_IDS (JSON keyed map {platform:{botId,username?}}). Cut over from the scalar GATEWAY_RELAY_PLATFORM/_BOT_ID (no fallback, D-Q1.5c). - self_provision_relay() loops one /relay/provision per platform under one gatewayId+secret, partial-failure-tolerant. - WebSocketRelayTransport takes the identity SET, sends one hello per identity (connector accumulates the advertised set), and stamps the per-frame OutboundFrame.platform + its matching advertised botId on outbound. - RelayAdapter remembers each chat's underlying source.platform (mirroring the existing guild/dm scope capture) and tags the reply's egress platform. - send_relay_policy() declares one relevance policy per fronted platform (the connector keys policy by (tenant,platform,instanceId)). Single-platform deploys are byte-identical on the wire (1-element list, no per-frame tag -> connector session-default fallback). typecheck/ruff clean; relay unit 221 pass (+10 new); all 15 cross-repo E2E drivers green vs connector origin/main.
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
…, N-hello, per-frame egress (Phase 1.5) (NousResearch#52830) Cut over the agent half of Shape A (D-Q1.5a/b.1/c) to front a SET of platforms on one relay WS: - relay_platform_identities() parses GATEWAY_RELAY_PLATFORMS (list) + GATEWAY_RELAY_BOT_IDS (JSON keyed map {platform:{botId,username?}}). Cut over from the scalar GATEWAY_RELAY_PLATFORM/_BOT_ID (no fallback, D-Q1.5c). - self_provision_relay() loops one /relay/provision per platform under one gatewayId+secret, partial-failure-tolerant. - WebSocketRelayTransport takes the identity SET, sends one hello per identity (connector accumulates the advertised set), and stamps the per-frame OutboundFrame.platform + its matching advertised botId on outbound. - RelayAdapter remembers each chat's underlying source.platform (mirroring the existing guild/dm scope capture) and tags the reply's egress platform. - send_relay_policy() declares one relevance policy per fronted platform (the connector keys policy by (tenant,platform,instanceId)). Single-platform deploys are byte-identical on the wire (1-element list, no per-frame tag -> connector session-default fallback). typecheck/ruff clean; relay unit 221 pass (+10 new); all 15 cross-repo E2E drivers green vs connector origin/main.
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
…, N-hello, per-frame egress (Phase 1.5) (NousResearch#52830) Cut over the agent half of Shape A (D-Q1.5a/b.1/c) to front a SET of platforms on one relay WS: - relay_platform_identities() parses GATEWAY_RELAY_PLATFORMS (list) + GATEWAY_RELAY_BOT_IDS (JSON keyed map {platform:{botId,username?}}). Cut over from the scalar GATEWAY_RELAY_PLATFORM/_BOT_ID (no fallback, D-Q1.5c). - self_provision_relay() loops one /relay/provision per platform under one gatewayId+secret, partial-failure-tolerant. - WebSocketRelayTransport takes the identity SET, sends one hello per identity (connector accumulates the advertised set), and stamps the per-frame OutboundFrame.platform + its matching advertised botId on outbound. - RelayAdapter remembers each chat's underlying source.platform (mirroring the existing guild/dm scope capture) and tags the reply's egress platform. - send_relay_policy() declares one relevance policy per fronted platform (the connector keys policy by (tenant,platform,instanceId)). Single-platform deploys are byte-identical on the wire (1-element list, no per-frame tag -> connector session-default fallback). typecheck/ruff clean; relay unit 221 pass (+10 new); all 15 cross-repo E2E drivers green vs connector origin/main.
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…, N-hello, per-frame egress (Phase 1.5) (NousResearch#52830) Cut over the agent half of Shape A (D-Q1.5a/b.1/c) to front a SET of platforms on one relay WS: - relay_platform_identities() parses GATEWAY_RELAY_PLATFORMS (list) + GATEWAY_RELAY_BOT_IDS (JSON keyed map {platform:{botId,username?}}). Cut over from the scalar GATEWAY_RELAY_PLATFORM/_BOT_ID (no fallback, D-Q1.5c). - self_provision_relay() loops one /relay/provision per platform under one gatewayId+secret, partial-failure-tolerant. - WebSocketRelayTransport takes the identity SET, sends one hello per identity (connector accumulates the advertised set), and stamps the per-frame OutboundFrame.platform + its matching advertised botId on outbound. - RelayAdapter remembers each chat's underlying source.platform (mirroring the existing guild/dm scope capture) and tags the reply's egress platform. - send_relay_policy() declares one relevance policy per fronted platform (the connector keys policy by (tenant,platform,instanceId)). Single-platform deploys are byte-identical on the wire (1-element list, no per-frame tag -> connector session-default fallback). typecheck/ruff clean; relay unit 221 pass (+10 new); all 15 cross-repo E2E drivers green vs connector origin/main.
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
…, N-hello, per-frame egress (Phase 1.5) (NousResearch#52830) Cut over the agent half of Shape A (D-Q1.5a/b.1/c) to front a SET of platforms on one relay WS: - relay_platform_identities() parses GATEWAY_RELAY_PLATFORMS (list) + GATEWAY_RELAY_BOT_IDS (JSON keyed map {platform:{botId,username?}}). Cut over from the scalar GATEWAY_RELAY_PLATFORM/_BOT_ID (no fallback, D-Q1.5c). - self_provision_relay() loops one /relay/provision per platform under one gatewayId+secret, partial-failure-tolerant. - WebSocketRelayTransport takes the identity SET, sends one hello per identity (connector accumulates the advertised set), and stamps the per-frame OutboundFrame.platform + its matching advertised botId on outbound. - RelayAdapter remembers each chat's underlying source.platform (mirroring the existing guild/dm scope capture) and tags the reply's egress platform. - send_relay_policy() declares one relevance policy per fronted platform (the connector keys policy by (tenant,platform,instanceId)). Single-platform deploys are byte-identical on the wire (1-element list, no per-frame tag -> connector session-default fallback). typecheck/ruff clean; relay unit 221 pass (+10 new); all 15 cross-repo E2E drivers green vs connector origin/main.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Multi-platform-per-agent — the gateway half (Phase 1.5, Shape A)
One hosted agent now fronts a set of platforms (e.g. Discord and Telegram) over a single relay WebSocket, instead of being pinned to one. This is the
hermes-agenthalf of the cross-repo cut-over; it pairs with the connector's per-frame egress (gateway-gateway #75, already merged) and the NAS deploy-config stamp (nous-account-servicefeat/relay-multiplatform-stamp).What changed (
gateway/relay/)relay_platform_identities()parses the env-scoped deploy config into the ordered(platform, bot_id)set:GATEWAY_RELAY_PLATFORMS— comma-sep list (e.g.discord,telegram)GATEWAY_RELAY_BOT_IDS— JSON keyed map{"discord":{"botId":"…"},"telegram":{"botId":"…","username":"…"}}GATEWAY_RELAY_PLATFORM/_BOT_ID— no fallback path retained (per the design decision). A malformed map degrades to empty bot ids (boot never crashes).self_provision_relay()loops one/relay/provisionPOST per platform under onegatewayId+ one secret, partial-failure-tolerant (one platform's bad config doesn't sink the rest).WebSocketRelayTransportholds the identity set, sends onehelloper identity (the connector accumulates them into its advertised set), and stamps the per-frameOutboundFrame.platform+ its matching advertised botId on every outbound.RelayAdapterremembers each chat's underlyingsource.platformon inbound (mirroring the existing guild/DM scope-capture) and tags the reply's egress platform — so a reply routes back through the right sender.send_relay_policy()declares one relevance policy per fronted platform (the connector keys policy by(tenant, platform, instanceId)).Back-compat
A single-platform deploy passes a 1-element list and emits no per-frame tag → the connector falls back to the session-default platform → the wire shape is byte-identical to before.
Verification
ruffclean,tsc-equivalent (pyright) clean on changed files.origin/main(with fix(whatsapp): multi-user session isolation and bridge message handling #75) — proving the N-hello + per-frame-egress wire interop on real sockets, plus the single-platform back-compat path.Infographic