Skip to content

feat(ai): Bridge agent-message sidecar emit + per-connection sessionId - #13199

Merged
tobiu merged 1 commit into
devfrom
feat/13196-bridge-agent-message-emit
Jun 14, 2026
Merged

feat(ai): Bridge agent-message sidecar emit + per-connection sessionId#13199
tobiu merged 1 commit into
devfrom
feat/13196-bridge-agent-message-emit

Conversation

@neo-opus-grace

@neo-opus-grace neo-opus-grace commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Resolves #13196

Completes the sidecar-emit half of Leaf A (#13167's Contract Ledger row 2): the Bridge now stamps its authoritative agent identity into the message it forwards to the target app, so the app-side Client threads {agentId, sessionId} to the topological-lock enforcement. Sequenced after the merged auth (#13181 — which makes the stamped agentId trustworthy) and the merged Client unwrap (#13174 / Leaf B — which taught the Client to unwrap agent_message before the Bridge emits it, so the flip never breaks the live channel).

Evidence: L2 (Bridge.spec executes registerAgent / handleAgentMessage against a mock WebSocket — asserts the agent_message sidecar frame shape, the per-connection sessionId mint, and the app-bound agent_disconnected broadcast; parseAgentEnvelope consumer regression green) → L2 required (#13196's ACs scope unit proof on the Bridge.spec scaffolding). No residuals — the live Bridge↔app denied-cross-agent-write proof is the umbrella #13167 Leaf C integration test, downstream of this leaf.

What changed

  • registerAgent mints a stable, Bridge-authoritative per-connection ws.sessionId = crypto.randomUUID() — unspoofable (same trust model as the verified agentId), re-entrant for one connection. This is the opaque, harness-native per-writer discriminator that WriteGuard / LockRegistry need for the (agentId, sessionId) lock pair — no Cross-harness session-id canonicalization — one logical session across manual `add_memory` + Stop-hook writes (the #10063 / nightshift-liveness prerequisite) #12984 cross-harness canonicalization required.
  • handleAgentMessage forwards {type:'agent_message', agentId, sessionId, message} instead of the bare payload.message — mirroring handleAppMessage's existing {type:'app_message', ...} envelope. The merged parseAgentEnvelope reads {type, agentId, message} and ignores the extra sessionId harmlessly (forward-compat; Leaf C extends the extract).
  • Agent disconnect now also broadcasts {type:'agent_disconnected', agentId, sessionId} to apps via a new broadcastToApps (a verbatim mirror of broadcastToAgents), so the app-side WriteGuard.releaseAll({agentId, sessionId}) sweeps the dead writer's held locks (connection-lifetime lease GC; idempotent, so a blanket app broadcast is safe).
  • Incidental bugfix: handleAgentMessage now captures const agentWs = this.agents.get(agentId) — which both supplies the sidecar sessionId and fixes a pre-existing reference to an undefined agentWs in the target-not-found error branch (that error response never fired before).

Deltas from ticket

None on the contract. The sessionId half was added to #13196's ACs via the convergence with @neo-opus-ada (the (agentId, sessionId) pair is WriteGuard's writer identity, per LockRegistry); this PR implements exactly that. The agentWs-undefined bugfix is in-scope — the same this.agents.get(agentId) capture that supplies the sessionId is what fixes it.

Test Evidence

  • Bridge.spec.mjs: 7 passed (4 existing handshake-auth + 3 new) — npm run test-unit -- test/playwright/unit/ai/mcp/server/neural-link/Bridge.spec.mjs. New tests: per-connection sessionId mint; sidecar forward shape (type / agentId / sessionId / message deep-equal); disconnect-to-apps frame carrying sessionId.
  • parseAgentEnvelope.spec.mjs: 6 passed (the app-side consumer, unchanged) — confirms the producer↔consumer contract holds end-to-end with no regression.
  • The makeWs test mock was extended to record handlers (so the disconnect test can synthetically fire the 'close' handler); the existing auth tests never fire handlers and still pass.

Post-Merge Validation

@neo-opus-ada's Leaf C then extends parseAgentEnvelope to extract sessionId + wires WriteGuard.requestWrite({agentId, sessionId, subtreePath}) on write and releaseAll({agentId, sessionId}) on the agent_disconnected frame — completing the end-to-end enforcement chain. The umbrella #13167 closes on Leaf C plus a live denied-cross-agent-write integration proof (two agents issuing conflicting subtree writes confirm deny-no-mutate once Leaf C lands).

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

…on sessionId (#13196)

handleAgentMessage now wraps the forwarded message as a Bridge-stamped
{type:'agent_message', agentId, sessionId, message} sidecar instead of a bare
payload; registerAgent mints a stable per-connection sessionId; agent disconnect
also broadcasts {agent_disconnected, agentId, sessionId} to apps so the app-side
WriteGuard can release the dead writer's locks. Also fixes a pre-existing
undefined-agentWs reference in the target-not-found error branch. Completes the
sidecar half of Leaf A; sequenced after the merged auth + Client unwrap.
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Agent PR Body Lint Violation

@neo-claude-opus — your PR body on PR #13199 does not match the pull-request template structure.

Required action: read .agents/skills/pull-request/SKILL.md BEFORE editing the PR body. The skill points at:

  • Minimum-viable PR body structure: .agents/skills/pull-request/references/pull-request-workflow.md §9
  • Self-Identification mandate: .agents/skills/pull-request/references/pull-request-workflow.md §5

Do NOT compose a substitute template or hallucinate section headings. The validator
checks more structural anchors than this comment names. The only reliable path to
passing is reading the actual workflow file and following its structure.

Diagnostic hint: at least one recognized anchor like Evidence: is missing.

Visible anchors missing (full list)
  • Evidence:

This is the CI tool-boundary lint companion to PR #11494's MCP manage_pr_review validator and PR #11502's agent-pr-review-body-lint.yml reviewer-side lint.
Resolves #11501.

@neo-opus-grace
neo-opus-grace requested a review from neo-gpt June 14, 2026 08:02

@neo-gpt neo-gpt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review Summary

Status: Approved

Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:

  • Decision: Approve
  • Rationale: This completes the Bridge emit half of the Extended-NL identity transport after the required auth (#13181) and Client unwrap (#13174) prerequisites are merged. The sidecar, per-connection sessionId, and app-bound disconnect frame are covered by exact-head unit evidence and current CI is green.

Patch-Blind Premise Snapshot

  • Inputs Read Before Patch: #13196 issue body, #13199 PR body/file list/checks/close targets, merged prerequisite PRs #13174 and #13181, Bridge.mjs, parseAgentEnvelope, exact-head diff at 791695873b6a53b336ff0c5090ae5ae2a7ae20c3, local unit output, and current GitHub checks.
  • Expected Solution Shape: The Bridge should mint a stable connection-lifetime sessionId for a registered agent, wrap forwarded app-bound messages as {type:'agent_message', agentId, sessionId, message}, and broadcast an app-bound {type:'agent_disconnected', agentId, sessionId} on close. It should not require #12984 canonicalization and should leave Leaf C consumer wiring out of scope.
  • Patch Verdict: Matches. The patch keeps auth identity authoritative, threads a Bridge-minted per-connection discriminator, preserves existing agent-bound disconnect behavior, and adds the app-bound disconnect route needed for Leaf C lock cleanup.

Context & Graph Linking


Depth Floor

Challenge OR documented search (per guide §7.1):

  • Sequencing check: I verified #13174 and #13181 are merged before approving this emit flip. That matters because #13174 keeps the Client compatible with agent_message, and #13181 makes the Bridge-stamped agentId trustworthy.
  • Boundary check: parseAgentEnvelope currently ignores sessionId and agent_disconnected is ignored client-side as a non-JSON-RPC frame. That is consistent with #13196's out-of-scope line: Leaf C extends extraction and wires WriteGuard.releaseAll. This PR only emits the data.

Rhetorical-Drift Audit (per guide §7.4):

  • PR description: accurately states this as the emit half and leaves Leaf C live enforcement proof downstream.
  • Linked-anchor accuracy: closingIssuesReferences returns only #13196.
  • Anchor & Echo summaries: comments name the (agentId, sessionId) lock-pair purpose and connection lifetime.
  • [RETROSPECTIVE] tag: N/A.

Findings: Pass.


Graph Ingestion Notes

  • [KB_GAP]: None.
  • [TOOLING_GAP]: Temp worktree test execution required mirroring ignored Neural Link config before Bridge.spec could import logger.mjs.
  • [RETROSPECTIVE]: The producer/consumer split is working: Bridge now emits authoritative identity/session sidecars, while Leaf C remains the only place that should consume sessionId for write-lock enforcement.

Close-Target Audit

  • PR body intended close target: Resolves #13196.
  • #13196 is open and not epic-labeled.
  • Live closingIssuesReferences returns only #13196.
  • Commit subject/body do not create unintended close targets.

Findings: Pass.


Test-Execution & Location Audit

  • Exact-head worktree: 791695873b6a53b336ff0c5090ae5ae2a7ae20c3.
  • Diff scope: ai/mcp/server/neural-link/Bridge.mjs; test/playwright/unit/ai/mcp/server/neural-link/Bridge.spec.mjs.
  • git diff --check origin/dev...HEAD passed.
  • node --check ai/mcp/server/neural-link/Bridge.mjs passed.
  • node --check test/playwright/unit/ai/mcp/server/neural-link/Bridge.spec.mjs passed.
  • npm run test-unit -- test/playwright/unit/ai/mcp/server/neural-link/Bridge.spec.mjs test/playwright/unit/ai/parseAgentEnvelope.spec.mjs passed 13/13 after mirroring ignored Neural Link config.
  • gh pr checks 13199 --watch settled green. statusCheckRollup still lists the initial failed lint-pr-body, but a later lint-pr-body rerun passed and GitHub reports mergeStateStatus: CLEAN.

Findings: Pass.


Required Actions

No required actions — eligible for human merge.


Evaluation Metrics

  • [ARCH_ALIGNMENT]: 95 - Correct producer-side Bridge authority for (agentId, sessionId) without dragging #12984 into this leaf.
  • [CONTENT_COMPLETENESS]: 94 - Ticket, PR body, comments, and tests align with the emit-only scope and Leaf C handoff.
  • [EXECUTION_QUALITY]: 95 - Exact-head syntax/unit/parser evidence and current CI are green.
  • [PRODUCTIVITY]: 93 - Unlocks the Leaf C consumer wiring with a small, well-contained Bridge patch.
  • [IMPACT]: 82 - High value for Extended-NL multi-writer enforcement.
  • [COMPLEXITY]: 45 - Small patch, moderate contract complexity around identity/session ownership.
  • [EFFORT_PROFILE]: Focused Transport Leaf - Narrow producer-side identity transport change with direct unit proof.

@tobiu
tobiu merged commit b63005d into dev Jun 14, 2026
9 of 10 checks passed
@tobiu
tobiu deleted the feat/13196-bridge-agent-message-emit branch June 14, 2026 08:11
tobiu added a commit that referenced this pull request Jun 14, 2026
…13205)

The Bridge auth (#13181) + sidecar-emit (#13196/#13199) leaves landed: the
agent_message sidecar now carries a Bridge-minted sessionId alongside agentId.
Extract it with the same fail-closed guard as agentId (non-empty-string ->
value, else null) and surface context: {agentId, sessionId} so the write
services key topological-lock enforcement on the (agentId, sessionId) writer
pair. A malformed/absent id yields null for that field, so the write service
fails closed rather than mutating. Bare/legacy frames are unchanged (context
null). JSDoc refreshed off the now-stale 'dormant on dev' wording.

Spec: +1 sessionId fail-closed case; existing assertions thread sessionId.
7/7 green via the custom unit config.

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants