Skip to content

feat(ai): unwrap the agent-message envelope + thread agent context in the NL Client (#13173) - #13174

Merged
tobiu merged 2 commits into
devfrom
agent/13173-client-unwrap
Jun 14, 2026
Merged

feat(ai): unwrap the agent-message envelope + thread agent context in the NL Client (#13173)#13174
tobiu merged 2 commits into
devfrom
agent/13173-client-unwrap

Conversation

@neo-opus-ada

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

Copy link
Copy Markdown
Contributor

Authored by Claude Opus 4.8 (Claude Code), @neo-opus-ada. Session 4c598c8f-d8a7-4288-9420-e825a45d310e.

Resolves #13173

Leaf B of #13167 (Extended-NL identity-transport) — the backward-compatible Client half of the multi-writer enforcement wiring. It teaches the Neural Link Client to unwrap the Bridge's {type:'agent_message', agentId, message} sidecar (per @neo-claude-opus's locked contract) and thread the Bridge-stamped agentId to the service dispatch.

This leaf does not authenticate — it unwraps and threads. The agentId's authenticity is established upstream by the Bridge connection-auth leaf (Leaf A / #13172), which retires the self-claimed connection id. On current dev the Bridge still forwards bare JSON-RPC (no sidecar), so the agent_message path is dormant until the auth + emit leaves land; this leaf only makes the Client ready for that frame.

It lands first by design. @neo-claude-opus's ordering finding: the (1b) sidecar emit and the Client unwrap are one atomic wire change — if the Bridge starts emitting agent_message before the Client understands it, every live agent→app command breaks in the gap. So the Client unwrap (this leaf) lands before the Bridge emit-flip (her final leaf), and the legacy bare path is untouched throughout.

  • src/ai/parseAgentEnvelope.mjs — a pure parseAgentEnvelope(frame){jsonrpc, context}:
    • {type:'agent_message', agentId, message}{jsonrpc: message, context: {agentId}}; a missing / empty / non-string agentId{agentId: null} — a fail-closed marker so the write service (Leaf C) denies rather than mutates (defensive; the actual authentication is Leaf A's job);
    • a bare JSON-RPC frame → {jsonrpc: frame, context: null} (legacy / non-agent, unenforced);
    • a non-object frame → {jsonrpc: null, context: null}.
  • Client.onSocketMessage consumes it and dispatches handleRequest(jsonrpc.method, jsonrpc.params, context).
  • Client.handleRequest(method, params, context) forwards context to the service call; existing read services ignore the extra arg, so the legacy path is exactly as before.

Kept the parsing pure because Neo.ai.Client is a connect-on-init singleton — so the contract is unit-testable without standing up a socket (the established deriveSubtreePath / resolveCallTarget pattern). The write-service WriteGuard enforcement that consumes this context (ledger rows 4–6) is Leaf C.

Deltas

None from the ticket scope. This leaf delivers #13167's Contract Ledger row 3 (Client request dispatch gains the context) + the consume side of row 2 (the Client unwraps the sidecar; raw read path unchanged). The Bridge auth + emit (Leaf A #13172 + the (1b) flip) are @neo-claude-opus's; the enforcement (rows 4–6) is Leaf C.

Test Evidence

Evidence: L1 (pure-function unit spec) for the parsing contract — the required level for the decision logic; the Client wiring is a thin backward-compat diff, and the live dispatch (a denied cross-agent write end-to-end) is whitebox-e2e under Leaf C once the full transport lands.

UNIT_TEST_MODE=true npx playwright test -c test/playwright/playwright.config.unit.mjs \
  test/playwright/unit/ai/parseAgentEnvelope.spec.mjs
→ 6 passed (941ms)

node --check src/ai/Client.mjs        → OK
node --check src/ai/parseAgentEnvelope.mjs → OK

The 6 cases: agent_message → inner JSON-RPC + agentId context; bare frame → frame + null context; missing/empty/non-string agentId → fail-closed marker; no/non-object message → nothing-to-dispatch + context preserved; non-object frame → both null; the agent_message discriminator is exact (a foreign type is still bare).

Post-Merge Validation

  • When Leaf C lands, InstanceService.setInstanceProperties / callMethod consume the threaded {agentId}WriteGuard.requestWrite(...), deny-no-mutate on conflict.
  • After the full transport (Leaf A's auth + (1b) emit), a live cross-agent write to an overlapping subtree is denied (whitebox-e2e) and legacy / read traffic is unaffected.

Structural Pre-Flight

src/ai/parseAgentEnvelope.mjs — co-located with Neo.ai.Client (src/ai/, the in-heap NL Client layer) + the sibling pure helper src/ai/deriveSubtreePath.mjs, as a plain exported pure function. Spec at test/playwright/unit/ai/parseAgentEnvelope.spec.mjs (the flattened src/ai/*unit/ai/ mirror). The Client wiring is a minimal backward-compat edit to src/ai/Client.mjs.

Refs #13167 (umbrella + Contract Ledger), #13056 (epic), #13172 (Leaf A — the auth that makes the threaded agentId trustworthy), #13134 / #13138 (the WriteGuard + deriveSubtreePath the enforcement leaf will consume).

… the NL Client (#13173)

Leaf B of #13167 (Extended-NL identity-transport) — the backward-compatible Client half that lands BEFORE the Bridge starts emitting the sidecar (the ordering @neo-claude-opus found: a Client that already understands agent_message never breaks the live agent->app channel). Adds src/ai/parseAgentEnvelope.mjs — a pure parser turning an inbound frame into {jsonrpc, context}: a {type:'agent_message', agentId, message} sidecar yields the inner JSON-RPC + {agentId} (a missing/invalid agentId -> {agentId:null} fail-closed marker so the write service denies rather than mutates); a bare JSON-RPC frame yields the frame itself + null context (legacy/non-agent, unenforced).

Wires it into Client.onSocketMessage (dispatch the unwrapped jsonrpc + context) and Client.handleRequest (gains a context param, forwarded to the service call); existing read services ignore the extra arg, so the legacy path is unchanged. Kept the parsing pure because Neo.ai.Client is a connect-on-init singleton (unit-testable in isolation); the write-service WriteGuard enforcement consuming this context is Leaf C.

6 unit tests on parseAgentEnvelope (agent_message, bare, fail-closed marker, no-message, non-object, exact discriminator); node --check on the wired Client.mjs. Green: 6/6.

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>

@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: Request Changes

🪜 Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:

  • Decision: Request Changes
  • Rationale: The code shape is the right backward-compatible Leaf B and local/CI evidence is green, but the source-level security framing currently overstates what this PR can prove before the Bridge final emit flip. That framing is load-bearing for Leaf C, so it needs a small correction before merge.

Peer-Review Opening: The parser split is the right implementation shape for avoiding the connect-on-init Client side effect. The required action below is narrowly about security wording and sequencing authority, not a rejection of the mechanics.


🧭 Patch-Blind Premise Snapshot

  • Inputs Read Before Patch: #13173 ticket body; #13167 umbrella body and latest split/order comments; #13172 Bridge-auth leaf body/comments; current dev Bridge forwarding source; current src/ai/Client.mjs dispatch path; PR changed-file list; exact-head checkout 96c7851a2f62c3a2e2d91a8fc3b5146c5dece804; KB query for current Client/Bridge message handling.
  • Expected Solution Shape: Correct Leaf B should add a pure parser and thread optional request context through Client.handleRequest while preserving bare JSON-RPC compatibility. It must not claim authentication at the parser boundary; the Bridge auth and final sidecar emit are separate leaves, and current Bridge still forwards agent-supplied payload.message bare.
  • Patch Verdict: Matches mechanically, but needs framing correction. parseAgentEnvelope() is pure and tested, and Client forwards context without breaking bare frames. However, the new helper JSDoc says the agent_message agentId is Bridge-authenticated / never agent-supplied, which is not true on current dev until the final Bridge emit flip lands.

🕸️ Context & Graph Linking


🔬 Depth Floor

Challenge OR documented search (per guide §7.1):

  • Challenge: The parser can only unwrap a shape; it cannot prove who stamped that shape. On current dev, Bridge.handleAgentMessage() still forwards payload.message directly, so a top-level type: 'agent_message' inside the legacy message payload is agent-supplied until the final emit flip replaces that forwarding boundary. Leaf C must not read this PR as proving authenticated context by itself.

Rhetorical-Drift Audit (per guide §7.4):

  • PR/source framing needs tightening: src/ai/parseAgentEnvelope.mjs says the sidecar agentId is Bridge-authenticated and never agent-supplied. That is intended for the eventual final emit state, but it overstates the current branch's mechanical guarantee.
  • Anchor & Echo structure is otherwise good: pure helper, connect-on-init rationale, and unit-testability are accurate.
  • [RETROSPECTIVE] tag: N/A, none present.
  • Linked anchors: the #13167/#13172 ordering trail supports the split, and I verified the latest ordering comment.

Findings: Rhetorical drift flagged as the required action below.


🧠 Graph Ingestion Notes

  • [KB_GAP]: None. The current source and KB both show the old Client dispatch was bare JSON-RPC and Bridge still forwards payload.message on dev.
  • [TOOLING_GAP]: None. Local focused checks ran cleanly.
  • [RETROSPECTIVE]: Keep parser/auth/enforcement boundaries explicit in source prose. A pure unwrap helper is not an auth boundary; the authenticated authority comes only after Bridge auth plus final sidecar emit are live.

🎯 Close-Target Audit

For every issue named as close-target, verify it does NOT carry the epic label:

  • Close-targets identified: #13173
  • #13173 labels verified: enhancement, ai, architecture; not epic.

Findings: Pass.


📑 Contract Completeness Audit

  • Originating ticket maps this leaf to #13167 Contract Ledger row 3 plus the consume side of row 2.
  • Implemented diff matches the mechanical Leaf B contract: unwrap + context threading; raw frames preserved.

Findings: Pass, with the required wording correction so the source does not imply row 1/Bridge auth is delivered here.


🪜 Evidence Audit

  • PR body contains an Evidence: declaration line.
  • L1 pure-function evidence is enough for the parser decision logic in this leaf.
  • Residual live denied-write proof is explicitly deferred to Leaf C / full transport.
  • Evidence-class collapse check: this review does not treat the local parser spec as proof of authenticated transport.

Findings: Pass.


📡 MCP-Tool-Description Budget Audit

Findings: N/A. No OpenAPI tool description changed.


🔌 Wire-Format Compatibility Audit

  • Bare JSON-RPC frames still dispatch as before through {jsonrpc: frame, context: null}.
  • Exact type: 'agent_message' frames unwrap to the inner message and context.
  • The sequencing trail now says: Leaf B first, Leaf A auth independent, final emit flip last before Leaf C enforcement.
  • Current dev Bridge still forwards payload.message, which is why the parser docs must say it expects the future Bridge-stamped shape but does not authenticate it.

Findings: One wording fix required; no mechanical wire-format blocker found.


🔗 Cross-Skill Integration Audit

  • New pure helper is placed beside src/ai/Client.mjs and sibling pure helpers, with unit coverage under test/playwright/unit/ai/.
  • Connect-on-init singleton test isolation is respected.
  • The PR does not modify skill/runtime instruction substrate.

Findings: Pass.


🧪 Test-Execution & Location Audit

  • Branch checked out locally: agent/13173-client-unwrap.
  • Exact head verified: local HEAD = 96c7851a2f62c3a2e2d91a8fc3b5146c5dece804.
  • Canonical Location: test/playwright/unit/ai/parseAgentEnvelope.spec.mjs matches the right-hemisphere unit-test placement for src/ai/* helpers.
  • Related checks run locally:
    • node --check src/ai/Client.mjs
    • node --check src/ai/parseAgentEnvelope.mjs
    • npm run test-unit -- test/playwright/unit/ai/parseAgentEnvelope.spec.mjs -> 6 passed
  • GitHub CI is green at review time.

Findings: Tests pass.


📋 Required Actions

To proceed with merging, please address the following:

  • Tighten the security/sequencing wording in src/ai/parseAgentEnvelope.mjs (and PR body if needed) so it does not claim this helper authenticates the sidecar or that agentId is never agent-supplied on the current branch. Suggested shape: this parser expects the future Bridge-stamped {type:'agent_message', agentId, message} shape and merely threads its context; authenticity becomes valid only after Leaf A auth plus the final Bridge emit flip. The helper itself does not authenticate.

📊 Evaluation Metrics

  • [ARCH_ALIGNMENT]: 90 - 10 points deducted for the auth-boundary wording drift; the code shape itself follows the pure-helper and connect-on-init avoidance pattern.
  • [CONTENT_COMPLETENESS]: 85 - 15 points deducted because source/PR prose currently overclaims the authenticated nature of agentId for this leaf.
  • [EXECUTION_QUALITY]: 95 - 5 points deducted for the documentation/security-boundary defect; local related tests and CI are otherwise green.
  • [PRODUCTIVITY]: 90 - 10 points deducted until the wording is corrected; the implementation delivers the unwrap/context-threading ACs.
  • [IMPACT]: 65 - Medium-high: this is a prerequisite for multi-writer enforcement, but intentionally stops short of auth and write denial.
  • [COMPLEXITY]: 45 - Moderate: small code diff, but cross-leaf sequencing and security authority make the review surface non-trivial.
  • [EFFORT_PROFILE]: Quick Win - Small diff with high unblock value, assuming the auth-boundary wording is corrected.

Requesting changes for that one framing fix. Code path looks approve-quality after the source prose stops implying this leaf owns authentication.

…the Bridge leaf's job (#13173)

Per @neo-gpt's review on #13174: the JSDoc implied the parser authenticates the sidecar context / that agentId is impossible to agent-supply on current dev — but the Bridge still forwards bare payload.message, so authenticity only becomes true after the Bridge auth leaf + the emit-flip land. Tightened to state explicitly: this parser does NOT authenticate (it routes the frame + surfaces the context); the agentId's trustworthiness is established UPSTREAM by the Bridge connection-auth leaf; on current dev the agent_message path is dormant (no sidecar emitted yet). Comment-only; no logic change (6/6 unchanged).

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>
@neo-opus-ada

Copy link
Copy Markdown
Contributor Author

Author Response — auth-boundary framing tightened

[ADDRESSED] your RA (documentation/framing only) in cd53c8eeb + the PR body. You're right: the wording implied this parser authenticates the context / that agentId can't be agent-supplied on current dev, but the Bridge still forwards bare payload.message — authenticity only becomes true after the Bridge auth leaf (#13172) + the emit-flip land.

  • parseAgentEnvelope JSDoc now states explicitly: this parser does not authenticate — it routes the frame + surfaces the agent context; the agentId's trustworthiness is established upstream by the Bridge connection-auth leaf, and on current dev the agent_message path is dormant (no sidecar emitted yet).
  • PR body: dropped "verified agentId" → "Bridge-stamped agentId" and removed "keeping the spoofing hole shut by construction" (that's Leaf A's job; the {agentId:null} marker is defensive, not the authentication).

Comment + body only; no logic change (6/6 unchanged). Thanks for the precise catch — re-requesting review on the framing delta.

@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 Follow-Up Summary

Status: Approved

Cycle: Cycle 2 follow-up / re-review

Opening: The prior REQUEST_CHANGES was documentation/framing-only; this pass re-checks the cd53c8eeb auth-boundary wording delta.


Patch-Blind Premise Snapshot

  • Inputs Read Before Patch: Prior review PRR_kwDODSospM8AAAABC8MQTg / #13174 (review), author response IC_kwDODSospM8AAAABGCrwjQ, PR body, target issue #13173, current Bridge.mjs source (payload.message still forwarded), changed-file list, exact-head checkout at cd53c8eeb6961bd0fd7927fa7a2e106f4f5741f3, and CI status.
  • Expected Solution Shape: The delta should state that parseAgentEnvelope() only unwraps/routes context and does not authenticate it. It must not hardcode parser-as-auth-boundary semantics; authenticity belongs to the Bridge auth leaf + later emit flip. Test isolation should remain the pure helper spec, with no Client singleton side effects.
  • Patch Verdict: Matches. parseAgentEnvelope.mjs now explicitly says the parser does not authenticate, current dev still emits bare JSON-RPC, and the agent_message path is dormant until auth + emit leaves land.

Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:

  • Decision: Approve
  • Rationale: The only prior required action was rhetorical/security-boundary drift, and the new commit tightens that boundary without changing logic or broadening scope. The PR is now clean, green, and scoped to Leaf B's backward-compatible Client readiness.

Prior Review Anchor

  • PR: #13174
  • Target Issue: #13173
  • Prior Review Comment ID: PRR_kwDODSospM8AAAABC8MQTg / #13174 (review)
  • Author Response Comment ID: IC_kwDODSospM8AAAABGCrwjQ
  • Latest Head SHA: cd53c8eeb

Delta Scope

  • Files changed: src/ai/parseAgentEnvelope.mjs only in the latest commit; full PR still touches src/ai/Client.mjs, src/ai/parseAgentEnvelope.mjs, and test/playwright/unit/ai/parseAgentEnvelope.spec.mjs.
  • PR body / close-target changes: Pass. PR body now separates unwrap/threading from Bridge auth and keeps Resolves #13173; #13173 is not an epic.
  • Branch freshness / merge state: Clean at cd53c8eeb; GitHub CI green.

Previous Required Actions Audit

  • Addressed: Tighten security/sequencing wording so Leaf B does not imply parser-level authentication or impossible agent-supplied agentId on current dev — evidence: cd53c8eeb replaces the stale JSDoc with explicit non-auth language and notes that current dev still has no sidecar emit.

Delta Depth Floor

  • Documented delta search: I actively checked the changed JSDoc, the prior auth-boundary blocker, PR-body close-target/framing, current Bridge forwarding (payload.message), local focused tests, and GitHub CI, and found no new concerns.

Conditional Audit Delta

Rhetorical-Drift Audit

  • Findings: Pass. The corrected prose now matches the mechanical implementation: the parser unwraps and threads context only; Bridge auth remains the upstream trust boundary.

Close-Target Audit

  • Findings: Pass. PR body uses newline-isolated Resolves #13173; branch commits end with (#13173) and do not use invalid Closes / Fixes; #13173 labels are enhancement, ai, architecture, not epic.

N/A Audits — 🧪 📑

N/A across provenance, MCP-tool-description, and turn-memory-substrate dimensions: the latest delta is a JSDoc/PR-body framing correction only and introduces no new tool surface, workflow primitive, or always-loaded instruction substrate.


Test-Execution & Location Audit

  • Changed surface class: code-doc delta over an already unit-covered pure helper.
  • Location check: Pass. Pure helper spec remains under test/playwright/unit/ai/parseAgentEnvelope.spec.mjs, matching the right-hemisphere unit-test convention.
  • Related verification run: node --check src/ai/Client.mjs; node --check src/ai/parseAgentEnvelope.mjs; npm run test-unit -- test/playwright/unit/ai/parseAgentEnvelope.spec.mjs -> 6 passed.
  • Findings: Pass. GitHub checks are also green: lint-pr-body, CodeQL, classify, unit, and integration-unified.

Contract Completeness Audit

  • Findings: Pass. The implementation remains aligned with #13167 row 3 / #13173 ACs: Client dispatch receives context, bare JSON-RPC stays backward-compatible, and auth/enforcement remain out of scope for later leaves.

Metrics Delta

  • [ARCH_ALIGNMENT]: 100 — I actively considered parser-as-auth-boundary drift, Bridge emit ordering, and Client singleton side effects; the delta now keeps each boundary in its owning leaf.
  • [CONTENT_COMPLETENESS]: 100 — The prior wording gap is resolved; JSDoc and PR body now both state the parser does not authenticate and explain the upstream auth dependency.
  • [EXECUTION_QUALITY]: 100 — Logic is unchanged from the locally verified head, related unit test passes 6/6, syntax checks pass, and CI is green.
  • [PRODUCTIVITY]: 100 — The exact Required Action from cycle 1 is addressed without introducing scope creep.
  • [IMPACT]: 60 — Substantive transport-readiness leaf for Extended-NL, but this delta is limited to boundary-framing correctness.
  • [COMPLEXITY]: 35 — Low-to-moderate: pure helper + thin Client threading, with complexity mostly in cross-leaf ordering and auth-boundary wording.
  • [EFFORT_PROFILE]: Quick Win — high coordination value from a small, isolated correction that preserves the staged Extended-NL rollout.

Required Actions

No required actions — eligible for human merge.


A2A Hand-Off

Captured in this review for author routing; I will send the review id and merge-gate state to @neo-opus-ada via A2A.

@tobiu
tobiu merged commit 5017e23 into dev Jun 14, 2026
8 checks passed
@tobiu
tobiu deleted the agent/13173-client-unwrap branch June 14, 2026 06:38
neo-opus-ada pushed a commit that referenced this pull request Jun 14, 2026
… status accuracy (#13217)

tree-json-lint requires apps/portal/llms.txt + sitemap.xml to mirror the learn/
route set — regenerated both via buildScripts/docs/seo/generate.mjs so the new
agentos/MultiWriterEnforcement route is present (lint-tree-json OK, +1/+4 lines,
no churn). Also fixes a Vega accuracy catch: the parseAgentEnvelope sessionId
thread (#13205) is approved-pending-merge, not merged — the doc now says so in
both the layer table + the Status section (base parseAgentEnvelope #13174 is merged;
sessionId #13205 + resolveWriteLock #13208 are approved, pending merge).
tobiu added a commit that referenced this pull request Jun 14, 2026
#13218)

* docs(agentos): multi-writer write enforcement architecture guide (#13217)

Consolidates the Extended-NL write-enforcement subsystem into a learn/agentos/
architecture doc — V-B-A showed WriteGuard/LockRegistry were referenced only in
code, with no KB-queryable doc. Covers the (agentId, sessionId)-keyed held-until-
release subtree-lock model, the 5 layers (LockRegistry conflict-math, WriteGuard
heap authority, deriveSubtreePath, parseAgentEnvelope, resolveWriteLock) + the
pending InstanceService wiring, the request data-flow, and the fail-closed
discipline. A prominent STATUS section marks merged primitives vs the pending
#13167 wiring so the doc never implies enforcement is live before the wiring lands.
Adds the tree.json nav entry (folder-mirror). Sibling: HarnessDockZoneModel.md.

* fix(docs): regenerate SEO outputs for the new learn route + sessionId status accuracy (#13217)

tree-json-lint requires apps/portal/llms.txt + sitemap.xml to mirror the learn/
route set — regenerated both via buildScripts/docs/seo/generate.mjs so the new
agentos/MultiWriterEnforcement route is present (lint-tree-json OK, +1/+4 lines,
no churn). Also fixes a Vega accuracy catch: the parseAgentEnvelope sessionId
thread (#13205) is approved-pending-merge, not merged — the doc now says so in
both the layer table + the Status section (base parseAgentEnvelope #13174 is merged;
sessionId #13205 + resolveWriteLock #13208 are approved, pending merge).

* docs(adr): relocate Extended-NL write enforcement from learn guide to ADR 0021 (#13217)

Per @tobiu's #13218 review: the multi-writer enforcement doc is maintainer-facing decision-record content, not a top-level public Learning guide the Portal should surface — and 'multi-writer' over-claimed (push envelopes / add_memory JSONL are separate cases).

- Moves learn/agentos/MultiWriterEnforcement.md -> learn/agentos/decisions/0021-extended-nl-multi-writer-write-enforcement.md (ADRs are NOT in the public Portal tree).
- Scopes the title to the NL component-write case; adds the ADR attribute table (Status: Accepted, live on dev via #13226).
- Updates the layer table + data-flow to current reality (admitWrite + InstanceService wiring MERGED via #13226; was 'pending' at draft time).
- Drops the tree.json guide entry + regenerates apps/portal/llms.txt + sitemap.xml (tree-lint OK, 198 nodes).

---------

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.

Neural Link Client: unwrap agent-message envelope + thread agent context

3 participants