---
number: 11865
title: >-
  feat(agentos): extract swarm-heartbeat due-trigger projection —
  scheduling/swarmHeartbeat.mjs (#11859)
author: neo-opus-ada
state: MERGED
createdAt: '2026-05-23T20:42:52Z'
updatedAt: '2026-05-23T20:50:25Z'
closedAt: '2026-05-23T20:50:20Z'
mergedAt: '2026-05-23T20:50:20Z'
head: tobiu/11859-scheduling-swarm-heartbeat
base: dev
url: 'https://github.com/neomjs/neo/pull/11865'
---
Authored by Claude Opus 4.7 (Claude Code). Session 5572d9a5-558d-4bea-b416-e31496c289c4.

FAIR-band: in-band [verify @ merge-gate]

Resolves #11859

## Summary

Sub 16 of Epic #11831. Pure function in `scheduling/<task>.mjs`, matching the shape Sub 15 (PR #11863) established after 6 iterations of KISS pressure. Applies all Sub-15 lessons from the start — zero iteration overhead this round.

Evidence: L1 (4/4 pure-function unit tests pass) → L1 required for pure extraction (no behavioral change at orchestrator surface; new file is not wired in this Sub — Sub 18 #11862 owns the registry integration).

## Sub-15 lessons applied from the start

- File at `ai/daemons/orchestrator/scheduling/swarmHeartbeat.mjs` (no `Service`/`Coordinator` suffix — pure function, not a service)
- One JSDoc block above the one function (no file-level + function-level split)
- No `class extends Base`, no `singleton: true` config, no `Neo.gatekeep` (zero Neo class-system features used)
- No decay-prone JSDoc references (no `v13-path.md:N`, no section labels like `D3.1`, no neighboring-file method names)
- Spec doesn't import Neo bootstrap (no module-load gatekeep call requiring `globalThis.Neo`)
- Function signature parallel to `scheduling/dream.mjs` for consistency

## Strict boundary preserved

- `SwarmHeartbeatService` retains `initAsync()` + `pulse()` + all lifecycle/execution methods unchanged
- Orchestrator's inline schedule logic (`Orchestrator.mjs:906-934`) is NOT modified yet — Sub 18 #11862 wires the new projection into the registry
- The enabled-gate (`this.swarmHeartbeatEnabled`) stays in Orchestrator's call-site for now; Sub 18 will move profile-aware filtering into `pickNextCandidate`

## Test Evidence

`npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/scheduling/swarmHeartbeat.spec.mjs` → **4/4 pass (507ms)**

Test coverage:
- Interval-elapsed returns trigger
- Interval-not-elapsed returns null
- `intervalMs <= 0` disabled (both `0` and negative)
- Missing state graceful (lastRunAt defaults to 0)

## Post-Merge Validation

- [ ] Operator confirms no regression in swarm-heartbeat scheduling (new file exists but not wired; should be no observable change)

## Deltas from ticket

- File location updated to `scheduling/swarmHeartbeat.mjs` (no `Service`/`Coordinator` suffix) per operator's KISS feedback on PR #11863
- Ticket title still references `SwarmHeartbeatCoordinatorService` — title naming churn deferred

## Depends on

Epic #11831 (parent). Independent of Sub 15 #11858 (already merged); parallel-safe with Sub 17 #11860.

## Unblocks

Sub 18 #11862.

## Authority

Discussion #11857 Cycle-3.5 GPT `[GRADUATION_APPROVED]` + operator architectural ratify + operator KISS feedback on PR #11863 (scheduling/<task>.mjs pure-function pattern).

Per @tobiu's "you and me" instruction for Epic #11831 lane: no peer-review broadcast.

## Reviews

### `@tobiu` (APPROVED) reviewed on 2026-05-23T20:50:13Z

*No review body provided.*

---

