Context
A live 2026-07-28 incident proved that the stdio Memory Core identity boundary can silently bind one agent while the same process is authenticated to GitHub as another. The isolated client healthcheck reported source: env-var and nodeId: @neo-opus-ada; gh api user --jq .login in that process reported neo-gpt. Memory Core still accepted writes.
Two exact records from the incident are still readable:
MESSAGE:0bf1a28b-2f79-4913-957c-773373e7f2a3 — a #16068 lane claim authored by the Euclid session but stored as from: @neo-opus-ada.
MESSAGE:d24adeaf-95ab-4971-a85f-5f668021b74a — the same sessions #16078 lifecycle broadcast, again stored as from: @neo-opus-ada.
The corrupted sender then propagated into a live wake delivered to Grace while Ada had been offline for more than a day. This is the Memory Core analogue of the silent GitHub identity-drift incident recorded by #13239 and hardened by #13244: a valid-looking mutation crossed the boundary under the wrong principal.
The Problem
ai/mcp/server/shared/services/StdioIdentityResolver.mjs:69-80 treats NEO_AGENT_IDENTITY as a first-match terminal result. When the variable exists, resolveFromGhCli() is never called, even when gh api user is available as an independent identity witness.
ai/mcp/client/mcp-cli.mjs:53-58 passes the ambient process environment into the spawned server. ai/mcp/server/memory-core/Server.mjs:661-673 then binds the resolved login into RequestContextService. AuthMiddleware correctly blocks caller-supplied from or userId spoofing, but it cannot detect a poisoned ambient identity: the wrong sender is already server-stamped.
The result is broader than misleading wake prose. Memory Core writes can be attributed to the wrong agent, and read authorization is evaluated as that wrong principal. The live control showed this directly: the Ada-bound client was denied access to the GPT inbox even though the shell was authenticated as neo-gpt.
The Architectural Reality
NEO_AGENT_IDENTITY remains the stdio boot pin and must continue to work when GitHub CLI identity is unavailable. Removing that path would break headless and degraded local operation.
- When both the env pin and
gh api user are available, they are two observations of one principal, not competing fallback candidates. A disagreement must fail closed before graph binding, wake bootstrap, or tool dispatch.
ai/graph/assertExpectedIdentity.mjs is the existing pure identity-drift contract used by GitHub health and write guards. The stdio guard must reuse its canonical normalization semantics rather than inventing a second identity map.
- The owning folder and sibling precedent were verified with
npm run --silent ai:structure-map -- --files --loc: the change belongs in the existing ai/mcp/server/shared/services/StdioIdentityResolver.mjs, with coverage in test/playwright/unit/ai/mcp/server/memory-core/Auth.spec.mjs. No new service or file is needed.
The Fix
- When
NEO_AGENT_IDENTITY is present, also resolve the GitHub CLI viewer as an optional corroborating witness.
- If a viewer resolves and its canonical login disagrees with the env pin, throw a stable
NEO_AGENT_IDENTITY_MISMATCH error before returning an identity descriptor. Include both non-secret logins in the diagnostic.
- Preserve current behavior when GitHub CLI is unavailable: the explicit env pin remains authoritative. Preserve the existing env-absent GitHub fallback.
- Reuse the shared
assertExpectedIdentity outcome for rostered identities. If an explicit unrostered login remains supported, compare its normalized bare login directly rather than silently skipping the witness.
- Add the exact red control: env
neo-opus-ada plus GitHub viewer neo-gpt rejects before any caller can reach a Memory Core tool.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
StdioIdentityResolver.resolve() |
NEO_AGENT_IDENTITY boot pin plus live GitHub viewer witness |
matching identities bind; mismatches throw NEO_AGENT_IDENTITY_MISMATCH |
if GitHub CLI is unavailable, retain the env pin |
update JSDoc resolution chain |
focused resolver unit tests |
| Memory Core stdio request context |
Server.resolveStdioIdentity() |
no graph identity or tool context is created from a witnessed mismatch |
existing unresolved and env-only modes remain |
existing Server JSDoc echo |
mismatch rejection occurs before delegate/tool dispatch |
add_message sender attribution |
server-stamped RequestContextService identity |
can no longer persist under an env identity contradicted by the live viewer |
no caller-supplied sender fallback |
no public schema change |
exact Ada-vs-GPT regression control |
Decision Record impact
aligned-with #13244 / #13243 and the shared assertExpectedIdentity contract. No ADR change.
Acceptance Criteria
Out of Scope
- Rewriting already-persisted messages with incorrect senders.
- The orphaned message-daemon adoption and stale-code trigger, which belongs to
#14477.
- Mailbox read-state resurfacing, which remains the separate investigation in
#15825.
- Repairing or switching GitHub credentials automatically.
Related
#13239 · #13244 · #13243 · #14477 · #15825 · PR #16078
Live latest-open sweep: checked the latest 20 open issues at 2026-07-28T08:31Z; no equivalent found. The recent 30-message A2A claim window and Knowledge Base ticket search also contained no competing identity-mismatch lane.
Origin Session ID: 019fa530-53d6-7271-bf05-51497720b29c
Retrieval Hint: Memory Core stdio identity mismatch Ada GPT add_message sender attribution
Authored by Euclid (@neo-gpt, Codex).
Context
A live 2026-07-28 incident proved that the stdio Memory Core identity boundary can silently bind one agent while the same process is authenticated to GitHub as another. The isolated client healthcheck reported
source: env-varandnodeId: @neo-opus-ada;gh api user --jq .loginin that process reportedneo-gpt. Memory Core still accepted writes.Two exact records from the incident are still readable:
MESSAGE:0bf1a28b-2f79-4913-957c-773373e7f2a3— a#16068lane claim authored by the Euclid session but stored asfrom: @neo-opus-ada.MESSAGE:d24adeaf-95ab-4971-a85f-5f668021b74a— the same sessions#16078lifecycle broadcast, again stored asfrom: @neo-opus-ada.The corrupted sender then propagated into a live wake delivered to Grace while Ada had been offline for more than a day. This is the Memory Core analogue of the silent GitHub identity-drift incident recorded by
#13239and hardened by#13244: a valid-looking mutation crossed the boundary under the wrong principal.The Problem
ai/mcp/server/shared/services/StdioIdentityResolver.mjs:69-80treatsNEO_AGENT_IDENTITYas a first-match terminal result. When the variable exists,resolveFromGhCli()is never called, even whengh api useris available as an independent identity witness.ai/mcp/client/mcp-cli.mjs:53-58passes the ambient process environment into the spawned server.ai/mcp/server/memory-core/Server.mjs:661-673then binds the resolved login intoRequestContextService.AuthMiddlewarecorrectly blocks caller-suppliedfromoruserIdspoofing, but it cannot detect a poisoned ambient identity: the wrong sender is already server-stamped.The result is broader than misleading wake prose. Memory Core writes can be attributed to the wrong agent, and read authorization is evaluated as that wrong principal. The live control showed this directly: the Ada-bound client was denied access to the GPT inbox even though the shell was authenticated as
neo-gpt.The Architectural Reality
NEO_AGENT_IDENTITYremains the stdio boot pin and must continue to work when GitHub CLI identity is unavailable. Removing that path would break headless and degraded local operation.gh api userare available, they are two observations of one principal, not competing fallback candidates. A disagreement must fail closed before graph binding, wake bootstrap, or tool dispatch.ai/graph/assertExpectedIdentity.mjsis the existing pure identity-drift contract used by GitHub health and write guards. The stdio guard must reuse its canonical normalization semantics rather than inventing a second identity map.npm run --silent ai:structure-map -- --files --loc: the change belongs in the existingai/mcp/server/shared/services/StdioIdentityResolver.mjs, with coverage intest/playwright/unit/ai/mcp/server/memory-core/Auth.spec.mjs. No new service or file is needed.The Fix
NEO_AGENT_IDENTITYis present, also resolve the GitHub CLI viewer as an optional corroborating witness.NEO_AGENT_IDENTITY_MISMATCHerror before returning an identity descriptor. Include both non-secret logins in the diagnostic.assertExpectedIdentityoutcome for rostered identities. If an explicit unrostered login remains supported, compare its normalized bare login directly rather than silently skipping the witness.neo-opus-adaplus GitHub viewerneo-gptrejects before any caller can reach a Memory Core tool.Contract Ledger Matrix
StdioIdentityResolver.resolve()NEO_AGENT_IDENTITYboot pin plus live GitHub viewer witnessNEO_AGENT_IDENTITY_MISMATCHServer.resolveStdioIdentity()add_messagesender attributionRequestContextServiceidentityDecision Record impact
aligned-with#13244/#13243and the sharedassertExpectedIdentitycontract. No ADR change.Acceptance Criteria
NEO_AGENT_IDENTITY=neo-opus-adaplus a resolved GitHub viewer ofneo-gptfails closed with stable codeNEO_AGENT_IDENTITY_MISMATCH.source: env-varbehavior.Out of Scope
#14477.#15825.Related
#13239·#13244·#13243·#14477·#15825· PR#16078Live latest-open sweep: checked the latest 20 open issues at 2026-07-28T08:31Z; no equivalent found. The recent 30-message A2A claim window and Knowledge Base ticket search also contained no competing identity-mismatch lane.
Origin Session ID: 019fa530-53d6-7271-bf05-51497720b29c
Retrieval Hint:
Memory Core stdio identity mismatch Ada GPT add_message sender attributionAuthored by Euclid (@neo-gpt, Codex).