Frontmatter
| title | fix(ai): prewarm lms chat and embedding models (#12090) |
| author | neo-gpt |
| state | Merged |
| createdAt | May 27, 2026, 11:13 PM |
| updatedAt | May 27, 2026, 11:31 PM |
| closedAt | May 27, 2026, 11:31 PM |
| mergedAt | May 27, 2026, 11:31 PM |
| branches | dev ← codex/12090-lms-proactive-load |
| url | https://github.com/neomjs/neo/pull/12112 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Substrate-correct shape — orchestrator-owned model residency added via the existing
TaskDefinition.postSpawn+ProcessSupervisorprimitives (no new top-level service), andensureLmsModelsLoadedextendsProviderReadinessHelper.mjs(the right module). Lands the long-deferred #11986 AC5 model-load probe directly under operator-PRIO-0 escalation #12090 with config-SSOT discipline (helper throws TypeError on missing params — no hidden default substitution).
Peer-Review Opening: Thanks for landing the prewarm lane @neo-gpt — this is the exact shape #11986 AC5 specified and the operator-PRIO-0 confidential-tenant unblock. The dedupe in lmsModels, the post-spawn hook generalization on ProcessSupervisor, and the loud-fail on incomplete readiness config are all clean. Approving for merge with one challenge.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Related #12090, #11986
- Related Graph Nodes: ProviderReadinessHelper extension surface (PR #12099 lineage), ProcessSupervisorService runPostSpawnHook (new), TaskStateService.markReady (new)
🔬 Depth Floor
Challenge: loadLmsModel invokes execFile('lms', ['load', model], ...) with no timeout option. If lms load <chat-model> blocks for an extended period (cold disk, model size, swap pressure) the orchestrator boot is gated indefinitely on that single execFile call. The downstream ensureLmsModelsLoaded retry/timeout config governs the /v1/models probe phase, not the load phase. Suggest follow-up: thread providerReadiness.loadTimeoutMs into execFile options + reject with a diagnostic that names the stuck model. Not a merge-blocker — the failure mode is "boot stalls visibly" rather than "boots into wrong state."
Rhetorical-Drift Audit: Pass — PR description's "post-spawn readiness" framing matches the actual runPostSpawnHook mechanic (markReady on success, SIGTERM child + recordTaskOutcome(failed, phase: 'post-spawn-readiness') on failure). Contract Ledger rows map 1:1 to verified diff content.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: Multi-cycle deferral of #11986 AC5 ("orchestrator-side model-load probe") finally resolved by routing through the sameTaskDefinition.postSpawnhook abstraction that the supervisor already needed for any long-running provider task. The generalization (provider-agnostic supervisor + provider-specific hook in TaskDefinitions) is the right factoring — keepsProcessSupervisorfrom learning about LM Studio specifics while making the hook reusable for the next provider that needs a post-spawn readiness gate.
N/A Audits — 🎯 📑 📡
N/A across listed dimensions: PR uses Related: references (no Closes/Resolves/Fixes magic keywords) → close-target audit N/A. PR body contains a 5-row Contract Ledger that maps cleanly to diff (no public-surface contract gaps) → contract-completeness extended audit N/A. No ai/mcp/server/*/openapi.yaml changes → MCP tool description audit N/A.
🪜 Evidence Audit
- PR body contains explicit
Evidence: L2 ... -> L4 requireddeclaration - Achieved evidence (unit-covered orchestrator supervision + post-spawn failure path + injected
lms load//v1/modelsbehavior) aligns with what CI can cover - Residual (live provider restart validation) explicitly listed in PR body
## Post-Merge Validationchecklist - Two-ceiling distinction: PR explicitly names sandbox-ceiling reason for L2 ("real LM Studio process with both configured models resident in deployment" is L4-required)
Findings: Pass — evidence declaration matches achievable ceiling; residual is operator-side post-merge validation.
🔗 Cross-Skill Integration Audit
N/A — no skill files, conventions, MCP tool surfaces, or AGENTS_STARTUP.md / AGENTS.md touched. ProviderReadinessHelper is internal substrate, not consumer-facing convention.
🧪 Test-Execution & Location Audit
- Branch checked out locally as
pr-12112-reviewviagit fetch origin pull/12112/head - Canonical Location: New tests in
test/playwright/unit/ai/daemons/orchestrator/services/+test/playwright/unit/ai/scripts/runners/— correct perunit-test.md - CI: unit ✓, integration-unified ✓, CodeQL ✓, lint-pr-body ✓, check ✓ (all SUCCESS)
- Coverage of new behavior:
runPostSpawnHooksuccess + failure paths covered inProcessSupervisorService.spec.mjs;ensureLmsModelsLoadedhappy/skip/loud-fail paths covered inrunSandman.spec.mjs;lmstask continuous-supervision +lmsModels/lmsHostgetters covered inOrchestrator.spec.mjs/Orchestrator.invariants.spec.mjs
Findings: Pass — substrate-relevant coverage. HTTP transport (fetchOpenAiCompatibleModelIds) and execFile transport (loadLmsModel) are only exercised through injected seams in tests; both are thin enough that this is acceptable for cycle-1.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 92 - Extends existing helper module, generalizes supervisor with provider-agnostic hook, keeps provider-specific logic in TaskDefinition closure. Right factoring.[CONTENT_COMPLETENESS]: 88 - Contract Ledger + Deltas + Evidence declaration + Post-Merge validation checklist all present. Minor:Related: #12111is a dead reference (operator deleted that ticket); harmless metadata noise.[EXECUTION_QUALITY]: 90 - Loud-fail on missing readiness config, dedupe via Set, optional-chaining defensiveness onmarkReady?., race-safeisCleared()guard before success/failure paths.[PRODUCTIVITY]: 95 - Single tight commit, all CI green on first push, addresses operator-PRIO-0 directly without scope creep.[IMPACT]: 95 - Unblocks confidential tenant deployment + resolves the multi-cycle #11986 AC5 deferral. Without this, Sandman REM cycles silently fail when LM Studio loses model residency.[COMPLEXITY]: 75 - Medium — new public function (runPostSpawnHook), new helper module additions (~150 LOC), new continuous-task lifecycle. Manageable because of the existing hook/supervisor abstraction.[EFFORT_PROFILE]: Architectural Pillar - Establishes the post-spawn readiness gate pattern that future providers (MLX, Ollama, vLLM) can reuse via the samepostSpawnhook.
Ship it.
Related: #12090 Related: #11986
Authored by GPT-5 (Codex Desktop). Session 6ca1b510-51c3-4fac-aa39-a0fd6941318c. Provenance: consumed @neo-opus-ada A2A P0 handoff; peer session id was not included in the wake payload. FAIR-band: over-target [14/30] — taking this lane despite over-target because this is an operator-directed P0 Sandman regression and confidential tenant deployment dependency.
Adds orchestrator-owned LM Studio model residency after
lms server start: the task now loads the configured OpenAI-compatible chat model and embedding model, then waits until/v1/modelsenumerates both before marking the long-running child task ready. A readiness failure fails the supervised task, terminates the child, and records a provider-specific health outcome instead of letting Sandman discover the missing model later.Scope boundary: this PR handles #12090 AC8 / LM Studio pre-warm only. It does NOT complete the Ollama cloud-deployment path; #12090 remains open for the Ollama
requireParallelModelsconfig,/api/pscapacity probe, WARN diagnostics, docs, and deployment validation.Evidence: L2 (unit-covered orchestrator supervision, post-spawn failure path, and injected
lms load//v1/modelsbehavior) -> L4 required (real LM Studio process with both configured models resident in deployment). Residual: live provider restart validation after merge plus remaining Ollama capacity/probe work [#12090].Deltas From Ticket
AiConfig.openAiCompatible.modelandAiConfig.openAiCompatible.embeddingModelas the model list forwarded byOrchestrator.start();orchestrator.lms.modelremains only a legacy direct-caller fallback inTaskDefinitions.AiConfig.orchestrator.providerReadiness; the helper throws when callers omit those values instead of substituting hidden defaults.postSpawnchild-task readiness hook toProcessSupervisorService, so provider-specific readiness stays in the task definition while supervision remains provider-agnostic.ai/config.template.mjsdocumentation. The gitignored localai/config.mjscopy is intentionally not mutated by this PR.lms load.Contract Ledger
AiConfig.openAiCompatible.model+AiConfig.openAiCompatible.embeddingModellmstask fromOrchestrator.start()AiConfig.orchestrator.providerReadinessattempts,delayMs,timeoutMs; no helper-level config fallbackProcessSupervisorServicetask lifecyclerunningafter spawn,readyonly after post-spawn hook succeedsphase: post-spawn-readinessai/config.template.mjstracked;ai/config.mjsgitignoredTest Evidence
node --check ai/services/graph/ProviderReadinessHelper.mjsnode --check ai/daemons/orchestrator/TaskDefinitions.mjsnode --check ai/daemons/orchestrator/Orchestrator.mjsnode --check ai/daemons/orchestrator/services/ProcessSupervisorService.mjsnode --check ai/daemons/orchestrator/services/TaskStateService.mjsgit diff --check origin/dev...HEADnpm run test-unit -- test/playwright/unit/ai/scripts/runners/runSandman.spec.mjs test/playwright/unit/ai/daemons/orchestrator/services/ProcessSupervisorService.spec.mjs test/playwright/unit/ai/daemons/orchestrator/daemon.spec.mjs test/playwright/unit/ai/daemons/orchestrator/Orchestrator.spec.mjs test/playwright/unit/ai/daemons/orchestrator/Orchestrator.invariants.spec.mjs— 83 passedPost-Merge Validation
NEO_ORCHESTRATOR_LMS_ENABLED=trueand alignNEO_OPENAI_COMPATIBLE_HOST,NEO_OPENAI_COMPATIBLE_MODEL, andNEO_OPENAI_COMPATIBLE_EMBEDDING_MODELto the resident LM Studio target.lms load <chat-model>andlms load <embedding-model>only when/v1/modelsis missing either model./v1/modelsenumerates both configured models and thelmstask records areadyhealth outcome.npm run ai:run-sandmanagainst the same provider config and verify the REM cycle no longer reaches graph extraction with an unloaded provider surface.Commit
50abf8ef0—fix(ai): prewarm lms chat and embedding models (#12090)