Skip to content

Commit 2025943

Browse files
authored
feat(ai): make Docker Agent OS the default deployment posture (#16039) (#16188)
* feat(ai): prepare Docker-canonical local cutover (#16167) * refactor(ai): make deployment defaults config-owned (#16039) * docs(ai): map resident MCP endpoint flip (#16167) * test(ai): pin resident MCP cutover contract (#16167) * fix(ai): pass seat tokens into Codex launch (#16167)
1 parent d21f273 commit 2025943

28 files changed

Lines changed: 1562 additions & 251 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ resources/data/reports/
105105
.env
106106
.neo-ai-data
107107
!.neo-ai-data/concepts/
108+
.neo-ai-secrets
108109
# Parity-plane runtime root (docker-compose.dev.yml relocated plane) — runtime data, never tracked
109110
.neo-ai-data-parity
110111
ai/mcp/server/*/config.mjs

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This section names the gate and the load-bearing path, not a frozen checklist. [
2222
| **Qt-parity docking + stunning demos** — NL-driven, animated, e2e-tested | [#13158](https://github.com/neomjs/neo/issues/13158) (+ the #14587/#14589/#14590/#14591 batch) | ≥2 public demos with deterministic tour modes; the pillar-1×2 fusion demo (cockpit → docked panel → OS window → share) as the flagship |
2323
| **Golden Path v2 — the floor** — never empty, direction-attributed, honest states | [#14472](https://github.com/neomjs/neo/issues/14472) · [#14565](https://github.com/neomjs/neo/issues/14565) (subs #14566#14568) · [#14581](https://github.com/neomjs/neo/issues/14581) | zero-route regression fixed with fixtures (#14588 class); `INTENT_STARVED` renders; the `not-code-ready` backlog re-triaged to honest states |
2424
| **Local-first Agent OS onboarding** | [#14230](https://github.com/neomjs/neo/issues/14230) | the supported local setup path reaches a running Agent OS / Fleet Manager surface without hand-edited config; fork → running Agent OS → claimed lane → PR is runnable locally |
25-
| **One Reality — local/cloud Agent OS runtime parity** | [#15798](https://github.com/neomjs/neo/issues/15798) · [D#15595](https://github.com/orgs/neomjs/discussions/15595) · [#15490](https://github.com/neomjs/neo/issues/15490) | the epic's native DAG reaches its declared v13.2 parity-v1 first-self-use gate; Docker-based local/cloud topology parity is proven while stdio remains the contributor fallback |
25+
| **One Reality — Docker-canonical local/cloud Agent OS** | [#15798](https://github.com/neomjs/neo/issues/15798) · [D#15595](https://github.com/orgs/neomjs/discussions/15595) · [#15490](https://github.com/neomjs/neo/issues/15490) | @tobiu's machine and cloud run one Docker-canonical Agent OS topology; managed seats consume Memory Core and Knowledge Base through the same authenticated HTTP ingress; only irreducibly host-bound wake/session, Neural Link, and repository-workflow effects remain local; native MC/KB stdio, parallel-plane rehearsal, and cutover compatibility paths are removed after the switch |
2626
| **Brain coherence + runtime discipline** | [#12456](https://github.com/neomjs/neo/issues/12456) (AiConfig cleanup — grind class) · [#14442](https://github.com/neomjs/neo/issues/14442) discipline over A+B's demos | AiConfig cleanup lands without new config drift; demo work proves real product behavior; the CEO-dashboard slice stays gated on [#14472](https://github.com/neomjs/neo/issues/14472) |
2727

2828
**How it runs.** Sequencing honors the coherence-first ordering inside the milestone: the Brain-coherence floor (GP-floor + self-config) and the Local Runtime Parity spine land early; the cockpit PoC and demo polish land on top of the runtime we will actually deploy. Each cornerstone has a self-selected steward driving its outcome — [#15798](https://github.com/neomjs/neo/issues/15798) is stewarded by @neo-fable, and cornerstone 6's business epic [#14442](https://github.com/neomjs/neo/issues/14442) is Grace's on the record. Any peer claims subs. **The AiConfig-cleanup grind ([#12456](https://github.com/neomjs/neo/issues/12456)) is DONE** — closed 2026-07-16 with all ten subs landed, so it is no longer the unclaimed anchor this section named; ADR 0019 plus its lint now hold that floor, and new `ai/` config work consults the ADR rather than this epic. The identity render rules for every cockpit surface are governed by ADR 0032 ([#14445](https://github.com/neomjs/neo/issues/14445) — CLOSED; the ADR is authoritative now, not a pending gate). Cadence: parity is part of the release gate, not an overflow candidate; optional polish yields before One Reality does, and the deferred set holds firm.

ai/configBase.mjs

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -405,14 +405,19 @@ class ConfigBase extends ConfigProvider {
405405
// Optional username allowlist for PAT modes ('gitlab-pat' / 'github-pat'). Empty means any resolved user.
406406
allowedUsers : leaf([], 'NEO_AUTH_ALLOWED_USERS', 'csv'),
407407
/**
408-
* @summary Pins a rosterless local GitHub-PAT process to the provider subject
409-
* resolved from the configured bootstrap carrier before the HTTP listener opens.
408+
* @summary Optional deployment override for the auth-mode-derived provider pin.
410409
*
411-
* This is an explicit admission policy, never an alternate meaning for an empty
412-
* `allowedUsers`. Generic PAT profiles remain unchanged while this is `false`.
410+
* Null preserves the safe mode default: GitHub-PAT pins the bootstrap provider
411+
* subject, while auth modes without a provider bootstrap do not. A plural-resident
412+
* GitHub-PAT plane explicitly sets this false; no Compose profile restates true.
413+
* @type {Boolean|null}
414+
*/
415+
pinFirstProviderSubjectOverride: leaf(null, 'NEO_AUTH_PIN_FIRST_PROVIDER_SUBJECT', 'boolean'),
416+
/**
417+
* @summary Effective first-provider-subject policy derived from `auth.mode`.
413418
* @type {Boolean}
414419
*/
415-
pinFirstProviderSubject: leaf(false, 'NEO_AUTH_PIN_FIRST_PROVIDER_SUBJECT', 'boolean'),
420+
pinFirstProviderSubject: leaf(false),
416421
/**
417422
* @summary Bootstrap PAT used only when `pinFirstProviderSubject` is enabled.
418423
*
@@ -433,13 +438,20 @@ class ConfigBase extends ConfigProvider {
433438
* @type {String}
434439
*/
435440
providerBootstrapPatFile: leaf('', 'NEO_AUTH_PROVIDER_BOOTSTRAP_PAT_FILE', 'string'),
436-
// Auth provenance sources that may create missing AgentIdentity graph nodes at request time.
437-
// 'github-pat' is deliberately NOT in the default: github.com is a public identity
438-
// surface, so auto-provisioning must be opt-in for deployments that scope their caller
439-
// set (allowedUsers, GHES, private network). Auth success without a bound AgentIdentity
440-
// leaves graph-gated tools fail-closed rather than broken — authentication does not
441-
// imply Agent OS admission; the exclusion keeps admission explicit instead of ambient.
442-
autoProvisionIdentitySources: leaf(['gitlab-pat'], 'NEO_AUTH_AUTO_PROVISION_IDENTITY_SOURCES', 'csv')
441+
/**
442+
* @summary Optional deployment override for auth-mode-derived identity provisioning.
443+
*
444+
* Null derives the one provider provenance from `auth.mode`; an explicit empty CSV
445+
* disables provisioning. This keeps selector and derived source in one config
446+
* authority instead of requiring every deployment to repeat the obvious mapping.
447+
* @type {String[]|null}
448+
*/
449+
autoProvisionIdentitySourcesOverride: leaf(null, 'NEO_AUTH_AUTO_PROVISION_IDENTITY_SOURCES', 'csv'),
450+
/**
451+
* @summary Effective request-time identity-provisioning provenance.
452+
* @type {String[]}
453+
*/
454+
autoProvisionIdentitySources: leaf([])
443455
},
444456
/**
445457
* @summary Deployment-wide chat / generation model provider.
@@ -800,23 +812,22 @@ class ConfigBase extends ConfigProvider {
800812
dbPath: leaf(path.resolve(planeDataRootDefault, 'sqlite/memory-core-graph.sqlite'), 'NEO_AI_DB_PATH', 'string', {planeMember: true}),
801813
/**
802814
* Deployment profile for Agent OS maintenance ownership.
803-
* `local` preserves maintainer-checkout behavior; `cloud` disables local-only
804-
* maintenance lanes unless a narrower localOnly override opts them back in.
815+
* `cloud` is the canonical Agent OS posture and disables local-only maintenance
816+
* lanes unless a host process explicitly opts into `local`.
805817
* @type {'local'|'cloud'}
806818
*/
807-
deploymentMode: leaf('local', 'NEO_AI_DEPLOYMENT_MODE', 'string'),
819+
deploymentMode: leaf('cloud', 'NEO_AI_DEPLOYMENT_MODE', 'string'),
808820
/**
809821
* Orchestrator task-authority role. This is intentionally independent from
810822
* `deploymentMode`: storage/deployment defaults cannot express which process owns
811823
* host/session effects versus plane maintenance on the same machine.
812824
*
813-
* `legacy-mixed` preserves existing maintainer checkouts until an explicit
814-
* machine cutover. Container profiles declare `container-plane`; the
815-
* eventual host-edge overlay declares `host-edge`. Unknown values fail the
816-
* orchestrator's preflight ownership audit.
825+
* `container-plane` is the canonical default. A machine-local Orchestrator
826+
* explicitly declares `host-edge`; `legacy-mixed` exists only for rollback to a
827+
* pre-cutover revision. Unknown values fail the preflight ownership audit.
817828
* @type {'legacy-mixed'|'host-edge'|'container-plane'}
818829
*/
819-
authorityProfile: leaf('legacy-mixed', 'NEO_AI_ORCHESTRATOR_AUTHORITY_PROFILE', 'string'),
830+
authorityProfile: leaf('container-plane', 'NEO_AI_ORCHESTRATOR_AUTHORITY_PROFILE', 'string'),
820831
/**
821832
* Filesystem root under which tenant-repo mirrors are stored. The
822833
* `deriveTenantRepoMirrorPath` helper appends `tenant-repos/<tenant>/<repo>`,
@@ -1438,14 +1449,14 @@ class ConfigBase extends ConfigProvider {
14381449
* - `enabled`: whether the orchestrator may supervise `ollama serve` for local-dev
14391450
* roles explicitly routed through the native `ollama` provider. The task is
14401451
* still omitted when no configured chat / embedding role targets `ollama`, so
1441-
* this default does not start Ollama for the standard OpenAI-compatible setup.
1452+
* the operator must explicitly opt a host edge into supervising Ollama.
14421453
* When active, `OLLAMA_HOST`, `OLLAMA_KEEP_ALIVE`, `OLLAMA_CONTEXT_LENGTH`, and
14431454
* `OLLAMA_MAX_LOADED_MODELS` are derived from the canonical provider and
14441455
* local-model config leaves.
14451456
* @type {Object}
14461457
*/
14471458
ollama: {
1448-
enabled: leaf(true, 'NEO_ORCHESTRATOR_OLLAMA_ENABLED', 'boolean')
1459+
enabled: leaf(false, 'NEO_ORCHESTRATOR_OLLAMA_ENABLED', 'boolean')
14491460
},
14501461
/**
14511462
* Orchestrator-owned LM Studio CLI (`lms`) inference server config. Operators
@@ -1456,8 +1467,8 @@ class ConfigBase extends ConfigProvider {
14561467
* for local chat + embedding workloads; pick at most one via the respective `enabled` flag.
14571468
*
14581469
* - `enabled`: whether the orchestrator should supervise an `lms server start`
1459-
* child process. Enabled by default for local Agent OS chat + embedding roles;
1460-
* **macOS-only** (LM Studio CLI is not
1470+
* child process. Disabled by default: provider processes are deployment choices,
1471+
* not implicit Orchestrator children. The launcher remains **macOS-only** (LM Studio CLI is not
14611472
* shipped for Linux containers, so this lane is local-dev substrate, not
14621473
* cloud-deployment substrate).
14631474
* - `model`: legacy single-model field kept for existing operator overlays. The
@@ -1469,7 +1480,7 @@ class ConfigBase extends ConfigProvider {
14691480
* @type {Object}
14701481
*/
14711482
lms: {
1472-
enabled: leaf(true, 'NEO_ORCHESTRATOR_LMS_ENABLED', 'boolean'),
1483+
enabled: leaf(false, 'NEO_ORCHESTRATOR_LMS_ENABLED', 'boolean'),
14731484
model : leaf('qwen3-embedding-8b', 'NEO_ORCHESTRATOR_LMS_MODEL', 'string'),
14741485
port : leaf('1234', 'NEO_ORCHESTRATOR_LMS_PORT', 'string')
14751486
}
@@ -1660,6 +1671,10 @@ class ConfigBase extends ConfigProvider {
16601671
* Reactive computed config values (`Neo.state.Provider` formulas).
16611672
*/
16621673
formulas: {
1674+
'auth.pinFirstProviderSubject': data => data.auth.pinFirstProviderSubjectOverride ??
1675+
data.auth.mode === 'github-pat',
1676+
'auth.autoProvisionIdentitySources': data => data.auth.autoProvisionIdentitySourcesOverride ??
1677+
(['github-pat', 'gitlab-pat'].includes(data.auth.mode) ? [data.auth.mode] : []),
16631678
'engines.chroma.useTestDatabase': data => data.engines.chroma.useUnitTestDatabase || data.engines.chroma.useTestHarness,
16641679
'engines.chroma.dataDir' : data => data.engines.chroma.useTestDatabase ? data.engines.chroma.dataDirTest : data.engines.chroma.dataDirProd,
16651680
'engines.chroma.host' : data => data.engines.chroma.useTestDatabase ? data.engines.chroma.hostTest : data.engines.chroma.hostProd,

ai/deploy/Caddyfile.local-agent-os

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
:8080 {
2+
route {
3+
request_header -X-Preferred-Username
4+
request_header -X-Auth-Request-Preferred-Username
5+
6+
handle_path /mc/* {
7+
reverse_proxy mc-server:3001
8+
}
9+
10+
handle_path /kb/* {
11+
reverse_proxy kb-server:3000
12+
}
13+
}
14+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<!--
4+
Per-user macOS LaunchAgent template for the final signed, graphless host wake
5+
receiver (#16167 / #16180). The cutover runbook materializes the placeholders
6+
outside the repository before launchctl bootstrap. It never starts a host
7+
Orchestrator or the legacy Shape-C GraphLog daemon.
8+
-->
9+
<plist version="1.0">
10+
<dict>
11+
<key>Label</key>
12+
<string>com.neomjs.agent-os-wake</string>
13+
14+
<key>ProgramArguments</key>
15+
<array>
16+
<string>__NODE_BIN__</string>
17+
<string>ai/daemons/wake/receiver.mjs</string>
18+
<string>--manifest</string>
19+
<string>__WAKE_RECEIVER_MANIFEST__</string>
20+
<string>--state-dir</string>
21+
<string>__WAKE_RECEIVER_STATE_DIR__</string>
22+
<string>--host</string>
23+
<string>__WAKE_RECEIVER_HOST__</string>
24+
<string>--port</string>
25+
<string>__WAKE_RECEIVER_PORT__</string>
26+
</array>
27+
28+
<key>WorkingDirectory</key>
29+
<string>__NEO_REPO_ROOT__</string>
30+
31+
<key>EnvironmentVariables</key>
32+
<dict>
33+
<key>PATH</key>
34+
<string>__NEO_PATH__</string>
35+
</dict>
36+
37+
<key>RunAtLoad</key>
38+
<true/>
39+
40+
<key>KeepAlive</key>
41+
<true/>
42+
43+
<key>ThrottleInterval</key>
44+
<integer>10</integer>
45+
46+
<key>StandardOutPath</key>
47+
<string>__NEO_REPO_ROOT__/.neo-ai-data/wake-receiver/launchd.out.log</string>
48+
49+
<key>StandardErrorPath</key>
50+
<string>__NEO_REPO_ROOT__/.neo-ai-data/wake-receiver/launchd.err.log</string>
51+
52+
<key>ProcessType</key>
53+
<string>Background</string>
54+
</dict>
55+
</plist>

ai/deploy/docker-compose.dev.yml

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,6 @@ x-plane-env: &plane-env
101101
# renders the credential.
102102
x-provider-auth-env: &provider-auth-env
103103
NEO_AUTH_MODE: github-pat
104-
NEO_AUTH_TRUST_PROXY_IDENTITY: "false"
105-
NEO_AUTH_PIN_FIRST_PROVIDER_SUBJECT: "true"
106-
NEO_AUTH_AUTO_PROVISION_IDENTITY_SOURCES: github-pat
107104
NEO_AUTH_PROVIDER_BOOTSTRAP_PAT_FILE: &provider-bootstrap-pat-file /run/secrets/mcp-auth-token
108105
NEO_MCP_HEALTHCHECK_TOKEN_FILE: *provider-bootstrap-pat-file
109106

@@ -167,11 +164,7 @@ services:
167164
environment:
168165
<<: [*plane-env, *provider-auth-env]
169166
NEO_TRANSPORT: streamable-http
170-
NEO_AUTO_SYNC: "false"
171-
NEO_KB_AUTO_START_DATABASE: "false"
172167
NEO_CHROMA_HOST: chroma
173-
NEO_CHROMA_PORT: "8000"
174-
MCP_HTTP_PORT: "3000"
175168
# Ask-synthesis pass-throughs (ask_knowledge_base -> SearchService.ask): the synthesis
176169
# provider + its DEDICATED budget-cappable key (NEO_KB_ASK_API_KEY, never the shared
177170
# GEMINI_API_KEY). Empty default -> the config-template default applies; with no key the
@@ -236,17 +229,7 @@ services:
236229
environment:
237230
<<: [*plane-env, *provider-auth-env]
238231
NEO_TRANSPORT: streamable-http
239-
NEO_MC_PRIMARY: "false"
240-
NEO_AUTO_SUMMARIZE: "false"
241-
NEO_MEM_AUTO_START_DATABASE: "false"
242-
NEO_MEM_AUTO_START_INFERENCE: "false"
243-
NEO_AUTO_DREAM: "false"
244-
NEO_AUTO_GOLDEN_PATH: "false"
245-
NEO_REAL_TIME_MEMORY_PARSING: "false"
246-
NEO_AUTO_INGEST_FS: "false"
247232
NEO_CHROMA_HOST: chroma
248-
NEO_CHROMA_PORT: "8000"
249-
MCP_HTTP_PORT: "3001"
250233
# Single-process container hosts the WAL drain loop itself: without it the decoupled
251234
# add_memory writes the WAL but nothing embeds it, so semantic recall reads empty.
252235
# Sole-drainer invariant: exactly one loop per WAL dir; never enable alongside the embed
@@ -313,30 +296,15 @@ services:
313296
SERVICE_ENTRYPOINT: ai/daemons/orchestrator/daemon.mjs
314297
environment:
315298
<<: *plane-env
316-
# Cloud-safe lane profile: only the deployable lanes run. Local-only lanes and local
317-
# inference launchers stay off — the parity stack proves plane topology, not model hosting.
318-
NEO_AI_DEPLOYMENT_MODE: cloud
319-
NEO_AI_ORCHESTRATOR_AUTHORITY_PROFILE: container-plane
320-
NEO_ORCHESTRATOR_PRIMARY_DEV_SYNC_ENABLED: "false"
321-
NEO_ORCHESTRATOR_CHROMA_DAEMON_ENABLED: "false"
322-
NEO_ORCHESTRATOR_KB_SYNC_ENABLED: "false"
323-
NEO_ORCHESTRATOR_BRIDGE_DAEMON_ENABLED: "false"
324-
NEO_ORCHESTRATOR_GOLDEN_PATH_REPO_ENRICHMENT_ENABLED: "false"
325-
NEO_ORCHESTRATOR_MLX_ENABLED: "false"
326-
NEO_ORCHESTRATOR_LMS_ENABLED: "false"
327-
NEO_ORCHESTRATOR_OLLAMA_ENABLED: "false"
299+
# AiConfig owns the cloud/container-plane posture and disabled local/model lanes.
300+
# This profile declares only its relocated Chroma placement and Docker capability.
328301
NEO_CHROMA_HOST: chroma
329-
NEO_CHROMA_PORT: "8000"
330302
# Runtime access is scoped to THIS plane's compose project — the same scalar the project
331303
# `name` and `NEO_PLANE_ID` resolve from, so a parity orchestrator can never address the
332304
# native stack's containers.
333305
NEO_ORCHESTRATOR_RUNTIME_ACCESS_ENABLED: "true"
334-
NEO_ORCHESTRATOR_RUNTIME_ACCESS_MECHANISM: docker-socket
335-
NEO_ORCHESTRATOR_RUNTIME_ACCESS_SOCKET_PATH: /var/run/docker.sock
336306
NEO_ORCHESTRATOR_RUNTIME_ACCESS_COMPOSE_PROJECT: *plane-id
337307
NEO_ORCHESTRATOR_RUNTIME_ACCESS_ALLOWED_SERVICES: chroma,kb-server,mc-server
338-
NEO_ORCHESTRATOR_RUNTIME_ACCESS_READ_OPERATIONS: inspect,logs,stats
339-
NEO_ORCHESTRATOR_RUNTIME_ACCESS_LIFECYCLE_OPERATIONS: restart
340308
volumes:
341309
- ../..:/app
342310
# The PLANE ROOT rides a project-scoped named volume, not the repo bind above. Docker applies

0 commit comments

Comments
 (0)