RunsEverything that executes is a row in one tree — and the tree is also the dispatch queue.

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

read run:<id> returns a run's journal and state through the read verb, for the agent and the user alike; GetRun, ListRuns, GetRunJournal, CancelRun and SignalRun are the protocol equivalents, and runs/<rootRunId> is a live subscription key.

Related

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime