Skip to content

feat: gateway forwarding between the local mesh and the hub - #175

Merged
Mearman merged 3 commits into
mainfrom
feat/gateway-forwarding
Sep 17, 2026
Merged

Mearman merged 3 commits into
mainfrom
feat/gateway-forwarding

Conversation

@Mearman

@Mearman Mearman commented Sep 17, 2026

Copy link
Copy Markdown
Member

Implements the core forwarding leg of the cross-machine mesh epic (#153): the coordinator's gateway (#154) starts actually forwarding between the local mesh and the hub, in both directions.

  • Advertises local visible agents onto the hub via gossip, keyed by device-id, so remote gateways can address them.
  • Merges the hub's own gossiped directory into the local mesh's device directory, so remote agents surface in list_agents and are resolvable for DMs.
  • Routes a send/DM whose target isn't a local peer through the gateway's hub pairing, and dispatches inbound hub-relayed room-domain requests back into the local mesh, attributed to the true sender rather than the gateway.

Closes #155

Work in progress -- pushing incrementally.

AgentRegistry.getAgent only checked the local agents map, so a device
known solely through gossip (never registered or state-synced locally)
was unresolvable even though listAgents already merged it into its own
returned array. Extend getAgent to fall back to the same
listDiscoverableAgents merge listAgents uses, via a shared
synthesiseDiscoveredAgent helper.

RoomMessaging.sendDm checked recipient existence via a direct
agents.get lookup, which can never see a gossip-only device. Route it
through a new resolveAgent dependency (wired to AgentRegistry.getAgent)
instead, so a DM to a gossip-discovered recipient is no longer rejected
as AGENT_NOT_FOUND before ever reaching the transport layer. This is a
prerequisite for cross-machine DMs (agent-comms#155), where a remote
agent learned via the hub's gossip will never exist as a locally
registered record.
… mesh and the hub

The gateway (agent-comms#154) held a hub connection but did nothing
with it: no local agent was ever advertised onto it, nothing learned
from it was merged back, and a room-domain request addressed to a
device this side had no local session for simply failed as
not_connected. This wires the actual forwarding, both directions.

Outbound: whenever a local peer session's own gossiped directory
changes, forward every agent/self-bearing entry (by construction only
ever a visible agent, since MeshStore's selfAgentAdvert getter never
gossips that key for a hidden or ghost one) onto the hub's own raw
connection as a gossip frame, keyed by each device's own device-id.
sendGossipUpdate can only ever advertise a session's own single
device, so this rides HubSession's own tracked raw Connection instead.
connectHub also pushes an immediate catch-up of every already-known
local device, so a coordinator handover doesn't wait for each local
peer's own next periodic gossip tick to re-advertise.

Remote directory merge: HubSession now surfaces every remote directory
entry the hub's gossip and catch-up carry, merged into the transport's
own mesh-wide knownDevices via the same mergeKnownDevices helper a
local peer session's directory already uses -- a hub-learned agent
therefore surfaces in listAgents/getAgent with no separate lookup path.

Routing: WireMeshTransport.sendRoomRequest falls back to routing
through the hub's relay-connect/relay-data pairing when its target
isn't a local peer session, bounded by a timeout since the hub
silently drops a relay-connect to an unknown target-device with no
error frame. HubSession's own inbound dispatch now sends a real
room-domain verb (room.send, room.join, room.notify, ...) to the same
roomVerbHandlers a local peer session already uses, with a per-request
ConnectionHandle keyed by request.fromDevice -- the same "attribute to
the true sender, never the hub" discipline already applied to the
legacy opaque-frame path, extended to this one. A legacy state_sync or
state_update carried this way is still dropped before it ever reaches
onMessage, unchanged: it has no per-request capability token to verify
it against, unlike a real room-domain verb.

reportPresenceAdvert and the outbound-forwarding/routing helpers move
out to gossip-directory.ts and a new hub-forwarding.ts respectively to
keep wire-mesh-transport.ts under this repo's max-lines cap.
wireTestTransport never wired WireMeshTransport's getSelfAgentAdvert
parameter, unlike bridge-mesh.ts's own production wiring -- every test
built on it therefore never gossiped the agent/self extension at all,
regardless of registerAgent being called, which silently made
listKnownDevices/listAgents' own gossip-discovery merge untestable
through the standard helper. Wire it the same way production does.

Add integration coverage for agent-comms#155 using two independent
local meshes, each its own coordinator dialling a shared real relay
hub, never directly connected to each other: a non-gateway local peer
on one machine is discovered by the other machine's gateway purely via
hub gossip; a hidden or ghost agent is never advertised across it; and
a DM to a remote gateway's own agent, including its two-round consent
handshake, routes correctly through the hub's relay pairing.

Document the one confirmed limitation this surfaced: wire-mesh-core's
own session layer tracks at most one active relay pairing per remote
device, so an inbound hub-relayed room-domain request is dispatched as
addressed to this gateway's own agent unconditionally -- there is no
target-device disambiguation available yet to route it on to a
different local peer this same gateway also advertises.
@Mearman
Mearman force-pushed the feat/gateway-forwarding branch from 33488e5 to 18eee5f Compare September 17, 2026 17:07
@Mearman
Mearman marked this pull request as ready for review September 17, 2026 17:13
@Mearman
Mearman merged commit 7f98815 into main Sep 17, 2026
6 checks passed
@Mearman
Mearman deleted the feat/gateway-forwarding branch September 17, 2026 17:13
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 3.10.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review Completed 2026-09-17T17:23:22.507112Z 18eee5f Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gateway forwarding: advertise local agents, merge remote directory, route DMs with attribution

1 participant