Scheduler — cadence to fired run [Flow]
Source path: knowledge-base/diagrams/flows/scheduler.md
# Scheduler — cadence to fired run `[Flow]`
The headless Program scheduler is the **time-based orchestration** layer: it reads enabled schedules from the record, arms Croner timers, and on each tick runs a Research/Opinion/Merge/Position operation to completion — under concurrency, serialization, and spend gates. The pure *when-to-fire* logic is separate from the imperative shell that does the waking.
```mermaid
flowchart TB
Rec[("record — enabled schedules<br/>(days · hours · everyNWeeks)")]
Meta[("META — **baseline operations timezone**<br/>(aux/meta.json · one instance-wide IANA zone)")]
Rec -->|"reconcile() · per-document fingerprint + compact projection<br/>read only the source that changed"| Arm
Meta -->|"all cadences interpreted in it"| Arm
subgraph Arm["arm — pure WHEN → live timers"]
direction TB
Cron["scheduleToCron(schedule)<br/>(future windows only — no boot replay)"]
Timer["new Cron(pattern, tz, onFire)<br/>diff vs live timers: add new · drop stale"]
Cron --> Timer
end
Timer -->|"tick"| Fire
subgraph Fire["onFire — the gates"]
direction TB
Week{"weekGatePasses?<br/>(fortnightly/monthly — cron can't express)"}
Spend{"spend cap?<br/>(provider-spending jobs only)"}
Serial{"domain already running?<br/>(per-domain serialization)"}
Conc{"concurrency slot?<br/>(global cap, default 3)"}
Week -->|"no"| Skip1(["skip until due"])
Week -->|"yes"| Spend -->|"blocked"| Skip2(["skip — cap reached"])
Spend -->|"ok"| Serial -->|"busy"| Queue(["queue — one run per domain"])
Serial -->|"free"| Conc -->|"full"| Wait(["wait for a slot"])
Conc -->|"slot"| Run
end
Run["runOperationToEnd — the Research/Opinion/Merge/Position op<br/>(coalesced cycleIds if cadence matches)"]
Run -->|"same op machinery"| Ops(["operation-lifecycle → commits + echo"])
```
**Invariants**
- **Croner wakes; the record decides.** The scheduler holds no schedule authority of its own — it derives timers from enabled schedules. The 30-second `reconcile()` poll checks cheap per-document sidecar/body-stat fingerprints and retains only each source's compact job projection, so one canonical write re-reads one Domain/Constellation rather than the whole record; incomplete reads are never cached. Timers fire in **future windows only**; there is no boot replay of missed runs.
- **One baseline timezone, all cadences relative.** A cadence stores only its wall-clock day/hour; the ONE instance-wide **baseline operations timezone** (persisted in the record's META, Director-set in Settings) interprets every cadence at once. Croner fires the local hour in the baseline zone → the exact UTC instant; changing the baseline re-relativizes every schedule with no per-domain rewrite. Hydration seeds it from META, else the operator's browser zone. Cadence editors and the agenda render local-primary + UTC-secondary.
- **The everyNWeeks gate is checked at fire time.** Cron can't express fortnightly/monthly, so `weekGatePasses(schedule, lastRunAt, now)` gates it in the shell. Research cycles sharing a schedule **coalesce** into one Watch pass (days/hours sorted → order-independent).
- **Three discipline gates, all independent.** Per-domain serialization (at most one mutating run per domain), a global concurrency cap (default 3), and a per-instance **weekly spend ceiling** that gates only provider-spending jobs (syncs are exempt). Failures are isolated — one domain's error never stalls another.
- **Only always-on hosts arm it.** `isAlwaysOnServer()` gates the scheduler, reconcile loop, and journal drain ([platform-model](../concepts/platform-model.md)); serverless hosts run no timers.
**Where it lives:** `src/lib/server/program/scheduler.ts` (imperative shell — timers + gates), `src/lib/core/program/schedule-cron.ts` (pure `scheduleToCron` · `weekGatePasses` · `nextFireAt`, all baseline-zone-aware), `src/lib/core/program/operations-timezone.ts` (the baseline resolver: get/set/subscribe, validated), `src/lib/server/record/instance-usage.ts` (spend estimate). The run itself: [operation-lifecycle](operation-lifecycle.md) · [maintenance-run](maintenance-run.md). Timing rules owned by `src/lib/core/program/schedule-cron.ts`.
Open on GitHubRaw Markdown source