Skip to main content
Scheduled agents now remember a small notepad across runs — a cursor, a watermark, a last-seen id — so a recurring job can do incremental work instead of repeating itself.

Quick Start

1

Agent-centric — just enable it, no ceremony

Once the wrapper’s ConfigYamlScheduleStore is in use, a scheduled agent gets the notepad automatically. The agent reads its notepad from the prompt and writes back by returning a dict with state_updates (or state).
2

Direct Python — build the ScheduleJob yourself

No new arguments — the notepad is picked up automatically from the store.
3

YAML parity


How It Works

Each tick loads prior state, prepends a notepad block, runs the agent, then merges and persists whatever the gate and agent emit. The gate is only handed state= when it opts in (signature-detected), so legacy stateless gates never see a TypeError.

The Notepad Format

The block prepended to the prompt is deterministic — sorted key=value lines under a fixed header, separated from the message by a blank line. With prior state {"last_seen_pr": 4821, "cursor": "abc"}:
  • Sorted alphabetically by key so ordering is stable across runs.
  • Empty state → no notepad block (byte-for-byte the prior behaviour).
  • The block is separated from the message by a blank line.

How the Agent Writes Back

Two ways an agent can persist an update:
  1. Return an object with state_updates or state — a rich chat result exposing a dict. Only dicts are honoured; a plain string result writes nothing.
  2. A monitor gate’s GateResult.state_updates — persists on both the go-path and the no_change / skip path.
A stateful gate carries a watermark forward even on a suppressed tick:

Configuration Options

Behaviour is enabled automatically when the runner uses ConfigYamlScheduleStore (the wrapper default) — no new ScheduleJob fields, no new imports, no new module exports.
A job with delete_after_run=True is removed from the store (and its state popped) before the run reaches the executor. The executor therefore does not persist state for one-shot jobs — persisting now would recreate an orphan job_state entry nothing ever cleans up, and would be injected if the same explicit id were reused. Use cross-run memory only for recurring jobs.

Custom Stores

Advanced backends implement JobStateStoreProtocol — the same three methods, the same hasattr() capability detection. All three methods are optional; absence means today’s stateless behaviour.
This page is for the built-in default — the deeper contract lives in Scheduler Monitor and Scheduler Change Detection.

Best Practices

Store cursors, ids, and hashes — not full payloads. The 16 KiB cap rejects oversized writes and keeps the prior state, so a runaway set_state can never bloat config.yaml.
The agent sees the sorted key=value lines verbatim. Write instructions like “if last_seen_pr is present, only list PRs above it.” so the agent knows exactly which keys to read and write.
A GateResult.state_updates persists even when the tick is no_change / skipped, so the next tick sees the advance. Use this to move a monitor watermark forward on a silent tick.
delete_after_run=True skips persistence by design — a one-shot job has no “next run” to read the notepad. Use cross-run memory only for recurring jobs.

Scheduler Monitor

The change-detection sibling that carries a hashed watermark

Scheduler Change Detection

Deeper GateResult / JobStateStoreProtocol reference

Scheduler Pre-Run Gate

The stateless sibling — a cheap go/no-go gate

Scheduler Monitor Mode

Declarable monitor spec that drives change detection