Runs are the third of the Harness's three nouns: everything that executes. Every turn of conversation, every delegated child, every script is a row in the runs table (agents/src/sqlite-schema.sql:123), and each row knows its parent — so execution is a tree. The same table is the dispatch queue: the index runs_dispatch (status, queue, not_before, created_at) is the scheduler's view.
A run is not a session. RunInfo.kind is 'agent' | 'workflow', and sessionId is optional — "Transcript session for agent runs; workflow runs have none."
Making execution durable data rather than in-memory promises buys every long-running property the system has: waiting is free, a crash is recoverable, and day-scale work is ordinary rows.
Shape
type RunStatus = 'queued' | 'claimed' | 'running' | 'waiting' | 'succeeded' | 'failed' | 'canceled'Alongside identity (id, root_run_id, parent_run_id, depth) a row carries parent_tool_call_id (the call that spawned it — how a transcript row finds its child while it runs), continued_from_run_id, origin (user | trigger | agent | workflow | system), a mandatory title, input/output/error as CBOR, wait, attempt/max_attempts/not_before, queue, lease_owner/lease_expires_at, budget, usage, and plan.
Titles are enforced at enqueue — a blank one throws Run title is required. A chat turn is named from the user's message (first line, truncated at 80 characters); a continueAsNew successor inherits its predecessor's title verbatim, so a week-long loop reads as one continuing effort.
The queue
Two queues, interactive and background. Claiming is an atomic UPDATE … RETURNING that sets status='claimed', stamps a 60-second lease, and increments attempt. Candidate ordering puts interactive first, then FIFO by creation.
One live run per session is enforced inside the claim query itself, not by a lock above it: an agent run cannot be claimed while another run of the same session is claimed or running. A refused claim surfaces as a 409 Session is already streaming.
Concurrency: 8 agent runs and 32 script runs in flight (maxConcurrentModelRuns, maxConcurrentWorkflows).
Crash recovery is two steps. sweepAtBoot() requeues everything left claimed/running by a dead process. Then #reconcileWaitingRunsAtBoot() replays finished children into parents still parked on them — closing the window where a child committed its terminal status but the parent's wait resolution hadn't yet.
Retries are classified, not blanket. A provider 5xx is retryable; a 4xx is a config error and is not. Backoff is min(5s × 2^(attempt−1), 5min) plus jitter. An interactive turn gets maxAttempts: 1 — retrying would hold the session lock through the backoff — while a background run gets 3.
Cancellation cascades through a recursive CTE over the subtree: queued and waiting runs are canceled outright, their event waits cleared; claimed and running ones get an abort signal and finalize themselves.
Finalization
When a run reaches a terminal status it does four things beyond recording the outcome:
Rolls usage up. A parent's children bucket absorbs each child's own usage and that child's children bucket, flattened — so usage.children is the whole subtree and children.runs counts every descendant.
Settles plan steps whose attached children all succeeded.
Records unmet obligations, if it ended owing work.
Fires run-completed triggers, which is what makes automation chains possible.
Session status then re-derives (Sessions).
Errors are interrogable
A failed run's error carries more than a message: a code, a stack whose workflow.js:LINE frames index into the run's stored source, the tool name of the failing call, its journal callSeq (which joins the error to the exact journaled arguments and result), and any structured detail the tool attached — identical live and on replay. The desktop turns this into an inspector, not a red string.
Reading runs
Related
Time and Parking — wait reasons and wake sources
Scripts and the Journal — what a workflow run executes
Sessions and Threads — the conversation a run serves
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime