Context
PR #12414 (Resolves #10788) flipped Claude recovery to the osascript Tab-3 adapter and added @neo-opus-grace as a fifth hardcoded entry in resumeHarness.mjs's identityMap. Operator flagged the broader pattern: the recovery dispatcher's identity→harness routing and the appName → tabShortcut mapping are hardcoded — not usable outside Neo's own swarm + the Claude-Desktop-Tab-3 layout, a portability gap for the Agent-OS-on-client-codebases product.
The Problem
Two source-of-truth issues in ai/scripts/lifecycle/resumeHarness.mjs:
- Hardcoded roster.
identityMap literally enumerates @neo-gemini-pro / @neo-gpt / @neo-opus-ada / @neo-opus-grace. Activating an identity in the SSOT (ai/graph/identityRoots.mjs, e.g. #12413 / #12415) should not also require hand-editing this script — #12414 having to add the fifth line manually is the smell.
- Duplicated host-layout mapping. The
HARNESS_REGISTRY claude-desktop entry hardcodes appName:'Claude', tabShortcut:'3'; ai/daemons/bridge/daemon.mjs:641 independently hardcodes if (appName === 'Claude') tabShortcut = '3'. Same fact, two places.
The Architectural Reality
ai/graph/identityRoots.mjs is the canonical AgentIdentity roster and already carries appName + modelFamily per identity — the SSOT to derive routing from.
resumeHarness.mjs is a deliberately-lightweight lifecycle script; ai/scripts/migrations/backfillChromaSharedUserId.mjs avoids importing heavy modules for the same reason. A naive import of identityRoots may pull the Neo class system.
- Precedent for staying lightweight: PR #12405 made
chromaClientPrimitives.mjs's DestructiveOperationGuard import a lazy await import so a migration script could consume the primitive without the Neo-class weight. The same lazy/light pattern applies here.
The Fix
- Derive identity→harness routing from the
identityRoots SSOT (family/identity → adapter), so adding an identity is config-only.
- Single source for the
appName → tabShortcut mapping, consumed by both resumeHarness and bridge/daemon.mjs.
- Use a lightweight/lazy import path so
resumeHarness stays loadable in lightweight script contexts.
Acceptance Criteria
Out of Scope
osascript host-mechanics (Cmd+3 / paste) — inherently host-local, not the portability target.
- Changing recovery semantics or the adapter set.
Decision Record impact
none (refactor toward an existing SSOT; no ADR conflict).
Related
- Follows: #12414 (added the fifth hardcoded entry), #12413 / #12415 / #12418 (identity activation that should have been config-only).
- Pattern precedent: PR #12405 (lazy-import to keep a shared primitive lightweight).
Origin Session ID: 4cfb33e9-9d3a-4af7-afa8-e3d71ed4bcc7
Retrieval Hint: "resumeHarness identityMap hardcoded SSOT identityRoots appName tabShortcut bridge daemon duplicate #12414"
Context
PR #12414 (Resolves #10788) flipped Claude recovery to the
osascriptTab-3 adapter and added@neo-opus-graceas a fifth hardcoded entry inresumeHarness.mjs'sidentityMap. Operator flagged the broader pattern: the recovery dispatcher's identity→harness routing and theappName → tabShortcutmapping are hardcoded — not usable outside Neo's own swarm + the Claude-Desktop-Tab-3 layout, a portability gap for the Agent-OS-on-client-codebases product.The Problem
Two source-of-truth issues in
ai/scripts/lifecycle/resumeHarness.mjs:identityMapliterally enumerates@neo-gemini-pro/@neo-gpt/@neo-opus-ada/@neo-opus-grace. Activating an identity in the SSOT (ai/graph/identityRoots.mjs, e.g. #12413 / #12415) should not also require hand-editing this script — #12414 having to add the fifth line manually is the smell.HARNESS_REGISTRYclaude-desktopentry hardcodesappName:'Claude', tabShortcut:'3';ai/daemons/bridge/daemon.mjs:641independently hardcodesif (appName === 'Claude') tabShortcut = '3'. Same fact, two places.The Architectural Reality
ai/graph/identityRoots.mjsis the canonical AgentIdentity roster and already carriesappName+modelFamilyper identity — the SSOT to derive routing from.resumeHarness.mjsis a deliberately-lightweight lifecycle script;ai/scripts/migrations/backfillChromaSharedUserId.mjsavoids importing heavy modules for the same reason. A naiveimportofidentityRootsmay pull the Neo class system.chromaClientPrimitives.mjs'sDestructiveOperationGuardimport a lazyawait importso a migration script could consume the primitive without the Neo-class weight. The same lazy/light pattern applies here.The Fix
identityRootsSSOT (family/identity → adapter), so adding an identity is config-only.appName → tabShortcutmapping, consumed by bothresumeHarnessandbridge/daemon.mjs.resumeHarnessstays loadable in lightweight script contexts.Acceptance Criteria
resumeHarnessno longer enumerates@neo-*identities literally; routing derives fromidentityRoots(or an equivalently authoritative shared source).appName → tabShortcutmapping has one source consumed by bothresumeHarnessandbridge/daemon.mjs.resumeHarnessstill usable in lightweight script contexts; verify via the existingresumeHarness.specstatic checks + a lightweight-import probe).identityRootsrequires no edit toresumeHarness.Out of Scope
osascripthost-mechanics (Cmd+3 / paste) — inherently host-local, not the portability target.Decision Record impact
none(refactor toward an existing SSOT; no ADR conflict).Related
Origin Session ID: 4cfb33e9-9d3a-4af7-afa8-e3d71ed4bcc7 Retrieval Hint: "resumeHarness identityMap hardcoded SSOT identityRoots appName tabShortcut bridge daemon duplicate #12414"