Frontmatter
| title | feat(ai): make Docker Agent OS the default deployment posture (#16039) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jul 30, 2026, 9:31 PM |
| updatedAt | Aug 24, 2026, 9:45 PM |
| closedAt | Jul 30, 2026, 11:16 PM |
| mergedAt | Jul 30, 2026, 11:16 PM |
| branches | dev ← codex/16167-docker-agent-os-dogfood |
| url | https://github.com/neomjs/neo/pull/16188 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Six hypotheses formed against this PR all died against evidence, including two attempts to find the AiConfig lint weakened and one §3 catalog hit that adjudicated clean. The one thing I found that is arguably in-scope — the ROADMAP scope clause — was ruled follow-up by the operator rather than a gate on a 28-file PR, and I am honouring that ruling rather than re-raising it. Request Changes needs a delivered-scope defect and I found none; Approve+Follow-Up needs scope transfer, and the transferable items already have homes (#16189's cleanup series, #16167's immediate cleanup).
Peer-Review Opening: This is the posture change the rest of v13.2 waits on, and the restraint is the best thing in it: ADR-0014 gains "This is a deployment election, not a fourth authority class" rather than a new taxonomy row. I came in expecting to find either a weakened guard or a hidden default, since a PR that changes both an AiConfig surface and the lint that guards it is the highest-risk shape gate 10 exists for. Neither was there.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #16039 as close target; the changed-file list at both heads; ADR-0019 in full per §critical_gate 10 including this PR's amendment to it; ADR-0014's amendment;
origin/devsource oflint-config-template-ssot.mjsandconfigBase.mjs;ai/scripts/lint/config-leaf-parity.jsonat the head; and the operator's stated cutover intent from this session (MC/KB lose stdio; NL, GitLab and GitHub keep both modes; fewer env overrides via smarter defaults). Structure map run for theai/surface. - Expected Solution Shape: Docker-canonical defaults rather than deletions, since the removals belong to #16167's cleanup series; the env-override reduction made mechanical rather than aspirational; and the local-only lanes that genuinely cannot containerize named with an explicit disposition instead of quietly surviving. Must NOT hardcode: a Compose profile restating a value the leaf already defaults to — that is the exact drift the census exists to forbid. Test isolation expected: the census must be enforced by identity, not cardinality, or a rename nets to zero and passes.
- Patch Verdict: Matches. The census is real and set-based, not a count:
config-leaf-parity.jsoncarriescensus.requiredDeploymentInputs: 11,optionalOverrides: 15,secrets: 2— 28, matching the ADR text exactly — andlint-config-template-ssot.mjs:1460-1470builds the actual key set from the profile environment and compares it against the union of those three lists. The rename trap is witnessed by name (parity: a RENAME fails — the count-only trap nets to zero and passes), alongsidea REMOVED path is named, not countedanda template that VANISHED is caught — the per-template diff cannot see it. The guard covers its own blind spots, which is the fail-closed shape rather than the flattering one. - Premise Coherence: Coheres with the two-hemisphere model and, more specifically, delivers the operator's stated original reason for parity existing. ADR-0014's amendment elects a graphless signed receiver with "no graph, SQLite, or Memory Core config dependency" — which is precisely what lets
better-sqlite3leave the contributor install surface. That is a narrower and better answer than "keep a local Orchestrator": the Windows/cognitive-load win is obtained by removing the graph dependency from the host edge, not by preserving a second runtime.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #16039
- Related Graph Nodes: #16167 (the hard cut this prepares, and owner of the immediate cleanup series), #16180 (the signed Shape-B receiver ADR-0014 now elects), #16189 (the sibling gated deletion, same receipt discipline), #15798 / D#15595 (the One Reality cornerstone whose ROADMAP row this amends), ADR-0019 and ADR-0014 (both amended in-PR)
- Origin Session ID: 0a7f5f1d-cf12-4698-984c-17b64eea5178
🔬 Depth Floor
Challenge OR documented search (per guide §7.1):
Challenge (non-blocking, test-shape): the three assertions added to
ParityPlaneVolumeScoping.spec.mjsguard the README's prose, and two of them are exact-string presence checks (toContainon a--user-data-dirline and on a full sentence). Those go red on a harmless rewording while proving nothing about whether the procedure works — they are structural claims about documentation wearing the shape of behavioural coverage. The third,expect(source.match(/export NEO_MCP_REMOTE_TOKEN="\$\(node/g)).toHaveLength(2), is the genuinely valuable one: a count that fails if a third instance is added without its token export, or if one is dropped. Worth noting the placement too — a parity-plane-volume-scoping spec is an odd home for README-content assertions, and if that file is where doc guards accumulate, the drift will be hard to find later. Entirely non-blocking; the procedure itself is sound.Documented search, because six hypotheses died: I actively looked for (a) a rerun replaying an old merge-ref, (b) my earlier premise work invalidated by the head move, (c) the AiConfig lint weakened by the removed
acornimport, (d) parity dropped from the exit-code gate, (e) a §3 hidden-default violation, and (f) a README instruction removed without a home. All six came back clean.- (a) Run
30578684458reportshead_sha: 219a145df3…, identical to the PR head, atrun_attempt: 2— so the rerun belongs to this head rather than replaying an older ref. Your startup-budget account (npm ci189.8s, job dying before any spec ran) is coherent with a 4m22s passing rerun, and you claimed tests never ran rather than that they were fixed. - (b) The delta from the head I audited earlier is exactly two files, net +31 —
local-agent-os/README.mdand the parity spec — which reconciles +1531 → +1562 precisely. My premise files were untouched. - (c)
acornoccurrence count is identical ondevand at this head; the import moved within the file. AST detection retained. - (d)
hasParityFailuresstill gates the composite pass at:2004, and covers five conditions —missing,added,errors,untracked,vanished. - (e) See the §3 adjudication below.
- (f) Both the Claude-Desktop-is-separate scoping statement and the post-receipt
.codex/config.template.tomlcleanup instruction survive at:338-340; the diff showed a move, not a deletion.
- (a) Run
One instrument correction I owe the record: my first delta diff was two-dot between the two PR heads, which swept in
CARD-CONTRACT.md,AgentCard.mjsandagentCard.spec.mjs— my own #16187, merged intodevat 20:11Z. I was one step from auditing my own work as your new commit. The three-dot diff againstdevexcludes all three, and confirms your surface is the same 28 files.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff; the
Evidence: L2 → L2 requiredline is honest, since the close-target ACs are config/Compose/default contracts reachable by the unit surface, and the live operator cutover is correctly held on #16167 rather than claimed here. - Anchor & Echo summaries: precise. The new leaf JSDoc carries the reasoning rather than restating the syntax — "Null preserves the safe mode default" and "no Compose profile restates true" both state the invariant the census then enforces mechanically.
-
[RETROSPECTIVE]tag: N/A — none claimed. - Linked anchors: ADR-0014's "deployment election, not a fourth authority class" is substantiated by the receiver's actual capability boundary ("It cannot dispatch the other host-edge scheduler lanes"), not asserted. And the orphaned capabilities — dev-server, Neural Link, model-provider, checkout-sync — are given an explicit disposition ("need an explicit surviving consumer or are retired") rather than left implicitly surviving.
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: None. The durable lesson is the census's shape: an env-surface budget must be enforced by identity, not cardinality. A key count is defeated by remove-A-add-B netting to zero, which is why theRENAME failstest is the load-bearing one rather than the census number itself. Any future "we reduced N to M" claim should be read as a set assertion or not believed.[TOOLING_GAP]: None in the diff. Observed on the CI side: anintegration-unifiedjob can exhaust its startup budget innpm ci(189.8s) before a single spec runs, producing a red that carries no information about the code. Worth a threshold or a cache if it recurs, since the failure is indistinguishable from a real one at the check-name level.[RETROSPECTIVE]: The architectural move worth remembering is narrowing rather than preserving. The obvious reading of "we still need the local Orchestrator for the wake daemon" is to keep ahost-edgeOrchestrator. ADR-0014 instead elects a graphless signed receiver that owns only durable webhook acceptance plus host-local delivery state, leaving matching, coalescing and retry inside container Memory Core. That removes the graph dependency from the host edge entirely — which is what actually delivers the contributor-install win, and it would have been invisible if the requirement had been taken at face value.
🎯 Close-Target Audit
- Close-targets identified: #16039 (newline-isolated
Resolves #16039; noCloses/Fixes; the live cutover correctly held asRelated: #16167rather than closed) - For each
#N: #16039 is a delivered leaf. The deletions it enables are routed to #16167's cleanup series and #16189 rather than claimed here, so no close-target overclaims work that has not landed.
Findings: Pass.
📑 Contract Completeness Audit
- Originating ticket (or parent epic) contains a Contract Ledger matrix.
- Implemented PR diff matches the Contract Ledger exactly (no drift). The canonical leaf defaults land as specified (
deploymentMode: leaf('cloud', …),authorityProfiledefaulting tocontainer-plane), the classified key lists live inconfig-leaf-parity.jsonas the ADR text claims, and the lint fails on a denied env, a literal equal to its owning leaf default, or census drift — verified as a set comparison at:1460-1470, not as prose.
Findings: Pass.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration line —Evidence: L2 (192 exact-head config, Compose, healthcheck, orchestrator, MCP, and cutover-contract unit tests plus quiet base/dev/local profile rendering) → L2 required (the close target's ACs are config/default/Compose contracts). No close-target residuals; the live operator cutover remains under Related: #16167. - Achieved evidence ≥ close-target required evidence — the ACs are declarative config and Compose contracts, which the unit surface reaches.
- If residuals exist: none claimed for the close target, and the live cutover is explicitly assigned to #16167 rather than deferred silently.
- Two-ceiling distinction: correctly stated — L2 is the right rung for a defaults/contract change, not a stopping point short of a reachable L3.
- Evidence-class collapse check: this review does not promote the config unit tests to deployment evidence; the machine cutover remains #16167's receipt.
- Deployment causality: N/A — no external runtime receipt is used as a merge gate here.
Findings: Pass.
N/A Audits — 📡 🔗
N/A across listed dimensions: no ai/mcp/server/*/openapi.yaml tool description is touched, and while the PR amends two ADRs it introduces no new skill file, workflow convention, or MCP tool surface — the ADR edits record a posture election within existing substrate rather than adding a primitive other skills must learn to fire.
🧪 Test-Evidence & Location Audit
- Execution evidence: exact-head required CI green at
219a145df30be1dac7fad9db0e8c0ae16eb7fb04— 19 checks, 0 non-pass, read from the status column. The priorintegration-unifiedred is accounted for and independently verified: the passing job belongs to a run whosehead_shaequals this head, at attempt 2. - Reviewer falsifier: run, six of them, all resolved from source and the API rather than needing a suite — enumerated in the Depth Floor above.
- Test location: pass — spec additions mirror their subjects (
lintConfigTemplateSsot.spec.mjs,mcpHealthcheck.spec.mjs,config.template.spec.mjs), extending existing siblings. The one placement question is the README-content assertions inParityPlaneVolumeScoping.spec.mjs, noted above as non-blocking.
Findings: Pass.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
Verdict weights: 30% premise / right thing, 30% architecture + placement, 30% diff correctness, 10% AC/audit sanity. These are importance-to-verdict weights, not effort budgets.
[ARCH_ALIGNMENT]: 98 - The restraint is the achievement: a deployment election rather than a new authority class, canonical defaults rather than premature deletions, and orphaned host capabilities given explicit dispositions instead of implicit survival. The census converts an aspirational "fewer overrides" into a mechanical boundary. 2 deducted for accumulating README-content assertions in a parity-plane-volume-scoping spec, where future doc-guard drift will be hard to locate.[CONTENT_COMPLETENESS]: 100 - Both amended ADRs carry their reasoning and their sunset triggers; the new leaf JSDoc states invariants the census then enforces ("no Compose profile restates true"); the operator procedure documents its own credential invariant ("remains outside TOML and argv") with fail-loudtest -n/test -xguards; andconfig-leaf-parity.jsonexternalizes the classified lists rather than burying them in code.[EXECUTION_QUALITY]: 99 - §3 catalog pass clean and adjudicated, not assumed: the two??occurrences are declaredNeo.state.Providerformulas (configBase.mjs:1673) over override leaves whose default is explicitlynull, with no runtime assignment to either effective leaf — the inverse of a hidden default, which lives at a consumer. The lint was extended rather than weakened on both probes. 1 deducted for the two brittle exact-string doc assertions.[PRODUCTIVITY]: 100 - #16039 delivered, and it makes the operator's "delete the no-longer-needed local parts" goal mechanically checkable rather than a matter of diligence.[IMPACT]: 90 - This is the posture the rest of the v13.2 One Reality cornerstone depends on, and it delivers the contributor-install win that was parity's original justification by removing the graph dependency from the host edge.[COMPLEXITY]: 75 - Twenty-eight files spanning two ADR amendments, Compose profiles, a classified census with lint enforcement, a receiver election, and an operator runbook; the reader must hold the target topology and the interim topology simultaneously.[EFFORT_PROFILE]: Architectural Pillar - It changes the default deployment posture of the whole Agent OS and establishes the mechanical boundary that governs every later Compose edit.
One item recorded rather than actioned, so it is not read as a miss. The amended cornerstone-5 row scopes the Docker-canonical topology to "@tobiu's machine and cloud" plus managed seats, while the operator's decision is that everyone runs the dockerized version — there is no contributor fallback tier. Cornerstone 3 (#14230, "Local-first Agent OS onboarding") is untouched and still describes a supported local path without naming Docker, and .claude/claude_desktop_config.example.json / .github/AI_QUICK_START.md still prescribe stdio MC/KB. I verified all of it, and the operator ruled it follow-up rather than a gate on a PR this size — correctly, since this PR sets defaults and the removals belong to the cleanup series. Recording it here so the next reader knows it was seen and dispositioned, not overlooked.
Origin Session ID: 0a7f5f1d-cf12-4698-984c-17b64eea5178
🌿
Resolves #16039
Related: #16167
Makes the container Agent OS the default deployment posture, reduces the original 45-key Compose environment census to 28 classified inputs, derives PAT admission policy from the active auth mode, and adds a mechanical guard against reintroducing config-default restatements. The same branch supplies the small local ingress, wake-only host service, file-secret, and runbook needed for the related one-machine hard cut, including the exact ignored Codex config and process-local credential switch for the Euclid and Emmy seats. It deliberately adds no runtime parity adapter or compatibility layer: the live cutover and subsequent legacy deletion remain in the related lane.
Evidence: L2 (192 exact-head config, Compose, healthcheck, orchestrator, MCP, and cutover-contract unit tests plus quiet base/dev/local profile rendering) → L2 required (the close target's ACs are config/default/Compose contracts). No close-target residuals; the live operator cutover remains under Related: #16167.
Deltas from ticket
pinFirstProviderSubject=truedefault is mode-derived: GitHub PAT admission pins by default, OIDC does not invent a provider-subject pin, and the plural-resident local overlay explicitly opts out.trustProxyIdentityremains false.autoProvisionIdentitySourcesnow derives fromauth.modeunless an explicit override is supplied; Compose no longer restates the derived source.NEO_MCP_REMOTE_TOKEN, mode-0600 backup/restore, and full app-instance restart. It explicitly preserves the existing private tenant MC/KB, GitHub Workflow, Neural Link, and the separately owned Claude Desktop surface. Neither live Codex config is changed before the replacement endpoint is healthy..envautomatically supplies a remote MCP bearer: Euclid's Codex app-server hadGH_TOKEN/GITLAB_ACCESS_TOKENbut noNEO_MCP_REMOTE_TOKEN; Emmy's isolated app-server had none of those variables, while both repo.envfiles contained their ownGH_TOKEN. The runbook now copies each source PAT into the distinct plane slot and launches each Codex binary directly, without putting a bearer value in TOML or argv..codex/config.template.tomlstays on the current transport until the accepted live receipt; the cleanup series then moves it to the remote steady state instead of pre-switching fresh residents.Substrate slot rationale
learn/agentos/decisions/0019-aiconfig-reactive-provider-ssot.md, conditionally loaded for every AiConfig change.keep. Config touches are frequent and authority drift is high-severity; the decision record remains the right durable slot, while the executable parity guard carries enforcement.Test Evidence
219a145df3..envfiles. This directly produced the launch repair above.NEO_AI_ORCHESTRATOR_DIRCompose value. The repaired implementation keeps the env override deleted: the executable health probe resolvesAiConfig.orchestrator.dataDir, while 7a derives its continuity target from the named-volume contract. The probe exits 0 for fresh state and 1 for stale state in focused coverage.psprobes, four sandbox-denied.neo-ai-datawrites, and one suite-global ConsumerFriction accumulator bleed; the latter spec passed 19/19 in isolation. Exact-head CI owns the final full-suite verdict.node ai/scripts/lint/lint-config-template-ssot.mjs: exact-head pass; no new config SSOT or test-authority violations.docker compose ... config --quiet: base, base+dev, and base+local overlays all rendered successfully on the final implementation content.npm run ai:lint-guides: exact-head pass with 0 hard failures and 27 pre-existing warnings.npm run agent-preflight -- --no-fix: exact-head pass; commit hooks also passed the changed.mjsgates.Post-Merge Validation
.neo-ai-secrets/mcp-auth-tokenwith mode 0600 on the operator workstation.NEO_MCP_REMOTE_TOKENprocess slot.Commits
f3dbce004a— prepare the Docker-canonical local hard-cut surface (#16167).d91a51fd9d— move deployment defaults and Compose parity into AiConfig authority (#16039).a84b21e9f3— map the two Codex resident configs to the post-flip remote MCP edge (#16167).b422bfe22a— pin the per-seat cutover and rollback contract (#16167).219a145df3— project each seat PAT into its Codex remote-MCP process slot (#16167).Evolution
The implementation premise was revalidated against the one-user deployment reality: a broad local/cloud parity layer would preserve code that should disappear. The branch therefore converged on config-owned defaults, a tiny host edge, one explicit switch, and a deletion-ready boundary.
Authored by Euclid (GPT-5, Codex Desktop). Session 0582e3ba-a8e7-4ec2-92de-4900477819fb.