Context
The 2026-06-14 agent identity drift (incident record #13239 / merged #13240) was a silent failure: a drifted GH_TOKEN made gh api user resolve to the wrong agent, so PRs were opened + reviews posted under a mis-attributed identity — and nothing surfaced it. The github-workflow healthcheck verifies gh auth status (token present + valid) but never that the authed identity is the expected agent, so a drifted-but-valid token passes the healthcheck clean. The drift was only caught by humans reading PR-body self-ids after the fact.
This is the robust auto-detection half of the prevention. The discipline rule (Codex worktree placement #13242) and the cross-family gate-hardening (#13234 → merged #13237) are in; the loud machine detection is not. Per the #13240 prevention section: "a wrong identity should not silently produce a valid-looking PR."
The Problem
The same failure class as #12450 (the Chroma query swallow): a real failure erased into a clean-looking result instead of surfaced. The healthcheck is the natural detection seam — it runs before work — but it currently asserts only token validity, not token identity. A degraded healthcheck on identity-mismatch catches a drifted token before any state-changing write, turning a silent multi-PR misattribution into a loud, single, pre-write halt.
The "expected agent" is knowable: the harness boots with NEO_AGENT_IDENTITY (e.g. @neo-gpt), and ai/graph/identityRoots.mjs IDENTITIES maps each identity id → its canonical properties.githubLogin. So the assertion is deterministic: gh api user --jq .login (the live authed login) must equal the expected identity's githubLogin, and the Memory-Core self-identity (the second surface that shares the drift) must equal it too.
The Architectural Reality
- The github-workflow healthcheck lives in
ai/services/github-workflow/HealthService.mjs (verifies gh auth status today).
- The expected-identity source is
ai/graph/identityRoots.mjs IDENTITIES — each AgentIdentity has id (@neo-...) + properties.githubLogin (@neo-...) + modelFamily. gh api user --jq .login returns the bare login (no @), so the comparison must normalize the @ prefix.
- The same identity-assertion logic is needed by the write-boundary guard (#13243, @neo-gpt, in
ai/mcp/server/github-workflow/toolService.mjs) — so it must be a single shared pure core, not duplicated per-site.
The Fix
- Pure shared core —
assertExpectedIdentity({expected, actualLogin, memoryCoreIdentity}) -> {ok, reason}: normalizes the @/bare forms, looks up expected's githubLogin in IDENTITIES, and returns {ok:false, reason:"identity drift: authed as X, expected Y"} on any mismatch (fail-closed on a missing/unmappable expected id). Deps injected (the actual login + the resolved identity table) -> unit-provable with no live gh / Memory-Core. Both this healthcheck and #13243's write-guard consume it (one source of truth). Exact file location validated via structural-pre-flight at build time (proposed: a shared ai/ identity util within both consumers' import reach).
- Healthcheck integration —
HealthService reads NEO_AGENT_IDENTITY + gh api user --jq .login + the Memory-Core self-identity, calls assertExpectedIdentity, and on !ok sets the healthcheck status to degraded with the explicit drift reason. Catches a drifted token before any write.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
assertExpectedIdentity({expected, actualLogin, memoryCoreIdentity}) (new pure core) |
this ticket |
returns {ok, reason}; fail-closed on mismatch / unmappable |
n/a (new surface) |
JSDoc Anchor & Echo |
unit spec |
HealthService healthcheck status |
ai/services/github-workflow/HealthService.mjs |
adds degraded on identity drift (was: only gh auth status) |
existing auth-status checks unchanged |
JSDoc |
unit + degraded-path test |
NEO_AGENT_IDENTITY (env) -> expected githubLogin |
ai/graph/identityRoots.mjs IDENTITIES |
read-only lookup |
fail-closed if id unmappable |
identityRoots JSDoc |
unit |
Decision Record impact
aligned-with the §6.1 cross-family identity model + the #13234 / #13237 gate-hardening — this is the detection counterpart (catches drift at the healthcheck; the gate hardens review eligibility). No ADR change. The canonical identity source is identityRoots.mjs.
Acceptance Criteria
Out of Scope
- The write-boundary fail-closed guard itself (#13243, @neo-gpt — the sibling consuming this core).
- The root github-workflow MCP
@me mis-map (the upstream cause; this hardens detection regardless of that fix).
- The cross-harness
GH_TOKEN isolation discipline (#13242 Codex; the Claude / Gemini equivalents are a separate follow-up).
- Auto-remediation (resetting the token) — this ticket DETECTS + surfaces; the repair is operator / harness-restart per the #13240 runbook.
Related
- Sibling: #13243 (@neo-gpt — the write-boundary guard consuming this core).
- Incident: #13239 / merged #13240 (the runbook; this is its prevention-section "detection seam"). Gate-hardening: #13234 (closed) -> #13237 (merged). Discipline: #13242 (merged).
- Same failure class: #12450 (silent failure swallowed into a clean result).
- Parent epic: #13012 (Agent Harness).
Release classification: post-release (Agent-harness integrity; not v13.x-blocking).
Live latest-open sweep: checked the latest 20 open issues + the A2A in-flight claim queue immediately before filing; #13243 is the sibling write-boundary part (not a dup), no equivalent healthcheck-assertion ticket or claim found.
Origin Session ID: 2d993feb-ea2f-4468-8fbd-c53e62365f4d
Retrieval Hint: "github-workflow healthcheck identity assertion drift detection assertExpectedIdentity NEO_AGENT_IDENTITY identityRoots"
Context
The 2026-06-14 agent identity drift (incident record #13239 / merged #13240) was a silent failure: a drifted
GH_TOKENmadegh api userresolve to the wrong agent, so PRs were opened + reviews posted under a mis-attributed identity — and nothing surfaced it. The github-workflow healthcheck verifiesgh auth status(token present + valid) but never that the authed identity is the expected agent, so a drifted-but-valid token passes the healthcheck clean. The drift was only caught by humans reading PR-body self-ids after the fact.This is the robust auto-detection half of the prevention. The discipline rule (Codex worktree placement #13242) and the cross-family gate-hardening (#13234 → merged #13237) are in; the loud machine detection is not. Per the #13240 prevention section: "a wrong identity should not silently produce a valid-looking PR."
The Problem
The same failure class as #12450 (the Chroma query swallow): a real failure erased into a clean-looking result instead of surfaced. The healthcheck is the natural detection seam — it runs before work — but it currently asserts only token validity, not token identity. A
degradedhealthcheck on identity-mismatch catches a drifted token before any state-changing write, turning a silent multi-PR misattribution into a loud, single, pre-write halt.The "expected agent" is knowable: the harness boots with
NEO_AGENT_IDENTITY(e.g.@neo-gpt), andai/graph/identityRoots.mjsIDENTITIESmaps each identity id → its canonicalproperties.githubLogin. So the assertion is deterministic:gh api user --jq .login(the live authed login) must equal the expected identity'sgithubLogin, and the Memory-Core self-identity (the second surface that shares the drift) must equal it too.The Architectural Reality
ai/services/github-workflow/HealthService.mjs(verifiesgh auth statustoday).ai/graph/identityRoots.mjsIDENTITIES— eachAgentIdentityhasid(@neo-...) +properties.githubLogin(@neo-...) +modelFamily.gh api user --jq .loginreturns the bare login (no@), so the comparison must normalize the@prefix.ai/mcp/server/github-workflow/toolService.mjs) — so it must be a single shared pure core, not duplicated per-site.The Fix
assertExpectedIdentity({expected, actualLogin, memoryCoreIdentity}) -> {ok, reason}: normalizes the@/bare forms, looks upexpected'sgithubLogininIDENTITIES, and returns{ok:false, reason:"identity drift: authed as X, expected Y"}on any mismatch (fail-closed on a missing/unmappableexpectedid). Deps injected (the actual login + the resolved identity table) -> unit-provable with no livegh/ Memory-Core. Both this healthcheck and #13243's write-guard consume it (one source of truth). Exact file location validated via structural-pre-flight at build time (proposed: a sharedai/identity util within both consumers' import reach).HealthServicereadsNEO_AGENT_IDENTITY+gh api user --jq .login+ the Memory-Core self-identity, callsassertExpectedIdentity, and on!oksets the healthcheck status todegradedwith the explicit drift reason. Catches a drifted token before any write.Contract Ledger Matrix
assertExpectedIdentity({expected, actualLogin, memoryCoreIdentity})(new pure core){ok, reason}; fail-closed on mismatch / unmappableHealthServicehealthcheck statusai/services/github-workflow/HealthService.mjsdegradedon identity drift (was: onlygh auth status)NEO_AGENT_IDENTITY(env) -> expectedgithubLoginai/graph/identityRoots.mjsIDENTITIESDecision Record impact
aligned-withthe §6.1 cross-family identity model + the #13234 / #13237 gate-hardening — this is the detection counterpart (catches drift at the healthcheck; the gate hardens review eligibility). No ADR change. The canonical identity source isidentityRoots.mjs.Acceptance Criteria
assertExpectedIdentitypure core exists with unit coverage: match ->{ok:true}; login mismatch ->{ok:false, reason}; Memory-Core-identity mismatch ->{ok:false}; missing / unmappableexpected-> fail-closed{ok:false};@/bare normalization covered.HealthServicehealthcheck returnsdegraded+ the explicit drift reason when the authed login OR the Memory-Core identity != the expected agent, and stays healthy when both match.Out of Scope
@memis-map (the upstream cause; this hardens detection regardless of that fix).GH_TOKENisolation discipline (#13242 Codex; the Claude / Gemini equivalents are a separate follow-up).Related
Release classification: post-release (Agent-harness integrity; not v13.x-blocking).
Live latest-open sweep: checked the latest 20 open issues + the A2A in-flight claim queue immediately before filing; #13243 is the sibling write-boundary part (not a dup), no equivalent healthcheck-assertion ticket or claim found.
Origin Session ID: 2d993feb-ea2f-4468-8fbd-c53e62365f4d
Retrieval Hint: "github-workflow healthcheck identity assertion drift detection assertExpectedIdentity NEO_AGENT_IDENTITY identityRoots"