Parent Epic: #11022 (M3.5 Orchestrator Decomposition)
Context
As part of the M3.5 Orchestrator decomposition, we have successfully extracted TaskStateService (Sub-1) and ProcessSupervisorService (Sub-2). The next step is Sub-3: CadenceEngine extraction.
Currently, interval polling logic and cadence parsing (shouldRunIntervalTask, parseInterval, and boilerplate cycle-run logic) are tightly coupled inside ai/daemons/Orchestrator.mjs. We need to extract this into a dedicated CadenceEngine service.
Per OQ8 resolution in Discussion #11025, CadenceEngine must be a pure trigger-builder, NOT an execute-runner. It exposes a primitive like getIntervalTrigger({taskName, now, lastRunAt, intervalMs, reasonPrefix}).
Execution Plan
- Target: Extract interval/cadence logic from
ai/daemons/Orchestrator.mjs into ai/daemons/services/CadenceEngine.mjs.
- Boundary:
CadenceEngine returns a trigger object (or null if not due). It does not execute the task. Per-task coordinators decide "what work is due" by composing this primitive; the supervisor executes; the orchestrator wires.
- Tests: Implement unit tests for
CadenceEngine.spec.mjs covering interval parsing and trigger boundary conditions. Remove corresponding redundant tests from Orchestrator.spec.mjs while maintaining behavior.
Avoided Traps
- ❌ Making CadenceEngine an execute-runner: Doing a
runIfDue shape would make CadenceEngine a mini-Orchestrator and violates OQ8.
- ❌ Direct state mutation: The trigger object will be passed down to ProcessSupervisor, which consumes TaskStateService for state updates.
Acceptance Criteria
Parent Epic: #11022 (M3.5 Orchestrator Decomposition)
Context
As part of the M3.5 Orchestrator decomposition, we have successfully extracted
TaskStateService(Sub-1) andProcessSupervisorService(Sub-2). The next step is Sub-3:CadenceEngineextraction.Currently, interval polling logic and cadence parsing (
shouldRunIntervalTask,parseInterval, and boilerplate cycle-run logic) are tightly coupled insideai/daemons/Orchestrator.mjs. We need to extract this into a dedicatedCadenceEngineservice.Per OQ8 resolution in Discussion #11025,
CadenceEnginemust be a pure trigger-builder, NOT an execute-runner. It exposes a primitive likegetIntervalTrigger({taskName, now, lastRunAt, intervalMs, reasonPrefix}).Execution Plan
ai/daemons/Orchestrator.mjsintoai/daemons/services/CadenceEngine.mjs.CadenceEnginereturns a trigger object (ornullif not due). It does not execute the task. Per-task coordinators decide "what work is due" by composing this primitive; the supervisor executes; the orchestrator wires.CadenceEngine.spec.mjscovering interval parsing and trigger boundary conditions. Remove corresponding redundant tests fromOrchestrator.spec.mjswhile maintaining behavior.Avoided Traps
runIfDueshape would make CadenceEngine a mini-Orchestrator and violates OQ8.Acceptance Criteria
ai/daemons/services/CadenceEngine.mjsimplemented as a pure trigger-builder.parseIntervalandshouldRunIntervalTask.CadenceEngine.spec.mjsauthored and passing.