Context
Follow-up to PR #11899 (Sub 18 Phase 1: scaffolding + MBS wiring). Phase 1 split off the substrate-evolution foundation (registry + collector + picker primitives + MaintenanceBackpressureService activation) and shipped under a focused PR. Phase 2 ships the dispatch switchover + cleanup (mechanical work consuming the foundation) to complete ticket #11862 ACs.
This sub-ticket is a delivery-shape adjustment: Phase 1/2 split was operator-velocity-pushback-driven course correction from #11862's monolithic 5-commit Plan agent prescription. Substantive direction unchanged; mechanical execution staged for reviewer-side clarity + ship-velocity recovery.
The Problem
#11862 ACs not satisfied by PR #11899:
- AC1 (partial): CadenceEngine.runIfDue still has the executeFn parameter; pure-trigger-builder refactor per v13-path.md:117 / #11025 spec is incomplete.
- AC2: Orchestrator.mjs currently 611 LOC (was 892); target ≤400 LOC requires Phase 2 dispatch switch + Sub-13 cleanup.
- AC3: 13 mock-heavy
Orchestrator.spec.mjs tests still present; per-module collapse pending.
- AC6: Sub-13 cleanup (strip class @summary historical narrative + section divider comments "=== Service-DI Class A/B/C/D ===") pending.
Phase 1's per-poll MBS binding refresh is a defensive substrate idiom that should be revisited during Phase 2 — once cadenceEngine.runIfDue is dropped + dispatch becomes a single collect → pick → execute pipeline, the per-poll refresh location can be evaluated for consolidation OR replacement with per-prop afterSet hooks.
Architectural Reality
PR #11899 Phase 1 lands:
ai/daemons/orchestrator/scheduling/registry.mjs — 7 frozen coordinator descriptors
ai/daemons/orchestrator/scheduling/collector.mjs — collectDueCandidates pure function
ai/daemons/orchestrator/scheduling/picker.mjs — pickNextCandidate pure function with 3-stage pipeline
- 3 spec files (23 tests)
Orchestrator.mjs (892 → 611 LOC) — MBS wiring + 11 method deletes + Class B propagation
Phase 2 consumes Phase 1's primitives:
poll() body currently has 7 this.cadenceEngine.runIfDue(taskName, dueCheckFn, executeMaintenanceTask(executeFn), context) blocks
- Phase 2 replaces these with the 3-line pipeline:
const {candidates, errors} = collectDueCandidates({registry: TASK_REGISTRY, context: this.buildContext()});
const winner = pickNextCandidate({candidates, runningTasks, policyContext: this.buildPolicyContext()});
if (winner) await this.execute(winner);
- Add
execute(winner) method with 4-key dispatch map (supervised-child-process → runViaProcessSupervisor; service-runner → primary-dev-sync runner; in-process-async → dream/golden-path/swarm-heartbeat envelope)
- Delete
cadenceEngine.runIfDue from CadenceEngine.mjs + its dedicated tests
- Class @summary strip: lines 95-141 of current Orchestrator.mjs ("Service-DI 4-way classification" + "No configure(). No DEFAULT_X_*_MS" trailer) → compact 4-line @summary
- Section divider comments: 4-6 "=== Service-DI Class A/B/C/D ===" comments removed
The Fix
5-commit sequence (each independently green per Plan agent design from #11862):
- C3: Switch dispatch from
cadenceEngine.runIfDue to collect → pick → execute pipeline. Add execute(winner) method with 4-key dispatch map. Delete cadenceEngine.runIfDue + 3 dedicated tests in CadenceEngine.spec. Orchestrator drops to ~430 LOC.
- C4: Sub-13 cleanup. Strip class @summary historical narrative (lines 95-141, ~46 LOC). Remove 4-6 section divider comments. Replace with compact 4-line @summary citing scheduling/registry.mjs for descriptor model. Orchestrator drops to ~400 LOC (AC2 hit).
- C5: Collapse 13 mock-heavy
Orchestrator.spec.mjs tests (lines 142-413 per pre-#11862 grep). Keep 4-5 integration smoke tests through real registry+picker+MBS pipeline (one per executionKind). Test count drops from 22 → ~9 in Orchestrator.spec.mjs.
Per-poll MBS binding refresh (from PR #11899 Phase 1) re-evaluation: after C3 collapses the 7 runIfDue blocks into single 3-line pipeline, the per-poll refresh can either stay (defensive idiom; covers future test-bypass-start cases) OR migrate to a single afterSetWriteLog hook (cleaner but more code). Recommend stay-as-is unless review surfaces alternative.
Acceptance Criteria
Out of Scope
- No further Orchestrator splitting beyond ≤400 LOC target (Round-3 evaluation deferred to post-measurement per Plan agent design from #11862).
- No bootstrap split (deferred to Round-3).
- No new abstractions beyond the dispatch map (the existing registry primitives are the substrate).
Avoided Traps
- ❌ Re-implementing per-poll MBS binding refresh logic (PR #11899 idiom should stay until proven unnecessary).
- ❌ Adding more reactive parent-prop afterSet hooks (the per-poll refresh covers more cases for less code — substrate-lesson banked in PR #11899 commit 85e6b4928).
- ❌ Breaking the
runIfDue API mid-poll (atomic single-commit replacement preserves test green throughout).
- ❌ Aggressive test removal beyond the 13 mock-heavy identified ones (keeps integration smoke coverage).
Depends on
PR #11899 (Sub 18 Phase 1) — must merge first to ship Phase 1 primitives that Phase 2 consumes.
Related
- #11862 (parent — partially-resolved by PR #11899 Phase 1)
- Epic #11831 (parent epic — Orchestrator Round-2 closure)
- Discussion #11857 (origination)
- PR #11899 (Phase 1 implementation)
- Sub 19 #11861 (MaintenanceBackpressureService — merged, activated in Phase 1)
Origin Session ID: ba62643a-ae78-41b2-9ce5-e7890155760b
Handoff Retrieval Hints:
- Query Memory Core for
Sub 18 Phase 1 PR #11899 scaffolding MBS wiring per-poll refresh
- Query Memory Core for
Sub 18 Plan agent 5-commit design
- Branch:
tobiu/11862-orchestrator-sub-18 continues here after Phase 1 merge
Authored by: Claude Opus 4.7 (Claude Code)
Context
Follow-up to PR #11899 (Sub 18 Phase 1: scaffolding + MBS wiring). Phase 1 split off the substrate-evolution foundation (registry + collector + picker primitives + MaintenanceBackpressureService activation) and shipped under a focused PR. Phase 2 ships the dispatch switchover + cleanup (mechanical work consuming the foundation) to complete ticket #11862 ACs.
This sub-ticket is a delivery-shape adjustment: Phase 1/2 split was operator-velocity-pushback-driven course correction from #11862's monolithic 5-commit Plan agent prescription. Substantive direction unchanged; mechanical execution staged for reviewer-side clarity + ship-velocity recovery.
The Problem
#11862 ACs not satisfied by PR #11899:
Orchestrator.spec.mjstests still present; per-module collapse pending.Phase 1's per-poll MBS binding refresh is a defensive substrate idiom that should be revisited during Phase 2 — once
cadenceEngine.runIfDueis dropped + dispatch becomes a singlecollect → pick → executepipeline, the per-poll refresh location can be evaluated for consolidation OR replacement with per-prop afterSet hooks.Architectural Reality
PR #11899 Phase 1 lands:
ai/daemons/orchestrator/scheduling/registry.mjs— 7 frozen coordinator descriptorsai/daemons/orchestrator/scheduling/collector.mjs—collectDueCandidatespure functionai/daemons/orchestrator/scheduling/picker.mjs—pickNextCandidatepure function with 3-stage pipelineOrchestrator.mjs(892 → 611 LOC) — MBS wiring + 11 method deletes + Class B propagationPhase 2 consumes Phase 1's primitives:
poll()body currently has 7this.cadenceEngine.runIfDue(taskName, dueCheckFn, executeMaintenanceTask(executeFn), context)blocksconst {candidates, errors} = collectDueCandidates({registry: TASK_REGISTRY, context: this.buildContext()}); // ... orchestrator-side error reporting from errors ... const winner = pickNextCandidate({candidates, runningTasks, policyContext: this.buildPolicyContext()}); if (winner) await this.execute(winner);execute(winner)method with 4-key dispatch map (supervised-child-process→runViaProcessSupervisor;service-runner→ primary-dev-sync runner;in-process-async→ dream/golden-path/swarm-heartbeat envelope)cadenceEngine.runIfDuefrom CadenceEngine.mjs + its dedicated testsThe Fix
5-commit sequence (each independently green per Plan agent design from #11862):
cadenceEngine.runIfDuetocollect → pick → executepipeline. Addexecute(winner)method with 4-key dispatch map. DeletecadenceEngine.runIfDue+ 3 dedicated tests in CadenceEngine.spec. Orchestrator drops to ~430 LOC.Orchestrator.spec.mjstests (lines 142-413 per pre-#11862 grep). Keep 4-5 integration smoke tests through real registry+picker+MBS pipeline (one per executionKind). Test count drops from 22 → ~9 in Orchestrator.spec.mjs.Per-poll MBS binding refresh (from PR #11899 Phase 1) re-evaluation: after C3 collapses the 7 runIfDue blocks into single 3-line pipeline, the per-poll refresh can either stay (defensive idiom; covers future test-bypass-start cases) OR migrate to a single
afterSetWriteLoghook (cleaner but more code). Recommend stay-as-is unless review surfaces alternative.Acceptance Criteria
cadenceEngine.runIfDueparameterexecuteFndropped per v13-path.md:117 / #11025; signature becomesrunIfDue(taskName, dueCheckFn, context)returning trigger or null. ORrunIfDuedeleted entirely if Phase 2 dispatch shape doesn't need a trigger-builder layer (collector callsdescriptor.getDueTaskdirectly).Orchestrator.mjs≤ 400 LOC post-Phase-2 merge.Orchestrator.spec.mjstests collapsed; 4-5 integration smoke tests retained (one per executionKind + 1 lease-cross-daemon E2E).collectDueCandidatescauses no observable state mutation — NEGATIVE test in collector.spec.mjs already enforces.collectDueCandidatescontains noswitch(profile)policy body — source-text guardrail test in collector.spec.mjs already enforces.configure(). NoDEFAULT_X_*_MS" trailer + 4-6 Class A/B/C/D section divider comments.poll()callsMaintenanceBackpressureService.acquireLeaseAndExecute({...})and.executeWithGoldenPathDependencyGate({...})— already wired in Phase 1.Out of Scope
Avoided Traps
runIfDueAPI mid-poll (atomic single-commit replacement preserves test green throughout).Depends on
PR #11899 (Sub 18 Phase 1) — must merge first to ship Phase 1 primitives that Phase 2 consumes.
Related
Origin Session ID: ba62643a-ae78-41b2-9ce5-e7890155760b
Handoff Retrieval Hints:
Sub 18 Phase 1 PR #11899 scaffolding MBS wiring per-poll refreshSub 18 Plan agent 5-commit designtobiu/11862-orchestrator-sub-18continues here after Phase 1 mergeAuthored by: Claude Opus 4.7 (Claude Code)