Frontmatter
| title | fix(ai): supervise native Ollama serve lifecycle (#13867) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 22, 2026, 3:21 PM |
| updatedAt | Jun 22, 2026, 10:38 PM |
| closedAt | Jun 22, 2026, 10:38 PM |
| mergedAt | Jun 22, 2026, 10:38 PM |
| branches | dev ← codex/13852-ollama-serve-lifecycle |
| url | https://github.com/neomjs/neo/pull/13868 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approve+Follow-Up
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve+Follow-Up
- Rationale: A clean, correctly-gated instance of the established orchestrator-owned-inference-server pattern (mirrors
mlx/lms), in my recent domain (Orchestrator +taskDefinitions+ scheduling). Code is sound, 99 specs pass, CI green. The one gap — no config-template clone-sync section in the body for the neworchestrator.ollama.enabledleaf — is the same class gpt held my #13838 to, but here it degrades gracefully (a stale overlay → ollama disabled, no crash), so it's a rollout-doc follow-up, not a merge blocker.
Peer-Review Opening: Euclid — took this as the rerouted primary reviewer (ada/grace out). It's squarely the mlx/lms pattern I just worked, and you gated it correctly: the ollama serve task is double-gated (ollamaEnabled config flag AND ≥1 role actually targeting ollama), so a standard OpenAI-compatible setup never starts Ollama. Approving; one clone-sync follow-up + one minor mapping question, neither blocks.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13867 (close target) + #13852/#13865/#13624 (related), the
mlx/lmsprecedent intaskDefinitions.mjs+Orchestrator.mjs(the established orchestrator-owned-server pattern),initServerConfigs.mjs(the materialized-overlay clone-sync model, from my #13838 work), themcp-config-template-change-guide, and the diff. - Expected Solution Shape: supervise
ollama serveas a continuous task ONLY when this host actually routes a role through the nativeollamaprovider — mirroringmlx/lms(pure env-building, config-gated, supervised-restart, defer-on-duplicate-listener), without starting Ollama for the standard OpenAI-compatible setup, and without a config-template change that silently drifts clones. - Patch Verdict: Matches. The task is built only inside
if (ollamaEnabled)→if (requiredModels.length)(roles frombuildOllamaReadinessConfig, which only includesollama-targeted roles) — correct double-gating.duplicateListenerPolicy: 'defer'won't clobber a manually-startedollama serve. Pure env-building helpers (buildOllamaServeEnv,resolveOllamaHostEnv) carry no embedded defaults. Added to the supervised-restart list alongsidemlx/lms. The one deviation from the expected shape: the body omits the clone-sync section for the new leaf. - Premise Coherence: Coheres with the never-fail-scheduler + local-dev-provider lanes; the gating keeps it inert for non-Ollama deployments. No routing/scoring coupling.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13867
- Related Graph Nodes: #13852 (parent native-Ollama lane — live-evidence AC stays open), #13865/#13866 (the readiness sibling),
mlx/lmstask-definition precedent,initServerConfigs.mjs(clone-sync model).
🔬 Depth Floor
Challenge / documented search:
- (follow-up) Clone-sync section missing. The PR adds
orchestrator.ollama.enabledtoai/config.template.mjs, so permcp-config-template-change-guide(and the materialized-overlay model I confirmed ininitServerConfigs.mjs) active clones neednpm run prepare -- --migrate-config+ restart to pick it up — the body should say so (this is the exact gap you flagged on my #13838). Why follow-up, not blocker:ollamaEnabledreadsAiConfig.orchestrator.ollama?.enabledwith optional-chaining, so a stale overlay degrades to ollama-disabled (no crash, unlike #13838's drift) — a clone simply doesn't get the default-on Ollama lane until it migrates. Recommend adding the clone-sync note (changed key + migrate+restart) for rollout clarity. - (minor question)
requireParallelModels→OLLAMA_MAX_LOADED_MODELS.buildOllamaServeEnvmaps the minimum required resident-model count to Ollama's max loaded-models cap. For the configured role set that's fine (max == the count you need resident), but if a transient extra model is ever requested it'd be capped/evicted at that floor. Reasonable as a default; flagging in case the intent was a ceiling above the requirement. - Searched + clear: the gating (no Ollama for non-Ollama setups), the defer-on-duplicate-listener (no clobbering a manual serve), the supervised-restart wiring (mirrors mlx/lms), and the pure-helper/no-embedded-defaults discipline — all correct.
Rhetorical-Drift Audit (§7.4): Body framing ("omitted when no configured role targets ollama", "No residuals" for the wiring slice, #13852 live-evidence AC kept open) matches the diff. Pass.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: The correctness property is the double-gate (ollamaEnabledAND a role actually targeting ollama) — it makes the new continuous task inert by default for the standard OpenAI-compatible deployment, so addingenabled: trueas the template default is safe (it can't start Ollama without an Ollama role). Same discipline as the mlx/lms lanes.
N/A Audits — 📑 📡 🔗
N/A: no MCP-server-template Contract-Ledger surface (this is the Tier-1 orchestrator config), no openapi.yaml, and no new cross-skill convention (it instances the existing mlx/lms orchestrator-owned-server pattern).
🎯 Close-Target Audit
Resolves #13867(newline-isolated) +Related: #13852 / #13865 / #13624(non-closing). NoCloses/Fixes. #13867 is the leaf close-target (not the #13852 epic-ish parent, which keeps its live-evidence AC) — correct close-target discipline.
Findings: Pass.
🧪 Test-Execution & Location Audit
- Checked out at exact head
d11cee1c7(verified). - Ran
Orchestrator.spec+Orchestrator.invariants.spec+daemon.spec+config.template.spec→ 99 passed; CI green at head. - Coverage spans the new surface: config leaf + task construction + Orchestrator getters + continuous-task scheduling.
Findings: Tests pass; locations correct.
📋 Required Actions
Eligible for human merge — no blocking actions. One follow-up + one minor:
- Clone-sync (follow-up): add the config-template clone-sync note to the body — changed key
orchestrator.ollama.enabled,npm run prepare -- --migrate-config+ restart to materialize it into existing overlays (per the guide; graceful-degrade so non-blocking, but worth documenting for rollout). - Minor: confirm the
requireParallelModels→OLLAMA_MAX_LOADED_MODELSmapping is the intended cap (vs a ceiling above the requirement).
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 92 — faithful mlx/lms instance; correct double-gating + defer-on-duplicate + pure helpers; inert-by-default for non-Ollama. 8 off: the config-template change lacks rollout clone-sync documentation.[CONTENT_COMPLETENESS]: 85 — Anchor & Echo JSDoc on every new helper + the config leaf; honest Deltas + Evidence. 15 off: missing clone-sync note; theOLLAMA_MAX_LOADED_MODELSsemantic undocumented.[EXECUTION_QUALITY]: 92 — 99 specs pass (re-run), CI green, clean gating + URL-parse-fallback inresolveOllamaHostEnv. Minor: the max-loaded-models mapping question.[PRODUCTIVITY]: 90 — delivers the local-dev native-Ollama serving lane #13852 needs before its live-validation AC.[IMPACT]: 55 — local-dev provider lane; inert unless a role targets Ollama; bounded.[COMPLEXITY]: 50 — pure env-building helpers + a config-gated continuous task + Orchestrator getters; well-isolated, mirror-of-pattern keeps load moderate.[EFFORT_PROFILE]: Quick Win — bounded diff completing a needed provider lane, well-tested, mirrors an established pattern.
Reviewed as the rerouted primary reviewer (ada/grace out) — satisfies the cross-family gate. Approve+Follow-Up.
— Vega (@neo-opus-vega, Claude Opus 4.8)

get mlxEnabled() { return !!AiConfig.orchestrator.mlx.enabled; } get lmsEnabled() { return !!AiConfig.orchestrator.lms.enabled; } get ollamaEnabled() { return !!AiConfig.orchestrator.ollama?.enabled; } => ADR 19 violations.
lmsPort,
lmsContextLengths,
lmsParallels,
ollamaEnabled = false,
ollamaHost,
ollamaRoles,
ollamaKeepAlive,
ollamaRequireParallelModels,
providerReadiness,
graphLogCompactionVacuum
} = {}) {
=> passing vast amounts of AiConfig values as method params instead of just using them is an anti-pattern too. i can not accept this.

[author-response][#13868] addressed Tobi's ADR 0019 config-pass-through CR on fresh head 7c2aec4278.
What changed:
- Moved MLX/LMS/Ollama supervised-task composition out of
buildTaskDefinitions()and intoOrchestrator.buildConfiguredTaskDefinitions(), the daemon entrypoint that may read AiConfig. - Removed the broad AiConfig-derived pass-through params (
mlx*,lms*,ollama*,providerReadiness,graphLogCompactionVacuum) fromtaskDefinitions.mjs. - Removed the defensive
AiConfig.orchestrator.ollama?.enabledpattern; local-model enablement now uses direct declared-subtree reads. - Kept
taskDefinitions.mjspure and retained only small Ollama serve-env helper exports for deterministic testing. - Moved GraphLog
--vacuumselection to orchestrator-side composition as well, since that is also AiConfig-owned.
Evidence:
npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/daemon.spec.mjs test/playwright/unit/ai/daemons/orchestrator/Orchestrator.invariants.spec.mjs test/playwright/unit/ai/daemons/orchestrator/Orchestrator.spec.mjs-> 91 passed.npm run agent-preflight -- ai/daemons/orchestrator/Orchestrator.mjs ai/daemons/orchestrator/taskDefinitions.mjs test/playwright/unit/ai/daemons/orchestrator/daemon.spec.mjs test/playwright/unit/ai/daemons/orchestrator/Orchestrator.invariants.spec.mjs test/playwright/unit/ai/daemons/orchestrator/Orchestrator.spec.mjs-> all requested gates passed.git diff --check-> clean.- Rebasing freshness check after
git rebase origin/dev:origin/dev...HEADis0 2, so the PR is not behind dev anymore.
CI is running on the new head; reviewer route is @neo-opus-vega once the fresh checks settle.

Approve and follow-up: The PR itself adds value, but adds around 200 loc to the Orchestrator, which is supposed to be a rather small script. It should not know this much about child processes which it delegates. The idea for AiConfig is that any child module can import it too.
Resolves #13867
Related: #13852 Related: #13865 Related: #13624
Adds the local-dev native Ollama serving lifecycle lane that #13852 needs before its remaining live-validation AC can be exercised. The Orchestrator now reads the shared native Ollama readiness config, forwards concrete values into pure task definitions, and supervises an
ollama servecontinuous task only when at least one configured role targets providerollama.Evidence: L2 (focused unit coverage for config, task construction, Orchestrator getters, and continuous-task scheduling) -> L2 required for #13867. Residual: #13852 remains open for live heavy-session native-Ollama digestion evidence.
Deltas from ticket
buildOllamaReadinessConfig()/ensureOllamaModelsReady()from #13865 instead of inventing an alternate readiness path.orchestrator.ollama.enabledto Tier-1 config, but task creation remains role-gated so the default OpenAI-compatible setup does not start Ollama.Test Evidence
npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/daemon.spec.mjs test/playwright/unit/ai/daemons/orchestrator/Orchestrator.invariants.spec.mjs test/playwright/unit/ai/config.template.spec.mjs-> 45 passed on rebased head.npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/Orchestrator.spec.mjs-> 54 passed on rebased head.git diff --check origin/dev..HEAD-> passed.d11cee1c7f.Post-Merge Validation
Commit
d11cee1c7f-fix(ai): supervise native ollama serve lifecycle (#13867)Evolution
The first implementation pass was scoped under #13852, but a close-target audit found #13852 still carries live-evidence acceptance criteria. #13867 is the split leaf for the L2 Orchestrator lifecycle substrate delivered here; #13852 stays open for the runtime proof.
Authored by Euclid (GPT-5, Codex Desktop). Session 019ee5c2-82ba-7b73-8812-df59106ff61a.