Author's Note: Filed by Claude Opus 4.7 (Claude Code) during session b5a17132-7324-46e1-b73e-038825bb4d55 after empirically grounding the multi-day mailbox-debugging arc. @tobiu's reframing this turn — "explore if we even need both formats... what is elegant, what we need... not how to monkey patch" — pivoted me from extending normalizeMailboxTarget with a 4th branch to recognizing the dual AGENT:/@ namespace was accidental accumulation, not a deliberate design. The architecturally elegant answer is single-canonical-format end to end.
Context
ai/graph/identityRoots.mjs is the canonical source of truth and declares only 4 seeded identities:
@neo-opus-4-7 (AgentIdentity)
@neo-gemini-3-1-pro (AgentIdentity)
@tobiu (AgentIdentity)
AGENT:* (BroadcastSentinel — the only legitimate AGENT: form, by design)
AGENT:alice/AGENT:bob/AGENT:charlie nodes presently in the live graph are test pollution from unit-test runs prior to #10229's isolation refactor. The existing ai/scripts/normalizeGraphIdentities.mjs (added in #10259, merged via #10262) acknowledges this in its PURGE_NODES list comment: "test-fixture nodes that leaked into production SQLite from unit test runs prior to #10229's isolation refactor."
The current normalizeMailboxTarget (ai/mcp/server/memory-core/services/MailboxService.mjs:34-63) accommodates the pollution by short-circuiting on to.includes(':') (line 59) — preserving AGENT:alice/bob/charlie test-fixture form unchanged. This accidentally also preserves AGENT:neo-gemini-3-1-pro (caller-typo of @neo-gemini-3-1-pro), causing silent SENT_TO edge culls at GraphService.linkNodes:240-243 FK-check — the empirical root cause of the multi-day mailbox-debugging arc.
Empirical Anchor (this session)
Two test messages sent identical-content, different formats:
| Test |
to: parameter |
SENT_TO edge persisted? |
| #A |
AGENT:neo-gemini-3-1-pro |
❌ NO (silently culled at FK-check; target node not found) |
| #B |
@neo-gemini-3-1-pro |
✅ YES (target = seeded @-prefix node) |
Direct SQLite verification confirmed both bugs (caller format + main-checkout staleness) stacked across multi-day arc. With main checkout now post-#10325 + caller using @-format, A2A is functionally working — but the dual-namespace residue remains, and any future caller writing AGENT:bare-name will silently fail again.
The Elegant Fix (3-phase migration)
Phase 1 — Production graph cleanup
Extend ai/scripts/normalizeGraphIdentities.mjs PURGE_NODES list to include AGENT:charlie (newer pollution discovered this session, not in current list). Run --apply to purge all three AGENT:<bare-name> test-fixture nodes from the live graph.
Phase 2 — Test fixture rename
Update unit tests using AGENT:alice/AGENT:bob/AGENT:charlie to use @alice/@bob/@charlie (single canonical format, matches production AgentIdentity convention). Search surface: test/playwright/unit/ai/mcp/server/memory-core/**/*.spec.mjs + any other mailbox/graph spec files referencing the old form.
Phase 3 — Normalizer simplification
Replace the current branched normalizeMailboxTarget with a single conceptually-simple rule:
function normalizeMailboxTarget(to) {
if (!to) return to;
if (to === 'AGENT:*') return to;
if (to.startsWith('AGENT:')) return '@' + to.slice('AGENT:'.length);
if (to.startsWith('@@')) return to.slice(1);
if (!to.startsWith('@') && !to.includes(':')) return '@' + to;
return to;
}
Eliminates the conceptual special-case that AGENT:alice differs from AGENT:neo-gemini-3-1-pro — single rule, unambiguous. Self-documenting via the explicit AGENT:* exception.
Acceptance Criteria
Out of Scope
- Phase 1 make-failure-loud (#10284) — sibling ticket; complementary substrate-layer defense that catches any future format-mismatch case via post-
linkNodes verification. Both this ticket and #10284 should land; they reinforce each other but are independent.
- ChromaDB metadata migration —
userId rows in Chroma metadata referencing AGENT:<bare-name> are orthogonal substrate. Out of scope per normalizeGraphIdentities.mjs line 32-34 comment.
- Backfilling missing SENT_TO edges on pre-existing orphaned messages — Gemini's 5 pre-#10325 messages + my 4 outbox have culled SENT_TO edges. Content recoverable via direct SQL but routing edges are gone permanently. Migration recovery would be a separate one-shot script.
- DreamService / Retrospective daemon reindexing — memories/summaries referencing the old
AGENT: aliases become stale pointers; accept staleness as low-frequency read-path per normalizeGraphIdentities.mjs line 34-36.
identityRoots.mjs schema changes — the canonical declaration is correct as-is; this ticket migrates to its convention, not modifies it.
Avoided Traps
- Extending
normalizeMailboxTarget with a 4th branch. Rejected — that's the monkey-patch path that perpetuates the dual-namespace mess. Architectural cleanup at the source is more sustainable.
- Preserving
AGENT:alice/bob/charlie "for backward compatibility" with existing tests. Rejected — those nodes shouldn't exist in production at all per normalizeGraphIdentities.mjs's explicit treatment as pollution. Tests should follow production's canonical convention, not the reverse.
- Treating dual-format as a deliberate two-namespace feature. Rejected per memory mining +
identityRoots.mjs declaration: only AGENT:* is legitimate AGENT: form. Everything else is accidental accumulation.
- Renaming
AGENT:* to @* for total uniformity. Rejected — AGENT:* is intentionally distinct because it's a type (BroadcastSentinel) not an identity. Conflating the broadcast-fanout sentinel with the identity namespace would muddy semantics.
- Running
--apply on normalizeGraphIdentities.mjs from a worktree. Rejected — script is cwd-sensitive (per memory and script body); must run from main checkout /Users/Shared/github/neomjs/neo/. Document in PR's Post-Merge Validation section.
Related
- #10259 / #10262 — predecessor: created
normalizeGraphIdentities.mjs for @opus/@gemini alias merge + initial AGENT:alice/bob purge. This ticket completes that migration arc.
- #10174 — original
normalizeMailboxTarget introduction with AGENT:@login + bare-name handling.
- #10284 — sibling Phase 1 make-failure-loud (post-
linkNodes verification). Complementary substrate-layer defense; reinforces this ticket's caller-layer cleanup.
- #10229 — test-isolation refactor; the prior fix that prevents new test pollution from leaking. This ticket cleans up the leakage that landed pre-#10229.
- #10311 — Epic: Institutionalizing Swarm Autonomy. A2A stability is the immediate prerequisite for Track 1 (cron heartbeat) + Track 2 (event-driven wakeups) per @tobiu's strategic sequencing this turn.
- #10139 — A2A Mailbox primitive epic; this ticket lives within that scope.
- #10325 — sharedEntity:true primitive; orthogonal RLS-bypass mechanism, not affected by this ticket but lands alongside it on the same substrate.
Origin Session ID: b5a17132-7324-46e1-b73e-038825bb4d55
Retrieval Hint: "identity-format unification AGENT @ canonical single-namespace pollution test-fixture rename normalizeMailboxTarget normalizeGraphIdentities purge migration mailbox SENT_TO edge cull elegant healing organism"
Context
ai/graph/identityRoots.mjsis the canonical source of truth and declares only 4 seeded identities:@neo-opus-4-7(AgentIdentity)@neo-gemini-3-1-pro(AgentIdentity)@tobiu(AgentIdentity)AGENT:*(BroadcastSentinel — the only legitimateAGENT:form, by design)AGENT:alice/AGENT:bob/AGENT:charlienodes presently in the live graph are test pollution from unit-test runs prior to #10229's isolation refactor. The existingai/scripts/normalizeGraphIdentities.mjs(added in #10259, merged via #10262) acknowledges this in itsPURGE_NODESlist comment: "test-fixture nodes that leaked into production SQLite from unit test runs prior to #10229's isolation refactor."The current
normalizeMailboxTarget(ai/mcp/server/memory-core/services/MailboxService.mjs:34-63) accommodates the pollution by short-circuiting onto.includes(':')(line 59) — preservingAGENT:alice/bob/charlietest-fixture form unchanged. This accidentally also preservesAGENT:neo-gemini-3-1-pro(caller-typo of@neo-gemini-3-1-pro), causing silent SENT_TO edge culls atGraphService.linkNodes:240-243FK-check — the empirical root cause of the multi-day mailbox-debugging arc.Empirical Anchor (this session)
Two test messages sent identical-content, different formats:
to:parameterAGENT:neo-gemini-3-1-pro@neo-gemini-3-1-pro@-prefix node)Direct SQLite verification confirmed both bugs (caller format + main-checkout staleness) stacked across multi-day arc. With main checkout now post-#10325 + caller using
@-format, A2A is functionally working — but the dual-namespace residue remains, and any future caller writingAGENT:bare-namewill silently fail again.The Elegant Fix (3-phase migration)
Phase 1 — Production graph cleanup
Extend
ai/scripts/normalizeGraphIdentities.mjsPURGE_NODESlist to includeAGENT:charlie(newer pollution discovered this session, not in current list). Run--applyto purge all threeAGENT:<bare-name>test-fixture nodes from the live graph.Phase 2 — Test fixture rename
Update unit tests using
AGENT:alice/AGENT:bob/AGENT:charlieto use@alice/@bob/@charlie(single canonical format, matches production AgentIdentity convention). Search surface:test/playwright/unit/ai/mcp/server/memory-core/**/*.spec.mjs+ any other mailbox/graph spec files referencing the old form.Phase 3 — Normalizer simplification
Replace the current branched
normalizeMailboxTargetwith a single conceptually-simple rule:function normalizeMailboxTarget(to) { if (!to) return to; if (to === 'AGENT:*') return to; // sentinel preserved (only legitimate AGENT: form) if (to.startsWith('AGENT:')) return '@' + to.slice('AGENT:'.length); // strip AGENT: from anything else if (to.startsWith('@@')) return to.slice(1); // strip accidental double-@ if (!to.startsWith('@') && !to.includes(':')) return '@' + to; // prepend missing @ on bare names return to; }Eliminates the conceptual special-case that
AGENT:alicediffers fromAGENT:neo-gemini-3-1-pro— single rule, unambiguous. Self-documenting via the explicitAGENT:*exception.Acceptance Criteria
ai/scripts/normalizeGraphIdentities.mjsPURGE_NODESextended to includeAGENT:charlie--applyrun against live graph; verify noAGENT:<bare-name>nodes remain (onlyAGENT:*should survive inAGENT:namespace)AGENT:alice/bob/charliemigrated to@alice/@bob/@charlie; all tests passnormalizeMailboxTargetsimplified to single-rule form; existing test cases (#10174 + #10259 regression suite) continue to pass with updated expectationsMailboxService.mjsJSDoc updated to reflect new normalization semantics + remove the "test-fixture preservation" carve-out commentAGENT:*→AGENT:*) handlingadd_message({to: 'AGENT:neo-gemini-3-1-pro'})post-fix, confirm SENT_TO edge persists (was silently culled pre-fix)Out of Scope
linkNodesverification. Both this ticket and #10284 should land; they reinforce each other but are independent.userIdrows in Chroma metadata referencingAGENT:<bare-name>are orthogonal substrate. Out of scope pernormalizeGraphIdentities.mjsline 32-34 comment.AGENT:aliases become stale pointers; accept staleness as low-frequency read-path pernormalizeGraphIdentities.mjsline 34-36.identityRoots.mjsschema changes — the canonical declaration is correct as-is; this ticket migrates to its convention, not modifies it.Avoided Traps
normalizeMailboxTargetwith a 4th branch. Rejected — that's the monkey-patch path that perpetuates the dual-namespace mess. Architectural cleanup at the source is more sustainable.AGENT:alice/bob/charlie"for backward compatibility" with existing tests. Rejected — those nodes shouldn't exist in production at all pernormalizeGraphIdentities.mjs's explicit treatment as pollution. Tests should follow production's canonical convention, not the reverse.identityRoots.mjsdeclaration: onlyAGENT:*is legitimateAGENT:form. Everything else is accidental accumulation.AGENT:*to@*for total uniformity. Rejected —AGENT:*is intentionally distinct because it's a type (BroadcastSentinel) not an identity. Conflating the broadcast-fanout sentinel with the identity namespace would muddy semantics.--applyonnormalizeGraphIdentities.mjsfrom a worktree. Rejected — script iscwd-sensitive (per memory and script body); must run from main checkout/Users/Shared/github/neomjs/neo/. Document in PR's Post-Merge Validation section.Related
normalizeGraphIdentities.mjsfor@opus/@geminialias merge + initialAGENT:alice/bobpurge. This ticket completes that migration arc.normalizeMailboxTargetintroduction withAGENT:@login+ bare-name handling.linkNodesverification). Complementary substrate-layer defense; reinforces this ticket's caller-layer cleanup.Origin Session ID:
b5a17132-7324-46e1-b73e-038825bb4d55Retrieval Hint:"identity-format unification AGENT @ canonical single-namespace pollution test-fixture rename normalizeMailboxTarget normalizeGraphIdentities purge migration mailbox SENT_TO edge cull elegant healing organism"