Skip to content

Runs and sessions

Submit a run and the platform validates it, checks the budget, writes a row, and a worker claims it. Everything that happens after that is recorded: the steps, the events, the tokens, the cost.

A run’s status is one of seven values, and the set is closed:

StatusMeaning
queuedAccepted and waiting for a worker.
runningA worker is executing it.
suspendedStopped, waiting for something outside itself. See below.
succeededFinished; output is populated.
failedFinished badly; failure_class says what kind of badly.
timed_outRan past its deadline.
cancelledStopped by a person or by a caller.

The last four are terminal.

A run can be attempted more than once. attempt_count and max_attempts are on the run, and a retry produces a new attempt on the same run — same id, same history, one more attempt.

Replay is the other one: it produces a new run, against either the revision the original was pinned to or the current one. Retry when you think the same execution deserves another go; replay when you want a fresh execution to compare against.

When a run fails it carries a failure_class, and the class is what tells you whether anything can be done: transient, provider, tool, guardrail, budget, policy, timeout, recursion, cancelled, fatal.

A failed run still costs money. A run that made three model calls and then tripped a guardrail spent real spend, and it appears in the usage ledger — the aggregates cover every terminal status, not only the successful ones.

A suspended run is not stuck. It has stopped on purpose and is waiting for something, and suspension_kind says what:

KindWaiting for
approvalA human to approve or reject.
delegationA child run it started.
pauseSomebody paused it.
external_waitAn external system. Today: your client, executing the tools it owns. See Tools your client executes.

Resuming uses the contract the suspension declared. They are never interchangeable:

  • answer — a value is awaited; you supply it.
  • result: the results of work done outside the run are awaited, and you send them.
  • continue — nothing is awaited; you just say to carry on.

Sending the wrong one is a 409 that names the one it expected. That is deliberate: a resume that silently accepted either would let an approval be answered with a shrug.

A session is a thread: the runs that belong to one conversation, and the messages they produced.

There are two ways to attach a run to one, and the second is the interesting one:

  • session_id — you already have ours; continue that thread.
  • session_key — your own external identifier, resolved-or-created. A chat product keys a thread by its own conversation id and never stores ours.

When you list sessions, include_automatic defaults to true. Setting it to false hides sessions whose origin is delegation, a schedule or maintenance — which is what you want when a person is looking at a conversation list and machine traffic would swamp it. The default is true because hiding data by surprise is the worse mistake.

Every run records a trigger_kind, and it is how you tell an operator’s click from a cron job: api, schedule, event, channel, delegation, retry, replay, maintenance, system.

delegation is the one that means “another agent started me”. A run carries parent_run_id, root_run_id and delegation_depth, so a tree of agent-calls-agent is walkable from any node in it.

Where
The resultoutput on the run itself
What it did, step by stepGET /runs/{run_id}/steps
The journal, replayable and resumableGET /runs/{run_id}/events
Tokens and costGET /runs/{run_id}/usage
The execution stateGET /runs/{run_id}/state

Step payloads — the full inputs and outputs of each step, rather than the summary — sit behind an admin floor, because that is where the raw content of a run lives.