ADR-0028: Schedules, with cronsim for cron expressions¶
Status: Accepted Date: 2026-09-28 Deciders: Alex Nodeland
Context¶
Schedules publish ticks into workspaces on a timetable, every interval or on a cron expression in a time zone. Rules watch ticks, and ticks move rules' clocks forward in quiet workspaces, so absence rules fire on time (ADR-0007). RFC-0001 left the cron parser to phase 2. Replicas must never publish a tick twice, and a reactor that was down must not flood the log when it returns.
Decision¶
- cronsim parses cron expressions. It is small, typed, dependency-free and BSD-licensed. It is maintained as part of Healthchecks.io, and it handles daylight saving through
zoneinfo. Schedule(name, every= | cron=, timezone=, workspaces="all" | [(tenant, workspace)], catch_up="skip" | "all", max_catch_up=100)is validated data, registered onWorkspaceslike rules.schedule.after(moment)gives its next times, for listing them.- Each workspace remembers each schedule's last tick, saved in the transaction that appends the tick, and a tick's id is derived from the schedule and its time. Replicas therefore cannot publish a tick twice, and no lease is needed.
- A schedule's first check in a workspace starts its timetable; its first tick comes at its next time after that. After downtime,
"skip"publishes only the latest missed tick and"all"publishes each, up tomax_catch_up. Interval schedules compute missed ticks arithmetically, so a long outage costs nothing. - The reactor publishes ticks:
Reactor.tick(), whichsettle()andserve()include. Ticks are attributed toSystemActor(name="scheduler"), and each tick starts its own causal chain.
Options considered¶
cronsim (chosen)¶
| Dimension | Assessment |
|---|---|
| Typing | Ships py.typed; works under pyright strict |
| Dependencies | None |
| Time zones | zoneinfo, correct across daylight saving |
croniter¶
Pros: the most widely used, now maintained under pallets-eco. Cons: not typed, and larger than reflexr needs.
A cron parser of our own¶
Cons: cron semantics, and daylight saving in particular, are easy to get subtly wrong.
Consequences¶
- Easier: heartbeat checks, nightly reports and business-hours rules are a schedule and a rule.
- Harder: ticks add envelopes to every targeted workspace's log; long-lived logs will want the retention work listed in the open questions.
Action items¶
-
Schedule,tick_id, schedule state in storage,Reactor.tick. -
GET /v1/scheduleswith next ticks (phase 5).