---
id: 12005
title: Delete daemon.mjs router dead code; consolidate mlx/lms config into Orchestrator getters (thin-Orchestrator completion)
state: CLOSED
labels:
  - ai
  - refactoring
  - architecture
assignees:
  - neo-opus-ada
createdAt: '2026-05-26T00:12:36Z'
updatedAt: '2026-05-26T00:59:12Z'
githubUrl: 'https://github.com/neomjs/neo/issues/12005'
author: neo-opus-ada
commentsCount: 0
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-26T00:59:12Z'
---
# Delete daemon.mjs router dead code; consolidate mlx/lms config into Orchestrator getters (thin-Orchestrator completion)

**The 25+-ticket "thin Orchestrator" refactor wave moved behavior into purpose-shaped places** (registry pattern at `scheduling/registry.mjs`, per-lane `getDueTask` pure functions, singleton services like `SwarmHeartbeatService`/`TaskStateService`/`ProcessSupervisorService`). **But the daemon.mjs config router was left behind** — it still pre-resolves every lane's interval + enable-toggle + lane-internal config into a flat options bag that `Orchestrator.start()` discards because the Orchestrator has its own getter pattern that supersedes it.

## V-B-A — daemon.mjs is doing dead routing

[`ai/daemons/orchestrator/daemon.mjs::resolveOrchestratorStartOptions`](ai/daemons/orchestrator/daemon.mjs#L182) (lines 182-268) maps 13 keys into a flat options bag:

| Daemon-resolver key | Orchestrator getter that supersedes it |
|---|---|
| `summarySweepIntervalMs` | `get summarySweepIntervalMs()` at [Orchestrator.mjs:347](ai/daemons/orchestrator/Orchestrator.mjs#L347) |
| `kbSyncIntervalMs` | `get kbSyncIntervalMs()` at [Orchestrator.mjs:348](ai/daemons/orchestrator/Orchestrator.mjs#L348) |
| `backupIntervalMs` | `get backupIntervalMs()` at [Orchestrator.mjs:349](ai/daemons/orchestrator/Orchestrator.mjs#L349) |
| `primaryDevSyncIntervalMs` | `get primaryDevSyncIntervalMs()` at [Orchestrator.mjs:350](ai/daemons/orchestrator/Orchestrator.mjs#L350) |
| `dreamIntervalMs` | `get dreamIntervalMs()` at [Orchestrator.mjs:371](ai/daemons/orchestrator/Orchestrator.mjs#L371) |
| `goldenPathIntervalMs` | `get goldenPathIntervalMs()` at [Orchestrator.mjs:372](ai/daemons/orchestrator/Orchestrator.mjs#L372) |
| `swarmHeartbeatIntervalMs` | `get swarmHeartbeatIntervalMs()` at [Orchestrator.mjs:373](ai/daemons/orchestrator/Orchestrator.mjs#L373) |
| `kbSyncEnabled` | `get kbSyncEnabled()` at [Orchestrator.mjs:389](ai/daemons/orchestrator/Orchestrator.mjs#L389) |
| `primaryDevSyncEnabled` | `get primaryDevSyncEnabled()` at [Orchestrator.mjs:390](ai/daemons/orchestrator/Orchestrator.mjs#L390) |
| `bridgeDaemonEnabled` | `get bridgeDaemonEnabled()` at [Orchestrator.mjs:392](ai/daemons/orchestrator/Orchestrator.mjs#L392) |
| `swarmHeartbeatEnabled` | `get swarmHeartbeatEnabled()` at [Orchestrator.mjs:393](ai/daemons/orchestrator/Orchestrator.mjs#L393) |
| `goldenPathRepoEnrichmentEnabled` | `get goldenPathRepoEnrichmentEnabled()` at [Orchestrator.mjs:394](ai/daemons/orchestrator/Orchestrator.mjs#L394) |

Each Orchestrator getter does the IDENTICAL env-override + `AiConfig.orchestrator.X` fallback the daemon resolver does (lines 347-394 pattern: `return Env.parseNumber('NEO_ORCHESTRATOR_X') ?? AiConfig.orchestrator.intervals.X;`). `Orchestrator.start()` at [Orchestrator.mjs:416-489](ai/daemons/orchestrator/Orchestrator.mjs#L416) consumes ONLY `scriptDir, dataDir, dbPath, logFile, stateFile, heavyMaintenanceLeasePath, taskDefinitions, nodeBin, mlxEnabled/mlxModel/mlxPort, lmsEnabled/lmsModel/lmsPort, primaryDevSyncRootsConfig` — none of the 12 resolver-produced keys.

The resolver's output is **discarded at the start() boundary**. ~80 lines of pure router boilerplate (the function itself + `assignConfigInterval` helper + `assignLocalOnlyToggle` helper + spread call site at line 352).

### Why tests didn't catch this

[`test/playwright/unit/ai/daemons/orchestrator/daemon.spec.mjs`](test/playwright/unit/ai/daemons/orchestrator/daemon.spec.mjs) has 8+ test blocks asserting the resolver's output shape. The tests verify *what the function returns*, not *what consumes the return value*. Classic tests-as-architectural-anchor failure mode — they pin the dead code in place by giving it visible green coverage.

### `resolveMlxConfig` / `resolveLmsConfig` — live but architecturally borderline

[`daemon.mjs:286-326`](ai/daemons/orchestrator/daemon.mjs#L286). These ARE consumed: `startOrchestrator` resolves them → passes `mlxEnabled/mlxModel/mlxPort/lmsEnabled/lmsModel/lmsPort` to `Orchestrator.start({...})` → forwarded to `buildTaskDefinitions(...)` at [Orchestrator.mjs:431-436](ai/daemons/orchestrator/Orchestrator.mjs#L431).

Flow: `daemon.mjs (resolve) → Orchestrator.start (pass-through) → buildTaskDefinitions (consume)`. The mlx/lms env-override + fallback pattern is identical-shape to the interval-getter pattern. Substrate-correct shape: Orchestrator owns the resolution via getters (`get mlxEnabled()`, `get mlxModel()`, etc.), and `buildTaskDefinitions` either reads from `this.X` or is invoked with the Orchestrator-resolved values.

## The Fix

Three sub-changes, all surgical:

1. **Delete `resolveOrchestratorStartOptions` + `assignConfigInterval` + `assignLocalOnlyToggle`** from `daemon.mjs`. Delete the spread `...resolveOrchestratorStartOptions(...)` at line 352. Net daemon.mjs: -85 LOC.

2. **Consolidate `resolveMlxConfig` / `resolveLmsConfig` into Orchestrator getters** at the existing getter cluster. Pattern matches the interval-getter pattern already in place:
   ```js
   get mlxEnabled() { return Env.parseBool('NEO_ORCHESTRATOR_MLX_ENABLED') ?? !!AiConfig.orchestrator.mlx?.enabled; }
   get mlxModel()   { return process.env.NEO_ORCHESTRATOR_MLX_MODEL || AiConfig.orchestrator.mlx?.model; }
   get mlxPort()    { return process.env.NEO_ORCHESTRATOR_MLX_PORT  || AiConfig.orchestrator.mlx?.port; }
   // ditto for lms
   ```
   Remove `resolveMlxConfig` / `resolveLmsConfig` from `daemon.mjs`. Update `buildTaskDefinitions` call in `Orchestrator.start()` to read `this.mlxEnabled/this.mlxModel/this.mlxPort/this.lmsEnabled/this.lmsModel/this.lmsPort` directly. Net daemon.mjs: -50 LOC, Orchestrator.mjs: +6 getters.

3. **Collapse `daemon.mjs::startOrchestrator`** to:
   ```js
   export async function startOrchestrator(options = {}) {
       fs.ensureDirSync(DAEMON_DATA_DIR);
       await enforceSingleton();
       setupCleanupHandlers();
       await loadLocalAiConfig();

       return Orchestrator.start({
           dataDir: DAEMON_DATA_DIR,
           primaryDevSyncRootsConfig: AiConfig.orchestrator?.devSyncRoots,
           ...options  // test-injection seams only
       });
   }
   ```
   The Orchestrator picks up everything else from its own getters reading `AiConfig` + env directly.

4. **Delete the corresponding daemon.spec.mjs test blocks** for the removed resolvers (the tests verify dead code; removing the code retires the tests). Add new Orchestrator getter coverage for `mlxEnabled/mlxModel/mlxPort/lmsEnabled/lmsModel/lmsPort` mirroring the existing interval-getter test patterns.

## Contract Ledger

| Surface | Source of Authority | Proposed Behavior | Fallback / Edge Case | Docs | Evidence |
|---|---|---|---|---|---|
| `daemon.mjs::startOrchestrator` | This ticket | Thin process-boot wrapper only: PID/signal mgmt + dotenv + Neo bootstrap + loadLocalAiConfig + delegating to `Orchestrator.start({dataDir, primaryDevSyncRootsConfig, ...options})` | Test-injection options (`scriptDir`, `dbPath`, `taskDefinitions`, etc.) still forwarded via spread | Updated JSDoc reflecting thin-boot shape | Live smoke: orchestrator boots + all lanes resolve their cadence via getter chain |
| `Orchestrator` mlx/lms getters | This ticket | New: `get mlxEnabled() / mlxModel() / mlxPort() / lmsEnabled() / lmsModel() / lmsPort()` — same env+AiConfig pattern as interval getters | `AiConfig.orchestrator.mlx?.enabled` / `.model` / `.port` defaults (optional-chaining for missing config blocks) | JSDoc on each getter | Unit test: env override wins, AiConfig fallback works, undefined-block tolerated |
| `Orchestrator.start(options)` | This ticket | Reads mlx/lms config from `this.X` getters inside; `buildTaskDefinitions` call uses `this.mlxEnabled/this.mlxModel/...` | `options.mlxEnabled` / etc. **removed** from documented signature (only test-injection seams remain) | Updated JSDoc on start() options | Existing orchestrator.spec.mjs coverage continues passing |
| `daemon.spec.mjs` | This ticket | Removes resolveOrchestratorStartOptions / resolveMlxConfig / resolveLmsConfig test blocks (they test removed code) | None | None | All other daemon.spec tests continue passing |

## Acceptance Criteria

- [ ] AC1 — `daemon.mjs::resolveOrchestratorStartOptions`, `assignConfigInterval`, `assignLocalOnlyToggle` deleted entirely.
- [ ] AC2 — `daemon.mjs::resolveMlxConfig`, `resolveLmsConfig` deleted; `Orchestrator` exposes 6 new getters (`mlxEnabled`, `mlxModel`, `mlxPort`, `lmsEnabled`, `lmsModel`, `lmsPort`) mirroring the existing env+AiConfig fallback pattern.
- [ ] AC3 — `Orchestrator.start()` consumes mlx/lms config via `this.X` getters (not via `options.X`); `buildTaskDefinitions` call updated.
- [ ] AC4 — `daemon.mjs::startOrchestrator` collapses to thin boot wrapper (PID, signals, Neo bootstrap, config load, delegate). Only test-injection seams remain in the options signature.
- [ ] AC5 — `daemon.spec.mjs` test blocks for the removed resolvers deleted. New Orchestrator getter test coverage for mlx/lms added (mirrors interval-getter test patterns).
- [ ] AC6 — Live smoke: `npm run ai:orchestrator` boots cleanly; all lanes (summary, kbSync, backup, primaryDevSync, dream, goldenPath, swarmHeartbeat) resolve their cadence correctly via getters; mlx/lms tasks (when enabled in config) launch correctly.
- [ ] AC7 — Static grep clean: `grep -rn "resolveOrchestratorStartOptions\|resolveMlxConfig\|resolveLmsConfig\|assignConfigInterval\|assignLocalOnlyToggle" ai/ test/ --include='*.mjs'` returns zero matches.
- [ ] AC8 — Cross-family review per `pull-request §6.1`.

## Out of Scope

- Lane-internal config ownership beyond mlx/lms (e.g., moving summarySweepIntervalMs INTO `scheduling/summary.mjs` as a self-reading constant). The Orchestrator-getter pattern is the shipped substrate-correct shape; respecting it is sufficient. Deeper "lanes own their entire config" refactor is a separate substrate-evolution lane if needed.
- Process-management primitives (PID file, signal handlers, log file routing). These legitimately belong in daemon.mjs as the process-boot entry point.
- `loadLocalAiConfig` — config substrate bootstrap is correctly a daemon-level concern; runs before the Orchestrator class loads.
- Adjacent lanes #11994 (gate-fix on PR #12004) and #12003 (candidate-discovery). Different file surfaces, no overlap.

## Avoided Traps

- Preserving `resolveOrchestratorStartOptions` as a "backwards-compat shim" — operator's standing rule from earlier sessions: "no backwards compatibility for these config migrations." The flat-options bag was always discarded; preserving it would extend the dead-code lifetime.
- Moving mlx/lms config INTO each task definition's module (deeper than the Orchestrator getter pattern). That'd diverge from the established interval-getter pattern; consistency wins for now.
- Keeping the daemon.spec.mjs blocks "for archaeology" — removed-code tests are debt. The removed code's intent is preserved in this ticket body + git history.

## Related

- **25+-ticket "thin Orchestrator" refactor wave** that motivated the substrate-evolution direction this ticket completes
- **#11991** (config class-substrate completion) — established `class Config extends BaseConfig` pattern that makes `AiConfig.orchestrator.X` chain reliable for the getters to read
- **#11986/#11987** (lms server lifecycle as orchestrator task) — established mlx/lms as supervised-process lanes; this ticket consolidates their config to match the rest of the lane-config substrate
- **Sibling lanes:** #11994 / PR #12004 (Shape B gate fix), #12003 (active-a2a-participants candidate discovery) — adjacent substrate, non-overlapping files

Handoff Retrieval Hint: "daemon.mjs router cleanup resolveOrchestratorStartOptions dead code Orchestrator getters mlx lms config consolidation thin Orchestrator completion".

## Timeline

- 2026-05-26T00:12:37Z @neo-opus-ada added the `ai` label
- 2026-05-26T00:12:38Z @neo-opus-ada added the `refactoring` label
- 2026-05-26T00:12:38Z @neo-opus-ada assigned to @neo-opus-ada
- 2026-05-26T00:12:38Z @neo-opus-ada added the `architecture` label
- 2026-05-26T00:26:55Z @neo-opus-ada cross-referenced by PR #12006
- 2026-05-26T00:29:34Z @neo-opus-ada cross-referenced by #12007
- 2026-05-26T00:37:44Z @neo-opus-ada referenced in commit `84d396a` - "test(orchestrator): MLX/LMS test isolation + explicit-false-env-override coverage (#12005)

Cycle-2 response to @neo-gpt CHANGES_REQUESTED on PR #12006:

1. Test isolation. File-level beforeEach/afterEach hooks now save/restore
   AiConfig.orchestrator.mlx + AiConfig.orchestrator.lms (full slot snapshots),
   plus 6 NEO_ORCHESTRATOR_{MLX,LMS}_{ENABLED,MODEL,PORT} env vars. Mirrors the
   existing kbSync/tenantRepoSync save/restore discipline. Individual mlx/lms
   tests can now mutate freely without leak risk across Playwright worker reuse.

2. Coverage parity. Added 2 explicit-false-env-override tests (one for mlxEnabled,
   one for lmsEnabled): `env=false` beats `AiConfig.X.enabled = true`. Restores
   the coverage from the deleted resolveMlxConfig / resolveLmsConfig tests for
   the path most at regression risk if the getter's `??` operator drifts to `||`.

Simplified the 6 existing mlx/lms tests by removing redundant per-test try/finally
since the file-level hooks now own the cleanup contract.

Test evidence: 24/24 PASS Orchestrator.invariants.spec.mjs at this commit (22 prior + 2 new)."
- 2026-05-26T00:59:12Z @tobiu referenced in commit `0c95af0` - "refactor(orchestrator): delete daemon.mjs router dead code; consolidate mlx/lms config into Orchestrator getters (#12005) (#12006)

* refactor(orchestrator): delete daemon.mjs router dead code; consolidate mlx/lms config into Orchestrator getters (#12005)

The 25+-ticket "thin Orchestrator" refactor wave moved lane behavior into purpose-shaped
places (scheduling/registry.mjs, lane services like SwarmHeartbeatService) but left the
daemon.mjs config router behind. Empirical anchor: resolveOrchestratorStartOptions
(daemon.mjs:182-268) computed 13 keys (8 intervals + 5 enable-toggles) into a flat
options bag passed to Orchestrator.start({...}) — but Orchestrator.start NEVER consumed
those 13 keys; Orchestrator has its own getters at lines 347-394 that do the same
env-override + AiConfig.orchestrator.X fallback resolution. ~80 LOC of router boilerplate
doing zero runtime work. Daemon.spec.mjs pinned the dead code in place with 8+ test
blocks asserting the resolver's output shape.

Changes:

1. ai/daemons/orchestrator/daemon.mjs (362 → 186 LOC)
   - Deleted resolveOrchestratorStartOptions + assignConfigInterval + assignLocalOnlyToggle
     (dead code — output never consumed by Orchestrator.start).
   - Deleted resolveMlxConfig + resolveLmsConfig (consolidated to Orchestrator getters).
   - Removed `Env` import (unused after deletions).
   - Collapsed startOrchestrator() to thin process-boot wrapper:
     PID + signals + dotenv + Neo bootstrap + loadLocalAiConfig + delegate.

2. ai/daemons/orchestrator/Orchestrator.mjs (+ 13 LOC)
   - 6 new getters mirroring the existing interval-getter pattern:
     mlxEnabled / mlxModel / mlxPort / lmsEnabled / lmsModel / lmsPort.
   - Each consults Env.parseBool / parseString first, then AiConfig.orchestrator.X.
   - start() reads mlx/lms from `this.X` getters instead of `options.X`.
   - Removed mlxEnabled/mlxModel/mlxPort/lmsEnabled/lmsModel/lmsPort from documented
     options signature; only test-injection seams remain.

3. ai/daemons/orchestrator/TaskDefinitions.mjs
   - JSDoc updated to reference Orchestrator getters instead of deleted daemon.mjs resolvers.

4. test/playwright/unit/ai/daemons/orchestrator/daemon.spec.mjs (536 → 241 LOC)
   - Deleted imports for removed exports.
   - Deleted 5 test blocks for removed resolvers.
   - Refreshed buildTaskDefinitions test comments to point at Orchestrator getters.

5. test/playwright/unit/ai/daemons/orchestrator/Orchestrator.invariants.spec.mjs (+ 6 tests)
   - mlxEnabled env-precedence over AiConfig
   - mlx getters consult AiConfig when env absent
   - mlx getters undefined-safe when AiConfig.orchestrator.mlx missing
   - Same 3 tests for lms

Test evidence:
- daemon.spec.mjs: 9/9 PASS (was 17 with dead-code tests)
- Orchestrator.invariants.spec.mjs: 22/22 PASS (16 prior + 6 new mlx/lms tests)
- Broader orchestrator tree: 324/326 PASS (2 unrelated pre-existing DreamService failures)
- Static grep clean: `grep -rn "resolveOrchestratorStartOptions|resolveMlxConfig|resolveLmsConfig|assignConfigInterval|assignLocalOnlyToggle" ai/ test/ --include='*.mjs'` returns zero matches.

Net diff: -503 / +124 LOC. Substrate-clarity gain proportional to the deletion.

* test(orchestrator): MLX/LMS test isolation + explicit-false-env-override coverage (#12005)

Cycle-2 response to @neo-gpt CHANGES_REQUESTED on PR #12006:

1. Test isolation. File-level beforeEach/afterEach hooks now save/restore
   AiConfig.orchestrator.mlx + AiConfig.orchestrator.lms (full slot snapshots),
   plus 6 NEO_ORCHESTRATOR_{MLX,LMS}_{ENABLED,MODEL,PORT} env vars. Mirrors the
   existing kbSync/tenantRepoSync save/restore discipline. Individual mlx/lms
   tests can now mutate freely without leak risk across Playwright worker reuse.

2. Coverage parity. Added 2 explicit-false-env-override tests (one for mlxEnabled,
   one for lmsEnabled): `env=false` beats `AiConfig.X.enabled = true`. Restores
   the coverage from the deleted resolveMlxConfig / resolveLmsConfig tests for
   the path most at regression risk if the getter's `??` operator drifts to `||`.

Simplified the 6 existing mlx/lms tests by removing redundant per-test try/finally
since the file-level hooks now own the cleanup contract.

Test evidence: 24/24 PASS Orchestrator.invariants.spec.mjs at this commit (22 prior + 2 new).

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-26T00:59:13Z @tobiu closed this issue
- 2026-05-26T00:59:56Z @neo-opus-ada cross-referenced by #12008
- 2026-05-26T02:04:05Z @neo-opus-ada cross-referenced by PR #12010

