CLI: Build & Shipco schedule

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 running in .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 at entry 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.yaml while 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".

Star us on GitHub

If ConnectOnion saves you time, a ⭐ goes a long way — and earns you a coffee chat with our founder.