Runs, threads, and commands
The units of work. A command starts a run, a run is one turn, and a thread is the conversation that ties runs together.
These four terms describe how work is scoped and made durable. They are backend truth: the frontend renders them but never invents them.
The run lifecycle: every channel enters through one durable command door, the Postgres event log is the source of truth, and a reply re-enters with parent_run_id.
Organization
The tenant boundary. Resources such as secrets, skills, knowledge, runs, and
integrations belong to exactly one organization. A user is different: membership
is many-to-many, so one user can belong to several organizations and acts under
one active organization per session (backend/src/db/auth-schema.ts). Org
scoping is enforced by the backend auth layer and the database, not by the
client.
Thread
One continuing conversation. Replies reuse the thread’s history, its sandbox, and its working directory. Threading is server-side: a reply stores the raw user prompt only, and the backend composes the engine’s context preamble from recent turns. Prior turns are never stuffed into a stored prompt.
Run
One agent turn inside a thread, from an accepted command to a terminal result. The runs list returns thread roots; a single thread is fetched whole when you open it.
Child sessions
An agent can spawn child work in-run through the child_session_create,
child_session_list, child_session_events, and child_session_gather
gateway tools (backend/src/knowledge/gateway/child-session-tools.ts). Each
child is a queued serial turn on the same thread: at most one command per
thread is in flight at a time (backend/src/commands/dispatch.ts), so children
run one after another, never as a parallel process tree. The timeline renders
their progress through the child.started, child.updated, and
child.completed canonical events.
Command
The durable record of a product mutation. There are exactly two durable command
kinds (backend/src/commands/repo.ts): run.create enqueues a turn, and a
reply or resume is simply a new run.create turn on the same thread carrying
parent_run_id; run.cancel is the durable record of a user stop request.
Commands are the single door: the web app (runs/routes.ts), Slack
(slack/events.ts), and scheduled automations (schedules/fire.ts) all enter
through the same acceptRunCommand lane.
A command may carry a per-organization idempotency key. When a key is
present, a retried submission observes the original command instead of starting
duplicate work, and a reused key with a different payload fingerprint is
rejected with a 409 rather than silently rerun (backend/src/db/schema.ts).
Without a key the submission is still a durable command row, just not
deduplicated.
Step
A run’s timeline is written in two lanes.
Durable steps are coarse timeline items in the steps table. A step’s kind
is one of command, file, task, or done, with a label, an optional chip,
and optional structured code_json (backend/src/db/schema.ts).
Canonical events carry everything else a timeline reader sees. They are
provider-neutral (one discriminated union covers every engine,
packages/agent-harness/src/canonical.ts) and are persisted in
canonical_events before they are published, with an immutable deliverySeq
cursor, so a reconnect replays exactly the rows a live subscriber saw
(backend/src/runs/canonical-events.ts). The kinds a timeline reader sees
include:
| Canonical event group | Examples |
|---|---|
| Messages and reasoning | message.started/delta/completed, reasoning.delta/completed |
| Tools and plans | tool.started/progress/completed, plan.updated |
| Files and artifacts | file.changed, artifact.created, artifact.delivered |
| Questions and approvals | question.requested/resolved, approval.requested/resolved |
| Child sessions | child.started/updated/completed |
| Usage | usage.updated |
Because the event log is the source of truth, the two lanes together fully describe a run and can be replayed. The UI reduces them into the session timeline.