Docs
A job is a workflow
Steps, state, replay, and the one rule that makes carrying on after a pause safe.
A job is an input, a state and steps. It is an async function, so branching and
looping are if and for: a job can stop early (if every site is up, end),
loop (each of five sites) and wait (ask before restarting). Carrying on after a
pause means running the function again from the top, with every finished step
handing back what it returned last time.
One rule makes that safe: work happens inside a step, and code outside a step only decides. A step is written down, so it never runs twice. A line outside a step runs again every time the job resumes, so it must not send, write or spend.
That is the price of if and for instead of a builder API, and it is the trap
worth knowing before you write your second job.
What a job is handed
step(name, fn)does something, once, and writes down what it returned.model(name, options)asks a model one question and validates the answer against a zod schema.agent(name, options)hands a goal and some tools to a model and lets it choose the order, inside the tools it was given and the cap you set.ask(name, options)stops and waits for a person. See asking a person.
The first three are the autonomy hierarchy: reach down it, not up it.
stateis the shared store, with a schema of its own, and it survives a pause. A step's result is for the step after it;stateis what the whole job is accumulating. Two channels, not one bag, which is why a parked job can show somebody what it already found before it asks them anything.
A job says what it did
summary turns what run returned into one line, and that line is what the
overview shows beside the run. The job knows what its result means, so the page
never guesses. A job without one shows no line, and a prompt's line is the start
of its reply.
A job that was edited under a parked run
The replay checks each step's name as well as its place, because handing the wrong recorded answer to the wrong step would look like it worked. If the job changed, the run stops and says so. Start it again.
Two runs never overlap
Whether it is code or a prompt, a second run of the same job is skipped rather than queued, and a job already waiting on somebody is not started again. A job that takes longer than its own interval quietly runs less often than its cron line says.
When a job runs
cron is a cron line, and chloejs/timer writes one in words:
every(15).minutes // */15 * * * *
every(4).hours // 0 */4 * * *
every.hour.at(30) // 30 * * * *
every.day.at("07:00") // 0 7 * * *
every.day.at("10:45", "22:45") // 45 10,22 * * *
every.weekday.at("9:30") // 30 9 * * 1-5
every.monday.at("9:00") // 0 9 * * 1
every.month.on(1).at("09:00") // 0 9 1 * *What a cron line cannot say evenly is refused as the file loads, with the reason
and the file's name: every(7).minutes would run at :56 and again at :00,
every.month.on(31) would skip short months, and two times of day with
different minutes need two jobs. A plain cron string still works.
timezone is a real timezone name and daylight saving is handled through
Intl, so write every.day.at("07:00") with timezone: "America/New_York" and
stop doing UTC arithmetic in a comment.
Without a cron a job runs only when somebody starts it: the Run button,
npm run agent <agent> <job>, or POST /api/jobs/<agent>/<id>/run.