Runs
One execution of an agent, from the queue to a finished transcript with tokens and cost.
A run is one execution of an agent. The API records runs but never executes them: assigning a task, waking an agent or triggering a routine puts a run in the queue, and a runner such as sat runner claims it, executes it with Claude Code or Codex, streams the transcript back and reports tokens and cost. Runs are how work happens and how money is spent, so every budget, cost report and "live runs" view is built from them.
How it works
What queues a run
trigger | Created by | First transcript entry |
|---|---|---|
assignment | Creating a task with an assignee, or reassigning a task to a different agent. Only when the agent is available (not paused, pending_approval or terminated) and the task is not backlog, done or cancelled. | Assigned NF-12 |
manual | Wake on an agent's page, sat agents wake, or POST /agents/{agent_id}/wake. Agent heartbeats from the scheduler also use this endpoint, so they are manual runs too. | The reason you passed, for example heartbeat; otherwise Queued (manual) |
routine | Triggering a routine, by Run now, sat routines trigger or the scheduler. The routine first creates a task, and the run is linked to both the task and the routine. | Queued (routine) |
A heartbeat wake from sat scheduler start sends {"reason": "heartbeat"}, so you can tell it apart from a person's wake only by that first system transcript entry. There is no separate heartbeat trigger value.
Queue dedupe. Before creating a run, the API looks for a run that is still queued for the same agent with the same task_id and the same routine_id. If one exists, that run is returned instead of a new one. The trigger is not compared: a manual wake on NF-12 while an assignment run for NF-12 is still queued returns the assignment run. Once a run is claimed it no longer blocks new runs.
Lifecycle
- A run never goes back to
queued. APATCHwithstatus: queuedreturns400 INVALID_STATE. - A finished run (
succeeded,failedorcancelled) rejects every further update with400 RUN_FINISHED, including cancel. - When a run starts, its agent becomes
running. When it finishes, the agent goes back toidle(or toerrorafter a failure), unless the agent has another run stillrunning.
Claim, lease, heartbeat and expiry
Runners take work with POST /runs/claim, which atomically picks the oldest queued run whose agent is not dormant and not already running something, marks it running, stamps runner_id, started_at and heartbeat_at, and returns everything needed to execute it. One agent runs at most one claimed run at a time.
The claim is a lease. The runner keeps it alive by calling POST /runs/{run_id}/heartbeat; any PATCH /runs/{run_id} progress report also refreshes heartbeat_at. A running run whose last heartbeat (or start, if it never sent one) is older than RUN_LEASE_SECONDS (default 300) is expired: it becomes failed with an error such as "Runner my-mac:4242 stopped reporting (lease expired)". Expiry runs at the start of every claim, on POST /runs/expire-stale, and from sat scheduler start every 60 seconds.
A runner that sends a runner_id different from the one that claimed the run gets 409 RUN_LEASE_LOST and must stop. The full protocol, including what the runner does on cancel and lease loss, is in How runs work.
Transcript
The transcript is an ordered list of entries. Runners append with append_transcript on PATCH; the API adds system entries when the run is queued and claimed.
Prop
Type
GET /runs leaves transcripts out; fetch one run to get its transcript.
Tokens and cost
Runners report tokens_in, tokens_out and cost_cents (integer cents). Each PATCH replaces the previous values, so the last report wins. sat runner reports what the adapter measured: Claude Code reports its own cost, while Codex runs cost 0 unless the runner sets SAT_CODEX_PRICE_IN and SAT_CODEX_PRICE_OUT. See Budgets and costs.
Finishing enforces the agent's budget
Every way a run can finish (a runner's final report, cancel, lease expiry, agent termination) checks the agent's monthly budget. If the agent's spend this month has reached a non-zero monthly_budget_cents, the agent is set to paused and a budget_override approval is opened (one per agent at a time). A paused agent's queued runs stay queued and are not claimed until it is resumed.
Cancel
POST /runs/{run_id}/cancel works on queued and running runs, whichever runner holds them. It is the same as a PATCH with status: cancelled and no runner_id. A runner executing the run learns about it from its next heartbeat or report (409 RUN_LEASE_LOST or 400 RUN_FINISHED) and stops. Cancelling also enforces the agent's budget.
Fields
RunOut, returned by the list and inside other responses. GET /runs/{run_id} returns RunDetail, which adds transcript.
Prop
Type
Statuses
| Status | Meaning | Changed by |
|---|---|---|
queued | Waiting for a runner. | Created by an assignment, wake or routine trigger. |
running | A runner holds the lease and is executing it. | POST /runs/claim, or a PATCH with status: running (for runners that do not use claims; not recommended). |
succeeded | The runner reported success. | The runner's final PATCH. |
failed | The runner reported failure, or the lease expired. | The runner's final PATCH, or lease expiry. |
cancelled | Stopped by a person or by terminating the agent. | POST /runs/{run_id}/cancel, PATCH with status: cancelled, or POST /agents/{agent_id}/terminate (error "Agent terminated"). |
Use it
Runs appear in several places: Live runs on the dashboard (queued and running), the Runs tab on an agent's page (last 50), the Recent failed runs section of the inbox, and the activity log.
Select a run to open its detail page at /c/{companyId}/runs/{runId}. It shows:
- The agent, the trigger (for example "Grace · assignment run") and a link to the task, such as
NF-3 Implement rich-text editor core. - Started, Duration, Tokens in / out and Cost.
- The runner's summary, and the error in a red box when there is one.
- The Transcript, with time and role for each entry. While the run is
queuedorrunningthe page shows a live dot and "Waiting for the runner to report…", and refreshes as the runner reports.
Cancel run appears while the run is queued or running.
To queue a run by hand, open the agent and select Wake. It is hidden for paused, pending_approval and terminated agents.
Permissions
Any member of the company can list, read, wake, claim, report on and cancel runs. Non-members get 404 NOT_FOUND. There is no separate runner role: a runner acts with the credentials of the person who started it, usually an API key.
Events
| Event | When | Extra fields |
|---|---|---|
run.created | A run was queued by an assignment, wake or routine trigger. Also sent when dedupe returned an existing queued run. | agent_id |
run.updated | Claimed, reported on (every PATCH), cancelled, expired, or cancelled by termination. | agent_id, status |
agent.updated | The agent started or finished a run, or was paused for budget. | |
approval.created | Finishing the run paused the agent and opened a budget_override approval. Sent without an id. |
Heartbeats publish no events. See Live updates.
Limits and known gaps
- The API does not execute runs. Without a runner (
sat runner startor your own), runs stayqueuedforever. - Dedupe compares agent, task and routine only, not the trigger.
GET /runsreturns at most 500 rows and has no paging cursor. The CLI resolves id prefixes against the 500 most recent runs.- Cost is whatever the runner reports. SAT does not price tokens itself.
- Spend is counted by
created_at, so a run's cost counts toward the month it was queued in, even if it finished in the next month. - The transcript is stored as one JSON array on the run, with no size limit or truncation on the server.
- Run history is kept indefinitely; there is no retention or purge.
Related
How runs work
The claim, lease and report protocol in detail.
sat runner
Execute queued runs on your machine.
Budgets and costs
How run cost becomes spend and pauses agents.
Approvals
What happens when an agent reaches its budget.
List runs (API)
GET /api/companies/{company_id}/runs
Claim run (API)
POST /api/companies/{company_id}/runs/claim
Update run (API)
PATCH /api/companies/{company_id}/runs/{run_id}
Cancel run (API)
POST /api/companies/{company_id}/runs/{run_id}/cancel
Wake agent (API)
POST /api/companies/{company_id}/agents/{agent_id}/wake