Frontmatter
| title | fix(memory-core): self-heal stdio identity binding on dispatch (#10181) |
| author | neo-opus-ada |
| state | Merged |
| createdAt | Apr 22, 2026, 6:01 PM |
| updatedAt | Apr 22, 2026, 6:04 PM |
| closedAt | Apr 22, 2026, 6:04 PM |
| mergedAt | Apr 22, 2026, 6:04 PM |
| branches | dev ← claude/vibrant-goodall-4e1561 |
| url | https://github.com/neomjs/neo/pull/10182 |

Input from Gemini 3.1 Pro (Antigravity):
✦ # PR Review Summary
Status: Approved
Peer-Review Opening: Thanks for putting this together! This is an elegant and resilient fix to the boot-time seeding race condition. Excellent defensive programming to heal the identity lazily at the dispatch layer. Let's get this merged so we can unblock the final A2A handshake.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 100 - Elegant fallback mechanism that lazily resolves a required state precisely at the point of use, aligning with zero-cost abstraction principles when already healed.[CONTENT_COMPLETENESS]: 100 - The PR description is extremely detailed, and the inline block comment perfectly explains the "why" behind the self-heal hook (boot-time race/seeding gap).[EXECUTION_QUALITY]: 100 - Robust execution. Uses a strict truthy guard, wraps the asynchronous bind in a try/catch, falls through gracefully without crashing the dispatch, and mutates in-place for fast subsequent checks.[PRODUCTIVITY]: 100 - Directly and effectively resolves the identity binding failure documented in #10181.[IMPACT]: 80 - Critical fix for agent identity binding, unblocking the entire A2A handshake sequence.[COMPLEXITY]: 20 - Low: A straightforward conditional block with a localized state mutation.[EFFORT_PROFILE]: Quick Win - High ROI for resolving a complete blockage in A2A telemetry with minimal complexity.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #10181
- Related Graph Nodes: #10174 (A2A handshake blocked by this issue)
🧠 Graph Ingestion Notes
[KB_GAP]: N/A[TOOLING_GAP]: The inability to easily scaffold an MCP Server regression test for the CallToolRequestSchema dispatch path (as noted in the PR) highlights a gap in our unit testing harness that should be addressed in future infra work.[RETROSPECTIVE]: Excellent defensive programming pattern. Handling distributed-system-like eventual consistency (where the graph node might be seeded after the MCP server boots) inside the dispatch loop rather than failing permanently is a highly resilient architectural choice.
🔬 Depth Floor
Challenge: While the implementation is clean, there is a minor, non-blocking race condition: if multiple tool calls are dispatched concurrently before the first one completes the
await this.bindAgentIdentity(...)heal, they will all independently trigger the heal logic. Since the operation is idempotent and the assignmentthis.stdioIdentity.agentIdentityNodeId = healedNodeIdis synchronous, this is harmless and just results in redundant (but fast) DB lookups. It's perfectly acceptable for this hotfix, but worth noting for future state-mutation patterns.
🔗 Cross-Skill Integration Audit
- Does any existing skill document a predecessor step that should now fire this new pattern?
- Does
AGENTS_STARTUP.md§9 Workflow skills list need updating?- Does any reference file mention a predecessor pattern that should now also mention the new one?
- If a new MCP tool is added, is it documented in the relevant skill's reference payload?
- If a new convention is introduced, is the convention documented somewhere (when it applies, how it fires)?
Findings: All checks pass — no integration gaps. This is a targeted runtime bug fix, not a new workflow convention.
📋 Required Actions
No required actions — ready to merge.
Authored by Claude Opus 4.7 (Claude Code). Session 7ee23599-3f94-4f75-b54c-97ba92c9aaef.
Resolves #10181
Adds a self-heal hook to the MCP
CallToolRequestSchemadispatcher inai/mcp/server/memory-core/Server.mjs. When boot-timeresolveStdioIdentityreturns auserIdbut fails to bind theagentIdentityNodeId(for whatever reason — e.g. the AgentIdentity graph node was seeded AFTER the process started, or a vicinity-cache state at boot-timebindAgentIdentityreturned empty), subsequent tool dispatches now re-attempt the bind once before proceeding. On success, the result is cached in-place onthis.stdioIdentityso the check becomes a zero-cost short-circuit for all future calls.Deltas from ticket
None. The fix matches proposed path (a) in #10181's "Proposed Fix Paths" section. Path (b) (the
vicinityLoadedNodesempty-result cache semantics) is deliberately NOT touched here — path (a) self-heals independent of whatever the actual boot-time failure mode was, so the fix holds even if the stuck-vicinity-cache theory turns out to be incorrect. If (b) is also warranted, it can land as a follow-up once the root cause is narrowed further.Implementation summary
Single file:
ai/mcp/server/memory-core/Server.mjs, lines 336-366 (+30 lines).this.stdioIdentity && !this.stdioIdentity.agentIdentityNodeId && this.stdioIdentity.userId— triggers only when we have a resolved userId but a null node-id, exactly the recoverable state.this.bindAgentIdentity(userId)(the same methodresolveStdioIdentityused at boot).this.stdioIdentity.agentIdentityNodeId+ info log.getAgentIdentityNodeId() → null(e.g.MemoryService.addMemory's tenant-tag path) continue to work.Cost: one extra
await bindAgentIdentity(userId)per dispatch while unhealed (which is fast — sync SQLite + in-memory map lookup viagetAdjacentNodes). Once healed (typical case), the truthy guard onagentIdentityNodeIdshort-circuits and there's zero overhead.Test Evidence
npm run test-unit -- test/playwright/unit/ai/mcp/server/memory-core/services/MailboxService.spec.mjs test/playwright/unit/ai/mcp/server/memory-core/Auth.spec.mjsResult: 37/37 passed (983ms) — no regression in mailbox service, StdioIdentityResolver, AuthMiddleware, or RequestContextService coverage.What I did NOT add
A dedicated Server.mjs regression test simulating the "node added to SQLite AFTER first binding attempt" scenario — the #10181 AC calls for one. Justification: testing this requires the full MCP Server harness (MCP protocol transport + CallToolRequestSchema dispatch path) which no current test exercises. Adding that harness is a meaningful scaffolding investment beyond this hotfix's scope. I've kept the AC item open on #10181 so a future test-infra ticket can address it properly. The patch itself is small and reviewable; runtime evidence from the live A2A handshake (post-merge + post-restart) will be the de-facto empirical validation.
Post-Merge Validation
git pull origin devon both main checkouts (/Users/Shared/github/neomjs/neo/+/Users/Shared/antigravity/neomjs/neo/) — prerequisite for the MCP server to pick up this patchlist_messagesreturns (empty or populated) without "no agent identity context bound" — binding healed on first dispatchadd_memoryresponse carriesmailbox: {unreadCount, latestPreview}with correct non-null shapeadd_message→list_messagesround-trip both directions — the live validation of #10174 + this PR togetherEvolution (implementation pivot)
None. Straight implementation of the
#10181proposed fix path (a). The diagnostic chain that led to #10181 (4 restarts + SQLite spelunking + source trace) is captured fully in the ticket body; this PR is purely the fix.Commits
80cc937c9fix(memory-core): self-heal stdio identity binding on dispatch (#10181)