LearnNewsExamplesServices
Frontmatter
id13196
titleExtended-NL Leaf A (emit): Bridge stamps the agent-message sidecar + per-connection sessionId + app-bound disconnect
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-grace
createdAtJun 14, 2026, 9:10 AM
updatedAtJun 21, 2026, 2:17 AM
githubUrlhttps://github.com/neomjs/neo/issues/13196
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 14, 2026, 10:11 AM

Extended-NL Leaf A (emit): Bridge stamps the agent-message sidecar + per-connection sessionId + app-bound disconnect

Closed v13.1.0/archive-v13-1-0-chunk-2 enhancementaiarchitecture
neo-opus-grace
neo-opus-grace commented on Jun 14, 2026, 9:10 AM

Context

Sub-leaf of #13167 (the Extended-NL identity-transport umbrella), completing Leaf A row 2 (the Bridge→Client sidecar). #13167's Leaf A = "Bridge auth + sidecar"; the security-load-bearing auth half shipped as #13172 (PR #13181), deliberately split so the crypto landed in its own focused review. This is the remaining emit-flip: the Bridge stamps its authoritative agent identity into the forwarded frame so the App-side Client threads it to the topological-lock enforcement.

Sequenced AFTER Leaf B (#13174, merged): the Client learned to unwrap agent_message before the Bridge emits it, so the flip never breaks the live agent→app channel (per #13167's ordering rationale). Merges after the auth leaf #13181 — that leaf is what makes the stamped agentId trustworthy (a verified, signed identity rather than the spoofable ?id= claim).

The Problem

Bridge.handleAgentMessage forwards the bare payload.message to the target app, dropping the sender agentId. The merged enforcement primitives key held locks on the (agentId, sessionId) pair (LockRegistry #13125 / WriteGuard #13134), so the Client must receive both identifiers. The Bridge is the authoritative source: it binds the verified agentId at handshake (#13181) and owns the connection lifetime, making it the correct authority for a per-connection sessionId.

The Fix

  • Mint a stable per-connection sessionId (crypto.randomUUID()) at registerAgent — Bridge-authoritative + unspoofable (same trust model as the verified agentId), re-entrant for one connection. This satisfies WriteGuard's (agentId, sessionId) writer-pair requirement without #12984 canonicalization — the sessionId is the opaque, harness-native per-writer discriminator the lock semantics need.
  • Stamp the sidecar: handleAgentMessage forwards {type:'agent_message', agentId, sessionId, message} instead of the bare message — mirroring handleAppMessage's existing {type:'app_message', ...} envelope. Leaf B's parseAgentEnvelope reads {agentId, message} today (ignores the extra sessionId harmlessly); Leaf C extends it to extract sessionId.
  • Route disconnect: on agent close, broadcast {type:'agent_disconnected', agentId, sessionId} to apps so the App-side WriteGuard.releaseAll({agentId, sessionId}) sweeps the dead writer's held locks (connection-lifetime lease GC). Idempotent — a blanket app broadcast is safe.

Acceptance Criteria

  • registerAgent mints a stable per-connection ws.sessionId.
  • handleAgentMessage forwards the {type:'agent_message', agentId, sessionId, message} sidecar; the legacy/raw read path on the Client side is unaffected (Leaf B handles both frame shapes).
  • Agent disconnect broadcasts {type:'agent_disconnected', agentId, sessionId} to apps for WriteGuard lease release.
  • Unit proof on the existing Bridge.spec scaffolding: emit shape + sessionId binding + disconnect route.

Out of Scope

  • WriteGuard.releaseAll wiring + parseAgentEnvelope sessionId extraction — Leaf C (@neo-opus-ada).
  • The auth half (#13172 / PR #13181 — merges first).
  • sessionId cross-harness canonicalization (#12984 — not needed; the discriminator is opaque/harness-native).

Decision Record impact

none — completes the #13167 contract (ledger row 2); no ADR conflict.

Related

  • Umbrella: #13167 (this completes Leaf A row 2). Sequenced after Leaf B #13174 (merged) + auth #13181. Consumer: Leaf C (@neo-opus-ada).
  • Live latest-open sweep: checked the tracker + in-flight A2A claims; no separate (1b)-sidecar ticket exists — it was tracked only as the umbrella's Leaf A row 2 until now.

Authored by Claude Opus 4.8 (Claude Code), @neo-opus-grace (Grace). Origin Session ID: 0f5d9f1d-0683-452d-aac1-f467297186ac