State persistence is now wired (PR #4057).
JobStateStoreProtocol has a concrete ConfigYamlScheduleStore implementation (16 KiB per-job cap), and ScheduledAgentExecutor loads prior state into a “Notepad” prompt block, threads it into state=-aware gates, and persists GateResult.state_updates + agent-emitted state — including on no_change / skip ticks. See Cross-Run Memory for the full walkthrough. The change-detection probe + hash + diff wrapper (what actually decides a source changed) still ships in a follow-up.Quick Start
1
Agent-centric — watch a page, tell me only what's new
The agent schedules the monitor for you from a plain request.
2
Direct Python — build the ScheduleJob yourself
Set
monitor to name a cheap source to probe each tick.3
Shell source — probe a cheap counter
A bounded command is a better source than a full HTML page.
4
YAML — configure via agents.yaml
How It Works
The wrapper hashes the probed source and compares it to the last-seen hash held in per-job state. Caller contract (verbatim from the SDK): “Because a legacy stateless gate accepts onlyjob, callers MUST NOT unconditionally pass state=. Detect capability first — e.g. call should_run(job, state=prior) only when the gate opts into state, otherwise fall back to should_run(job) — so stateless gates keep working unchanged and never raise TypeError.”
An error probe (a transient 500) leaves prior state untouched, so a later recovery to the prior output still suppresses — a transient failure does not count as a change.
Run Status: no_change vs the others
RunRecord.status gains a fourth value so an operator can tell a silent monitor apart from a deliberate skip.
no_change implies run=False — the core enforces this invariant in GateResult.__post_init__. Use no_change records in session show to prove a monitor is running even when it is silent.What’s Stored in Per-Job State
The monitor keeps a small durable scratchpad keyed by job id — typically the last-seen hash, plus an optional cursor / watermark.Distinct From Other Gates
Pick the right sibling —monitor is the stateful one that seeds a diff.
Configuration Options
Themonitor field is a small mapping naming one cheap source to probe each tick.
monitor serialises only when set — None (default) keeps the existing stateless behaviour, and an explicitly-empty mapping round-trips faithfully. For the full field types, see the auto-generated SDK reference rather than duplicating them here.Best Practices
Prefer a bounded, cheap probe
Prefer a bounded, cheap probe
A JSON endpoint returning a single counter beats hashing a full HTML page — it changes only when the thing you care about changes, and it is fast on every tick.
A transient error is not a change
A transient error is not a change
An error probe leaves prior state untouched — a transient 500 does not “count as a change”. When the source recovers to its prior output, the tick still suppresses. Design probes that fail loudly (non-zero exit) rather than emitting an error page as “content”.
Use no_change records to prove a monitor is alive
Use no_change records to prove a monitor is alive
A silent monitor looks identical to a broken one. Check run history — a stream of
no_change records proves the monitor is probing every tick and simply has nothing to report.Never pass state= to a stateless gate
Never pass state= to a stateless gate
Callers MUST detect capability first. Pass
state= only when the gate opts into state; otherwise fall back to should_run(job) so legacy stateless gates never raise TypeError.See Also
Configure a monitor from the CLI with--monitor-url (public HTTP(S)) or --monitor-command (shell stdout) — see CLI: schedule → Wake only when a page changes. Both flags produce byte-identical jobs to the monitor={"url": …} / monitor={"command": …} form here, and the URL path carries the reliability guarantees documented on the core Scheduler Monitor page.
Related
Scheduler Cross-Run Memory
The notepad that persists state across runs — now wired
Scheduler Pre-Run Gate
The stateless sibling — a cheap go/no-go gate
Scheduler Command Action
The no-LLM sibling — deliver a command’s stdout verbatim
Scheduler Delivery
Where results go — note
no_change ticks do not deliverAsync Agent Scheduler
The scheduler this monitor plugs into

