One command boots the cockpit against the live containerized plane
Parent: #17271 (slice 1 — the journey). The parent's AC5 (the operator-witnessed L1 beat) stays with the parent; this leaf owns the mechanics the operator's session will run: one documented command that brings the cockpit up against the LIVE containerized plane with the full read set armed.
Context
npm run cockpit (the documented one-command journey) boots the fleet transport in in-process mode unless the operator hand-exports NEO_FLEET_PLANE_BASE + NEO_FLEET_PLANE_BEARER — the hand-assembly #17271 names as the gap. Every piece of plane-mode machinery already exists in ai/services/fleet/devFleetServer.mjs (verified at intake): plane admission with plane-side viewer verification, presence via createPlaneWhoIsOnlineReader, activity/mailbox/compose/catch-up through the proven plane client, honest-degraded wake lanes. What does not exist is the JOURNEY: a single command that resolves the binding and arms it.
The Architectural Reality
buildScripts/devCockpit.mjs supervises the two processes (webpack + fleet transport) and owns the launch contract (process bearer, handshake arming, probe/plan/refuse discipline).
- Plane config is leaf-owned (
ai/configBase.mjs: fleet.planeBase, fleet.planeBearer, fleet.planeBearerFile, fleet.planeAdmissionBearer(File)) — consumed at the use site per ADR-0019; no new leaves, no re-derivation.
- The canonical local plane publishes
http://127.0.0.1:3102 (ADR-0019 §10.7 local profile; ai/scripts/lifecycle/local-agent-os/README.md), path-routing authenticated /mc/mcp; its auth guard answers unauthenticated calls 401 invalid_token — the plane's identity signature (verified live).
- The viewer identity chain (
StdioIdentityResolver: env-var → gh CLI) and the plane's provider-PAT authority resolve the SAME subject when the bearer comes from gh auth token — the single-viewer invariant holds by construction.
The Fix
npm run cockpit:live → buildScripts/devCockpit.mjs --live:
- Resolve the binding (
resolveLivePlaneConfig): base = NEO_FLEET_PLANE_BASE else the canonical local plane; bearer = NEO_FLEET_PLANE_BEARER else the NEO_FLEET_PLANE_BEARER_FILE secret file (materialized into the fleet child's direct env channel — the dev entry reads only the direct leaf) else gh auth token. Fail-closed: pinned-but-unreadable file refuses (never falls through to a different credential); all-three-empty refuses with the remediation. The bearer VALUE never enters any note or log.
- Probe the plane (
probePlaneIdentity): unauthenticated; the auth guard's 401 IS the identity signature. Unreachable refuses pre-spawn naming the plane-start compose command. Bearer VALIDITY stays with the fleet entry's verified admission — one verification authority.
- Never adopt an incumbent: live mode refuses an occupied
:8083 — an incumbent's plane binding is not observable through /fleet/probe, so adoption could point the cockpit at the wrong plane.
- Custody (
buildFleetChildEnv): the resolved binding rides ONLY the fleet child's env — never the webpack child, never process.env mutation.
- Docs:
learn/agentos/RunningTheFleetCockpit.md gains the live-plane section (command, resolution order, honest-degraded lanes).
Contract Ledger
| # |
Surface (live anchor) |
Delta |
Source of authority |
Fallback / degraded |
Docs |
Evidence |
| 1 |
cockpit:live npm script (package.json) |
Additive |
This ticket |
n/a — new |
runbook §"The one command against the live plane" |
composed live run receipt |
| 2 |
buildScripts/devCockpit.mjs --live branch |
Additive; the zero-setup journey is byte-identical without the flag |
devCockpit.mjs launch contract |
refusals are named + pre-spawn |
module @summary |
9 new unit witnesses, suite 20/20 |
| 3 |
fleet.planeBase / fleet.planeBearer(File) (existing leaves) |
Consumed, unchanged |
ADR-0019 (use-site reads) |
empty base default → canonical local plane (named in notes) |
runbook |
resolution witnesses |
| 4 |
gh auth token fallback |
New consumption of the viewer-claim identity's own credential |
the stdio identity chain (env → gh CLI) |
gh absent/unauthenticated → named refusal |
runbook |
gh-fallback witness + live run |
| 5 |
Plane MCP ingress probe |
New, unauthenticated, side-effect-free |
the plane's own auth-guard 401 |
unreachable → plane-start remediation |
runbook |
probe witnesses (401/200/closed) |
| 6 |
Fleet wire envelopes / handshake payload |
Unchanged |
fleetWireMethods.mjs parity lint |
n/a |
n/a |
parity lint untouched in CI |
| 7 |
learn/agentos/RunningTheFleetCockpit.md |
New section |
this ticket |
n/a |
the file itself |
doc diff |
Decision Record impact
aligned-with ADR 0038 (FM-as-client topology: the cockpit consumes the plane through the authenticated MCP boundary; this ticket changes no topology, only the dev journey to it). ADR-0019 read per its read-gate; no config-leaf changes.
Acceptance Criteria
Out of Scope
The operator-witnessed L1 beat (parent #17271 AC5 — the closing evidence lives with the parent) · app-side rosterSourceMode semantics (intake evidence: the promotion/admission paths already render selected-source truth for populated registries, and the empty-registry case renders the labelled static roster with its retained cause — no masking to fix) · the devFleetServer planeBearerFile-indirection gap observed at intake (adjacent debt: the dev entry reads only the direct leaf; this journey's launcher materializes the file's value, so the journey is unblocked; the entry-level gap is recorded on the parent) · wake-push admission credentials (the deployment declares them; the journey renders the honest not-armed lane).
Avoided Traps
- A second supervisor process (a
devCockpitLive wrapper spawning devCockpit): nested supervisors for zero behavioral gain — the flag branch reuses the entire probe/plan/spawn/supervise machinery.
- Re-verifying the bearer in the launcher (MCP initialize duplication): the fleet entry's plane-side verified admission is the one verification authority; the launcher fails fast only on reachability.
- Live-mode incumbent reuse: extending
/fleet/probe to report plane bindings was rejected as wire-surface growth for a journey that can simply refuse named.
Related
Parent: #17271 · Epic: #14560 · the honest presence surface this consumes: #17248 / #17225 · ADR 0038 · ai/scripts/lifecycle/local-agent-os/README.md (the plane-start command).
Live latest-open sweep: latest 20 open checked 2026-08-17T07:45Z, no equivalent; A2A herd-window sweep clean (active claims: #17239/#17272 Ada, #17271 mine).
Origin Session ID: 0c5a1cf3-093b-4e9d-a7ba-74137e4d4f23
Retrieval Hint: query_raw_memories("cockpit:live one command live plane journey resolveLivePlaneConfig probePlaneIdentity buildFleetChildEnv")
One command boots the cockpit against the live containerized plane
Parent: #17271 (slice 1 — the journey). The parent's AC5 (the operator-witnessed L1 beat) stays with the parent; this leaf owns the mechanics the operator's session will run: one documented command that brings the cockpit up against the LIVE containerized plane with the full read set armed.
Context
npm run cockpit(the documented one-command journey) boots the fleet transport in in-process mode unless the operator hand-exportsNEO_FLEET_PLANE_BASE+NEO_FLEET_PLANE_BEARER— the hand-assembly #17271 names as the gap. Every piece of plane-mode machinery already exists inai/services/fleet/devFleetServer.mjs(verified at intake): plane admission with plane-side viewer verification, presence viacreatePlaneWhoIsOnlineReader, activity/mailbox/compose/catch-up through the proven plane client, honest-degraded wake lanes. What does not exist is the JOURNEY: a single command that resolves the binding and arms it.The Architectural Reality
buildScripts/devCockpit.mjssupervises the two processes (webpack + fleet transport) and owns the launch contract (process bearer, handshake arming, probe/plan/refuse discipline).ai/configBase.mjs:fleet.planeBase,fleet.planeBearer,fleet.planeBearerFile,fleet.planeAdmissionBearer(File)) — consumed at the use site per ADR-0019; no new leaves, no re-derivation.http://127.0.0.1:3102(ADR-0019 §10.7 local profile;ai/scripts/lifecycle/local-agent-os/README.md), path-routing authenticated/mc/mcp; its auth guard answers unauthenticated calls401 invalid_token— the plane's identity signature (verified live).StdioIdentityResolver: env-var → gh CLI) and the plane's provider-PAT authority resolve the SAME subject when the bearer comes fromgh auth token— the single-viewer invariant holds by construction.The Fix
npm run cockpit:live→buildScripts/devCockpit.mjs --live:resolveLivePlaneConfig): base =NEO_FLEET_PLANE_BASEelse the canonical local plane; bearer =NEO_FLEET_PLANE_BEARERelse theNEO_FLEET_PLANE_BEARER_FILEsecret file (materialized into the fleet child's direct env channel — the dev entry reads only the direct leaf) elsegh auth token. Fail-closed: pinned-but-unreadable file refuses (never falls through to a different credential); all-three-empty refuses with the remediation. The bearer VALUE never enters any note or log.probePlaneIdentity): unauthenticated; the auth guard's 401 IS the identity signature. Unreachable refuses pre-spawn naming the plane-start compose command. Bearer VALIDITY stays with the fleet entry's verified admission — one verification authority.:8083— an incumbent's plane binding is not observable through/fleet/probe, so adoption could point the cockpit at the wrong plane.buildFleetChildEnv): the resolved binding rides ONLY the fleet child's env — never the webpack child, neverprocess.envmutation.learn/agentos/RunningTheFleetCockpit.mdgains the live-plane section (command, resolution order, honest-degraded lanes).Contract Ledger
cockpit:livenpm script (package.json)buildScripts/devCockpit.mjs--livebranchdevCockpit.mjslaunch contract@summaryfleet.planeBase/fleet.planeBearer(File)(existing leaves)gh auth tokenfallbackfleetWireMethods.mjsparity lintlearn/agentos/RunningTheFleetCockpit.mdDecision Record impact
aligned-with ADR 0038(FM-as-client topology: the cockpit consumes the plane through the authenticated MCP boundary; this ticket changes no topology, only the dev journey to it). ADR-0019 read per its read-gate; no config-leaf changes.Acceptance Criteria
npm run cockpit:liveboots the supervised cockpit with the fleet transport bound to the containerized plane — zero hand-assembled wiring (default base + credential fallback), witnessed in a live run from an agent seat.who_is_onlineat capture time with the deployed plane revision recorded (the honest-surface improvements not yet deployed are named, not silently claimed).learn/agentos/RunningTheFleetCockpit.mddocuments the journey.Out of Scope
The operator-witnessed L1 beat (parent #17271 AC5 — the closing evidence lives with the parent) · app-side
rosterSourceModesemantics (intake evidence: the promotion/admission paths already render selected-source truth for populated registries, and the empty-registry case renders the labelled static roster with its retained cause — no masking to fix) · thedevFleetServerplaneBearerFile-indirection gap observed at intake (adjacent debt: the dev entry reads only the direct leaf; this journey's launcher materializes the file's value, so the journey is unblocked; the entry-level gap is recorded on the parent) · wake-push admission credentials (the deployment declares them; the journey renders the honest not-armed lane).Avoided Traps
devCockpitLivewrapper spawningdevCockpit): nested supervisors for zero behavioral gain — the flag branch reuses the entire probe/plan/spawn/supervise machinery./fleet/probeto report plane bindings was rejected as wire-surface growth for a journey that can simply refuse named.Related
Parent: #17271 · Epic: #14560 · the honest presence surface this consumes: #17248 / #17225 · ADR 0038 ·
ai/scripts/lifecycle/local-agent-os/README.md(the plane-start command).Live latest-open sweep: latest 20 open checked 2026-08-17T07:45Z, no equivalent; A2A herd-window sweep clean (active claims: #17239/#17272 Ada, #17271 mine).
Origin Session ID: 0c5a1cf3-093b-4e9d-a7ba-74137e4d4f23
Retrieval Hint:
query_raw_memories("cockpit:live one command live plane journey resolveLivePlaneConfig probePlaneIdentity buildFleetChildEnv")