Parent Epic
#11831 — Sub 16 of Epic. Second of the 3 coordinator extractions in Discussion #11857 Cycle-3.5 B-prime convergence.
Premise
Per v13-path.md:117 D3.1 boundary, per-task coordinators decide due. SwarmHeartbeatService currently has scheduling logic in Orchestrator's scheduleSwarmHeartbeat(). Extract to coordinator.
Specific care (per GPT Cycle-1.1): SwarmHeartbeatService.initAsync() is boot/lifecycle setup (one-time init for the pulse loop). It is NOT a scheduler concern and MUST stay in SwarmHeartbeatService. Coordinator owns ONLY due-trigger selection.
Prescription
Create ai/daemons/orchestrator/services/SwarmHeartbeatCoordinatorService.mjs:
getDueTask({state, now, swarmHeartbeatIntervalMs, ...}) → trigger | nullAcceptance Criteria
ai/daemons/orchestrator/services/SwarmHeartbeatCoordinatorService.mjs ships with getDueTask({...}) only
- Pure-function unit tests for
getDueTask({...}) with synthetic state
SwarmHeartbeatService retains initAsync(), pulse(), and all lifecycle/execution methods unchanged
- Orchestrator's
scheduleSwarmHeartbeat() body is NOT modified yet (Sub 18 wires)
- No new env vars / config keys / behavior changes
Avoided Traps
initAsync() MUST stay in SwarmHeartbeatService — coordinator absorbing it is the anti-pattern
- No reactive-config-aliased wrapper
- No premature Orchestrator integration
Depends on
Epic #11831.
Unblocks
Sub 18.
Authority
Discussion #11857 Cycle-3.5 graduation.
Authored by: [Claude Opus 4.7] (Claude Code)
Parent Epic
#11831 — Sub 16 of Epic. Second of the 3 coordinator extractions in Discussion #11857 Cycle-3.5 B-prime convergence.
Premise
Per
v13-path.md:117D3.1 boundary, per-task coordinators decide due.SwarmHeartbeatServicecurrently has scheduling logic in Orchestrator'sscheduleSwarmHeartbeat(). Extract to coordinator.Specific care (per GPT Cycle-1.1):
SwarmHeartbeatService.initAsync()is boot/lifecycle setup (one-time init for the pulse loop). It is NOT a scheduler concern and MUST stay inSwarmHeartbeatService. Coordinator owns ONLY due-trigger selection.Prescription
Create
ai/daemons/orchestrator/services/SwarmHeartbeatCoordinatorService.mjs:getDueTask({state, now, swarmHeartbeatIntervalMs, ...}) → trigger | nullAcceptance Criteria
ai/daemons/orchestrator/services/SwarmHeartbeatCoordinatorService.mjsships withgetDueTask({...})onlygetDueTask({...})with synthetic stateSwarmHeartbeatServiceretainsinitAsync(),pulse(), and all lifecycle/execution methods unchangedscheduleSwarmHeartbeat()body is NOT modified yet (Sub 18 wires)Avoided Traps
initAsync()MUST stay inSwarmHeartbeatService— coordinator absorbing it is the anti-patternDepends on
Epic #11831.
Unblocks
Sub 18.
Authority
Discussion #11857 Cycle-3.5 graduation.
Authored by: [Claude Opus 4.7] (Claude Code)