Scheduled work: .co/schedule.yaml and co schedule
An agent can do work on its own clock: a report every Monday at 9, a sync
every 15 minutes. You write the schedule in .co/schedule.yaml; the hosted
agent (the script that calls host(...), locally or after co deploy) runs it; co schedule shows it and
lets you run, pause or resume an entry.
The clock lives inside the agent process, so it works the same on macOS, Windows and Linux with no cron or systemd setup (#521).
Quick start
# .co/schedule.yaml
- name: morning report
at: "Mon 09:00"
tz: Australia/Sydney
run: "Summarise last week's orders and email the team"
- name: sync
every: 15m
exec: "python scripts/sync.py"
co schedule check # does the scheduler read it the way you meant?
co schedule # what runs next, what ran last, how it went
Start the hosted agent (python agent.py when it calls host(...), or
co deploy) and the entries run from then on.
Entry fields
| field | meaning |
|---|---|
name |
How the entry is shown and addressed (co schedule run <name>). Defaults to the run or exec text. Must be unique. |
run |
A prompt. It runs as an agent turn through the same path as a message sent to the agent, and lands in .co/session_results.jsonl like any other session. |
exec |
A shell command, run in the project directory. Recorded by exit code, with the end of stderr kept on failure. Use it for deterministic work: a model's "I ran the script" is not evidence the script ran (#709). Limited to 10 minutes. |
every |
An interval: 30s, 15m, 2h, 1d. Must be greater than zero. |
at |
A time: "09:00" (daily) or "Mon 09:00" (weekly). |
tz |
IANA timezone for at, e.g. Australia/Sydney. Defaults to UTC. |
Each entry needs exactly one of run or exec, and one of every or at.
How it runs
- One tick a minute. Each minute the agent finds the due entries, marks
each one
runningin.co/schedule-state.json, and runs each as its own task. A slow entry never delays another one. - One copy at a time. An entry still running when it comes due again is skipped, and the log says so. An entry that overruns every time is configured with the wrong interval.
- Catch-up, once. A run missed while the agent was down fires on the next
tick, once, however many were missed. A new
atentry does not back-fill earlier occurrences from before it existed. - One scheduler per cluster. Under
--workers N, one worker claims each tick, so an entry is not started N times. - Edits apply live. The file is re-read every tick. An entry the agent
writes into
schedule.yamlwhile running starts without a restart. - A bad entry is dropped, not fatal. It is reported at startup and by
co schedule check; the other entries keep running.
Seeing and controlling it: co schedule
| command | what it does | writes |
|---|---|---|
co schedule / co schedule list |
each entry: cadence, next run, last run, status, failure reason, session id, paused. --json for scripts. |
nothing |
co schedule check |
validate schedule.yaml exactly as the scheduler reads it; exit 1 naming each ignored entry |
nothing |
co schedule run <name> |
run the entry on the next tick, even if it is paused or not due | schedule state |
co schedule pause <name> |
stop the entry firing | schedule state |
co schedule resume <name> |
put it back on its schedule | schedule state |
run, pause and resume write .co/schedule-state.json, never
schedule.yaml. The state file belongs to the machine the agent runs on and
is not deployed over, so a pause survives restarts and deploys. The running
agent acts on it at its next tick, within a minute. Run the commands in the
agent's project directory, on the machine where it runs.
The agent's Home page shows the same state in its Scheduled section: running, paused, when it last ran, and why a run failed.
Where results go
| file | holds |
|---|---|
.co/schedule-state.json |
per entry: last run, status (running, done, failed), failure reason, session id, paused |
.co/session_results.jsonl |
the full session of each run entry: prompt, transcript, result, duration |
done means the turn returned, not that the work succeeded: a model that
answered "I could not reach the drive" also returned. Check the session, or
use exec when success is a fact a command can report.
If the agent stops while an entry is running, the next start records that run
as failed with the reason "the agent stopped during this run".
ConnectOnion