The first real @neo-gpt-emmy bootstrap exposed a gap between the shipped Fleet rail and the operator's actual Codex workflow. Fleet currently registers one codex harness type and launches the bundled CLI in long-lived app-server mode (src/ai/fleet/harnessTypes.mjs:20-25, ai/services/fleet/deriveHarnessLaunchSpec.mjs:54-59). Emmy instead required an interactive second Codex Desktop process with a composed contract: an isolated Codex state/auth home, a native Chromium profile, the resident identity, and the resident checkout as the initial project.
The initial two-instance probe against installed ChatGPT/Codex build 26.707.31428 established the launch model:
CODEX_ELECTRON_USER_DATA_PATH alone is not the native profile selector. A direct launch with only that env fell back to the existing browser session; adding --user-data-dir=<electron-profile> produced a distinct resident process and profile tree.
CODEX_HOME=<codex-home> independently isolated Codex state/auth/memory; open-file inspection showed Emmy's process touching only Emmy's app-profile and Codex-home roots.
--open-project=<repoPath> opened the exact resident checkout through argv. A codex:// URL is forbidden for resident routing: macOS LaunchServices delivered it to the already-running default app and the attempted second process exited.
CODEX_ELECTRON_USER_DATA_PATH=<electron-profile> remains the app's JS-side mirror of the native profile path, and CODEX_SPARKLE_ENABLED=false made the secondary process log enableSparkle=false enableUpdater=false; one resident remains the updater owner.
A sterile launch omitted inherited CODEX_THREAD_ID; the shipped Fleet minimal-env allowlist already provides the correct base for that negative contract.
A fresh direct-spawn probe against build 26.707.41301 validated the positive half on three disposable profiles: the packaged main remained the tracked child, both isolated homes materialized, the exact project persisted, the resident group opened nothing below the default Codex profile, and the main child accepted SIGTERM. It also falsified the former shutdown assumption: two profile-scoped browser_crashpad_handler processes survived each main-process exit, re-parented to PID 1, and remained alive; both --disable-breakpad and --disable-crashpad-for-testing were ineffective. The current build also did not reproduce the older updater-disable log marker.
These installed-build facts are evidence for a capability-gated recipe—not timeless public APIs or an exact-version allowlist. Chromium documents --user-data-dir as the profile override (source); OpenAI documents CODEX_HOME as the Codex state root (source).
The Problem
The Fleet Manager can supervise headless Codex, Claude Code, Claude Desktop, and Antigravity, but cannot launch an isolated interactive Codex Desktop resident. Reusing codex for both modes would collapse two different process/auth/profile contracts behind one durable key:
headless codex app-server needs one CODEX_HOME;
Codex Desktop needs CODEX_HOME plus native --user-data-dir profile isolation; the app-side profile env mirrors that same path, and --open-project binds the resident checkout without a global URL route;
main-PID exit is not lifecycle completion on the current build: repeated Fleet start/stop would otherwise leak two Crashpad handlers per isolated profile.
Without a first-class recipe and a profile-bounded helper teardown, the operator must keep hand-launching and hand-cleaning GPT peers outside Fleet ownership.
The Architectural Reality
HARNESS_TYPES is the shared Body↔Brain vocabulary; the existing claude-code / claude-desktop split is the direct precedent.
deriveAgentInstanceHome owns the contained per-agent home root; the Codex Desktop recipe can derive two child directories beneath that one root rather than inventing another global root.
deriveHarnessLaunchSpec is the pure curated-intent map. Registry/UI payloads carry harnessType, never command/args/env.
FleetLifecycleService owns the minimal child environment, reserved identity/token injection, PID/state, and stop semantics (lines 109-125).
The isolated Electron profile is also the safe ownership boundary for Codex Desktop helper teardown: only packaged browser_crashpad_handler processes whose --database resolves below that exact profile belong to the instance. Generic descendant adoption remains unnecessary and out of scope.
Any new executable default belongs in the declarative AiConfig.fleet.harnessBinaries leaf (ai/config.template.mjs:66-93); no use-site env re-derivation.
The Fix
Add a distinct codex-desktop harness type while keeping codex byte-compatible as the headless app-server type.
Add an ADR-0019-conformant Codex Desktop main-binary leaf and a pure launch contract that derives <instanceHome>/codex-home and <instanceHome>/electron-profile.
Spawn the packaged main binary directly under Fleet supervision with --user-data-dir=<electron-profile>. Compose --open-project=<cwd> from the final startAgentProvisioned repo path; never route through codex://.
Inject CODEX_HOME=<codex-home>, CODEX_ELECTRON_USER_DATA_PATH=<electron-profile>, and CODEX_SPARKLE_ENABLED=false only inside the supervised child environment. NEO_AGENT_IDENTITY, the registry PAT, Bridge token, and projection mode remain lifecycle-owned reserved injections; ambient CODEX_THREAD_ID remains excluded.
Probe the packaged main binary before implementation: two distinct homes must coexist, materialize separate state/profile trees, open the requested resident checkout, leave the operator's default profile untouched, and keep the main binary as the directly tracked child.
After the tracked main PID exits, discover only packaged browser_crashpad_handler processes whose --database path resolves below this instance's exact Electron profile, terminate that bounded set, and prove zero residual profile-owned processes. Ambiguous executable/profile ownership fails status and must not broaden into generic descendant cleanup.
Guard the internal app-side profile/open-project/updater contracts with installed-build capability probes. Require a falsifiable updater-disabled marker; if any required capability is absent or ambiguous, surface the family as unavailable before spawn. Never use an exact-version allowlist or claim a detached launcher is running.
Keep OAuth operator-owned. Split GUI launch state from CLI auth state: marker checks use typed authHome=<instanceHome>/codex-home, while authCommand resolves the bundled CLI from AiConfig.fleet.harnessBinaries.codex.
deriveHarnessLaunchSpec + final provisioned cwd + installed-build capability probe
direct main binary; --user-data-dir=<profile> + --open-project=<repoPath>; CODEX_HOME + app-side profile mirror; secondary updater demonstrably off
fail unavailable before spawn; no codex:// / detached running claim / env-only updater assumption
launch-contract JSDoc
dual-instance + exact-cwd identity live probe + unit matrix
Profile-owned Crashpad teardown
tracked main PID + exact Electron profile + live 26.707.41301 falsifier
after main exit, terminate only packaged handlers whose --database is below the instance profile; require zero residual profile-owned processes
ambiguous executable/profile ownership fails status; never widen to generic descendant adoption
lifecycle JSDoc
three disposable-profile probes + unit falsifiers for foreign/ambiguous handlers
fleet.harnessBinaries.codexDesktop
ADR 0019 / AiConfig
executable path read at the use site
existing non-executable refusal
config JSDoc
config lint + lifecycle unit
OAuth handoff/status
isolated CODEX_HOME marker contract
per-home codex login; no credential bytes cross Fleet DTOs
authRequired: true until marker exists
conductor help/JSDoc
status + command-quoting specs
Decision Record impact
aligned-with ADR 0020 (Fleet-first external-harness lifecycle), ADR 0034 §2.3 (curated Add-Peer intent; no renderer command/env), and ADR 0019 (declarative config ownership). No ADR amendment is required.
Acceptance Criteria
codex-desktop is registered once in the shared harness authority; existing codex behavior and key remain unchanged.
The recipe derives separate Codex-home and Electron-profile directories beneath the contained per-agent instanceHome; the final provisioned repo path becomes --open-project=<repoPath>.
The direct-binary probe proves two concurrent isolated instances, native --user-data-dir profile separation, separate Codex state, exact-project opening, no default-profile mutation, and direct parent/child tracking of the packaged main.
After the tracked main exits, the lifecycle discovers and terminates only packaged Crashpad handlers whose --database resolves below that instance's Electron profile; ambiguous ownership fails status, foreign handlers remain untouched, and zero profile-owned processes remain.
An unsupported app capability fails before spawn when native profile isolation, the app-side mirror/open-project contract, or a falsifiable updater-disabled marker cannot be proven; status never reports a detached launcher as running.
The lifecycle injects the Codex home plus app-profile mirror, disables the secondary updater, and preserves its existing reserved identity/token/PAT variables without exposing raw command/env over the bridge or inheriting CODEX_THREAD_ID.
An exact-cwd clean-shell L3 receipt resolves NEO_AGENT_IDENTITY and gh api user --jq .login to the Fleet definition after shell startup; operator dotfiles may not silently shadow the resident binding.
Per-home OAuth handoff uses the actual resolved bundled CLI and marker status; credentials remain operator-owned and secret-free in DTO/log output.
Existing Codex app-server, Claude Code, Claude Desktop, and Antigravity launch/status specs remain green.
Focused unit evidence plus an explicitly labeled L3 local dual-instance receipt are attached to the PR.
Out of Scope
Managed-checkout config/data bootstrap (separate Fleet leaf) · account creation/PAT issuance · OAuth automation · cockpit form design (#14614) · generic descendant adoption or cleanup of helpers not proven to belong to the exact instance profile.
Avoided Traps
Do not overload codex with two incompatible modes.
Do not treat open -n as a supervised child.
Do not freeze an undocumented Electron variable without a revalidation trigger.
Do not add raw command/args/env fields to Fleet wire DTOs.
Do not weaken shutdown to “the main PID exited”; current-build probes prove that leaks two profile-owned Crashpad handlers per cycle.
Do not kill by process name alone; the exact packaged executable plus profile-scoped --database path is the ownership proof.
Context
The first real
@neo-gpt-emmybootstrap exposed a gap between the shipped Fleet rail and the operator's actual Codex workflow. Fleet currently registers onecodexharness type and launches the bundled CLI in long-livedapp-servermode (src/ai/fleet/harnessTypes.mjs:20-25,ai/services/fleet/deriveHarnessLaunchSpec.mjs:54-59). Emmy instead required an interactive second Codex Desktop process with a composed contract: an isolated Codex state/auth home, a native Chromium profile, the resident identity, and the resident checkout as the initial project.The initial two-instance probe against installed ChatGPT/Codex build
26.707.31428established the launch model:CODEX_ELECTRON_USER_DATA_PATHalone is not the native profile selector. A direct launch with only that env fell back to the existing browser session; adding--user-data-dir=<electron-profile>produced a distinct resident process and profile tree.CODEX_HOME=<codex-home>independently isolated Codex state/auth/memory; open-file inspection showed Emmy's process touching only Emmy's app-profile and Codex-home roots.--open-project=<repoPath>opened the exact resident checkout through argv. Acodex://URL is forbidden for resident routing: macOS LaunchServices delivered it to the already-running default app and the attempted second process exited.CODEX_ELECTRON_USER_DATA_PATH=<electron-profile>remains the app's JS-side mirror of the native profile path, andCODEX_SPARKLE_ENABLED=falsemade the secondary process logenableSparkle=false enableUpdater=false; one resident remains the updater owner.CODEX_THREAD_ID; the shipped Fleet minimal-env allowlist already provides the correct base for that negative contract.A fresh direct-spawn probe against build
26.707.41301validated the positive half on three disposable profiles: the packaged main remained the tracked child, both isolated homes materialized, the exact project persisted, the resident group opened nothing below the default Codex profile, and the main child accepted SIGTERM. It also falsified the former shutdown assumption: two profile-scopedbrowser_crashpad_handlerprocesses survived each main-process exit, re-parented to PID 1, and remained alive; both--disable-breakpadand--disable-crashpad-for-testingwere ineffective. The current build also did not reproduce the older updater-disable log marker.These installed-build facts are evidence for a capability-gated recipe—not timeless public APIs or an exact-version allowlist. Chromium documents
--user-data-diras the profile override (source); OpenAI documentsCODEX_HOMEas the Codex state root (source).The Problem
The Fleet Manager can supervise headless Codex, Claude Code, Claude Desktop, and Antigravity, but cannot launch an isolated interactive Codex Desktop resident. Reusing
codexfor both modes would collapse two different process/auth/profile contracts behind one durable key:codex app-serverneeds oneCODEX_HOME;CODEX_HOMEplus native--user-data-dirprofile isolation; the app-side profile env mirrors that same path, and--open-projectbinds the resident checkout without a global URL route;open -nlauncher is not a truthful PID/stop handle, matching the existing exclusion atderiveHarnessLaunchSpec.mjs:154-158;Without a first-class recipe and a profile-bounded helper teardown, the operator must keep hand-launching and hand-cleaning GPT peers outside Fleet ownership.
The Architectural Reality
HARNESS_TYPESis the shared Body↔Brain vocabulary; the existingclaude-code/claude-desktopsplit is the direct precedent.deriveAgentInstanceHomeowns the contained per-agent home root; the Codex Desktop recipe can derive two child directories beneath that one root rather than inventing another global root.deriveHarnessLaunchSpecis the pure curated-intent map. Registry/UI payloads carryharnessType, never command/args/env.FleetLifecycleServiceowns the minimal child environment, reserved identity/token injection, PID/state, and stop semantics (lines 109-125).browser_crashpad_handlerprocesses whose--databaseresolves below that exact profile belong to the instance. Generic descendant adoption remains unnecessary and out of scope.AiConfig.fleet.harnessBinariesleaf (ai/config.template.mjs:66-93); no use-site env re-derivation.The Fix
codex-desktopharness type while keepingcodexbyte-compatible as the headless app-server type.<instanceHome>/codex-homeand<instanceHome>/electron-profile.--user-data-dir=<electron-profile>. Compose--open-project=<cwd>from the finalstartAgentProvisionedrepo path; never route throughcodex://.CODEX_HOME=<codex-home>,CODEX_ELECTRON_USER_DATA_PATH=<electron-profile>, andCODEX_SPARKLE_ENABLED=falseonly inside the supervised child environment.NEO_AGENT_IDENTITY, the registry PAT, Bridge token, and projection mode remain lifecycle-owned reserved injections; ambientCODEX_THREAD_IDremains excluded.browser_crashpad_handlerprocesses whose--databasepath resolves below this instance's exact Electron profile, terminate that bounded set, and prove zero residual profile-owned processes. Ambiguous executable/profile ownership fails status and must not broaden into generic descendant cleanup.authHome=<instanceHome>/codex-home, whileauthCommandresolves the bundled CLI fromAiConfig.fleet.harnessBinaries.codex.Contract Ledger
HARNESS_TYPES: codex-desktopcodexremains headlessderiveHarnessLaunchSpec+ final provisionedcwd+ installed-build capability probe--user-data-dir=<profile>+--open-project=<repoPath>;CODEX_HOME+ app-side profile mirror; secondary updater demonstrably offcodex:/// detached running claim / env-only updater assumption26.707.41301falsifier--databaseis below the instance profile; require zero residual profile-owned processesfleet.harnessBinaries.codexDesktopCODEX_HOMEmarker contractcodex login; no credential bytes cross Fleet DTOsauthRequired: trueuntil marker existsDecision Record impact
aligned-with ADR 0020 (Fleet-first external-harness lifecycle), ADR 0034 §2.3 (curated Add-Peer intent; no renderer command/env), and ADR 0019 (declarative config ownership). No ADR amendment is required.
Acceptance Criteria
codex-desktopis registered once in the shared harness authority; existingcodexbehavior and key remain unchanged.instanceHome; the final provisioned repo path becomes--open-project=<repoPath>.--user-data-dirprofile separation, separate Codex state, exact-project opening, no default-profile mutation, and direct parent/child tracking of the packaged main.--databaseresolves below that instance's Electron profile; ambiguous ownership fails status, foreign handlers remain untouched, and zero profile-owned processes remain.CODEX_THREAD_ID.NEO_AGENT_IDENTITYandgh api user --jq .loginto the Fleet definition after shell startup; operator dotfiles may not silently shadow the resident binding.Out of Scope
Managed-checkout config/data bootstrap (separate Fleet leaf) · account creation/PAT issuance · OAuth automation · cockpit form design (
#14614) · generic descendant adoption or cleanup of helpers not proven to belong to the exact instance profile.Avoided Traps
codexwith two incompatible modes.open -nas a supervised child.--databasepath is the ownership proof.Related
Parent
#13015· launch-template predecessors#14914,#14972· GUI-auth ownership#14988· conductor#14937· cockpit consumer#14614.Origin Session ID: de713f27-0e82-4960-b4c6-f281e0c36449
Retrieval Hint:
query_raw_memories("Emmy isolated Codex Desktop CODEX_HOME CODEX_ELECTRON_USER_DATA_PATH manual onboarding friction")