Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Schedules

A schedule is a saved run and a cadence — nothing more. It has no arguments of its own that run does not have. If you can type it, you can schedule it.

centinel schedule set tampa-daily --cron "0 3 * * *" --source tampa
centinel schedules
centinel history
centinel serve

centinel serve is what fires them. --no-schedule serves the read API without firing anything, which is what a machine that serves a corpus somebody else collects wants.

Writing one

centinel schedule set <id> [flags]
centinel schedule rm  <id>
FlagMeaning
--cron EXPR5-field cron expression, or a shorthand like @daily
--tz ZONEIANA zone name. Defaults to the host’s.
--source IDsource to run. Repeatable. Omit for every enabled source.
--skip STAGEstage to skip. Repeatable.
--limit Nstop collection after this many addresses, per source
--refreshre-fetch and re-derive everything at every fire. Expensive, and deliberate.
--jitter-secs Nseconds of jitter. Zero fires exactly on the minute.
--disabledwrite the block but leave it disarmed
--no-catch-updo not fire on startup when overdue
--replacereplace an existing schedule with this id

On a terminal with no arguments, the CLI asks. Everywhere else the id and cadence are required — the op itself never prompts, because an op that blocks on input cannot be called over HTTP.

Whichever way you write one, schedule set prints the next few fire times in the schedule’s own zone. Three dates settle whether 0 3 * * 1 meant Mondays or the 1st, and that is the last cheap moment to notice it did not.

Why cron, and why a zone

Cron, because a civic record has a shape a fixed interval cannot express: before the Tuesday meeting, overnight, not during business hours.

The zone is part of the schedule rather than the host, because a corpus outlives the machine that collects it, and “3am” means a different instant after a daylight-saving change. Storing the zone keeps the intent rather than the offset.

Jitter exists because forks are the point. If a hundred cities each run the same default cadence, they all hit their upstreams on the same minute.

Schedules live in the config

centinel.toml, as [[schedule]] blocks, beside the [[source]] blocks they name. Not in the store, because the store is fact — what was collected — and a cadence is intent. The same distinction that makes source list report a union.

Creation is a command rather than hand-edited TOML because a hand-written block invites two silent mistakes: a cron expression that parses and means something else, and a source id that does not exist. centinel schedules --check runs exactly the validation serve performs before binding, so you can find out after editing rather than at the next restart.

An invalid schedule refuses to start the server. A scheduler that silently drops the one broken entry is a scheduler you cannot trust the other twenty of.

One lane

Runs do not overlap. One at a time, per store, with the lock on disk — because the CLI is a second process, and centinel run typed by hand while the server is mid-run is the ordinary case, not an edge case.

The queue is depth one per schedule, FIFO, with no merging. A fire that arrives while something is running waits; a second one that arrives while one is already waiting is dropped, because two identical pending runs do the work of one.

Catch-up fires once on startup when a schedule is overdue, never a backlog. A machine that was off for a week does not wake up owing seven runs.

Reading what happened

centinel schedules                  # what is configured, when each next fires, how the last went
centinel history                    # every attempt, newest first
centinel history --failed
centinel history --schedule tampa-daily
centinel history --source tampa
centinel history --since 2026-08-01T00:00:00Z
centinel history --run 8f3c         # one run, by id or unambiguous prefix — the whole report

history covers manual runs too, not only scheduled ones. It is a record of attempts.

One record per attempt, not per success. A crash leaves evidence: the record is written when the run starts, so a run that died is a row that never completed rather than a gap you have to infer.

The run id is its start instant, which is why a prefix is enough to name one.

Additions and subtractions

The history record carries the arithmetic of what each run changed — bytes that entered the corpus, and what the discovery delta was. Nothing is ever removed: a Resource that vanished from a sitemap is a subtraction from the snapshot, not a deletion from the archive. The bytes stay, and the liveness changes.

The counts are in the record; the addresses are in the log. That split is deliberate — a run record that listed every address would be a second copy of the log, and the second copy is the one that goes stale.

Next: Models.