Skip to main content
Turn any scheduled job into a change-aware monitor — wake only when a watched source changed, and hand the agent only the diff.
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 only job, 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.
The state is bounded (size-capped by the implementation) and cleared when the job is removed. The core owns only the shape; the wrapper decides where to persist it.

Distinct From Other Gates

Pick the right sibling — monitor is the stateful one that seeds a diff.

Configuration Options

The monitor 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

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.
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”.
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.
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.

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 deliver

Async Agent Scheduler

The scheduler this monitor plugs into