Domain model
State machines
An execution moves through these states:blocked means the execution is paused waiting on a human approval or a
subagent. It re-enters running once no step is waiting. A step parked by a
rate limit leaves the execution running. pending means the execution has not
started.
A step moves through these states:
Determinism and replay
When an execution resumes, the kernel re-dispatches the agent. It runs again from its entry point with the same input. Every effect is intercepted before it runs:- A deterministic step ID is computed from the call’s content.
- It is looked up against the kernel’s
stepsprojection, as a point query. - If a result is recorded, it is returned and the effect does not run.
- If not, the effect runs and its outcome is recorded against that same ID.
Step identity
kindistool_call,llm_call, orlocal.targetis the tool name or model id.args_hashis a stable hash of the canonicalized arguments.occurrenceis the count of prior identical calls in this delivery attempt, so callingread_file("foo")twice yields two distinct step IDs.
{kind, target, args} along with the
dispatch_id from its webhook and gets the ID back in the decision.
Occurrence is counted per delivery attempt, under the execution lock. Claiming a
dispatch clears the count. Every attempt starts from zero, so the same effect
sequence recomputes the same IDs and short-circuits on what the last attempt
recorded.
Durability and failure
step.executing is written before the external call, and the terminal event
(step.succeeded or step.failed) after. That ordering is what lets the kernel
spot orphaned effects on recovery.
On re-dispatch the kernel finds each step in one of three states:
- Absent → run it.
- Terminal → replay the recorded outcome, and never re-invoke.
- Started only (orphan) → resolve by the step’s declared idempotency:
safe_to_retry(the default) re-invokes.at_most_oncemarks the step failed withindeterminate, and the agent’s loop decides how to reconcile.
Sessions
An execution can carry an immutablesession, which names a chain of executions
that continue one another, such as the turns of a conversation. Executions in a
session run one at a time; the rest wait in pending, with no dispatch or
delivery attempts. Different sessions run concurrently. Sessions are global to
the kernel, so executions for different agents in one session also run one at a
time.
An execution holds its session while running or blocked, so approval waits,
retry backoff, and rate-limit pauses all keep it, and deadlines include the time
spent waiting. Reaching a terminal state releases the session and starts the
oldest execution waiting in it.
When an execution in a session starts, its parent becomes the agent’s most
recently created completed execution in that session. A failed or cancelled
execution never becomes a parent. An agent completes an execution with state
alongside its output, and the next execution reads its parent’s state, or
the parent’s output when it recorded none. The kernel stores state as opaque
JSON; agents keep their conversation there in their framework’s own format, so
they need no session storage of their own.
An execution created with parent_execution_id continues that completed
execution of the same agent. It branches a conversation from an earlier turn and
leaves the original session untouched. Its session is optional, and when given
must be new, so later turns continue the branch.
Retention deletes a session’s executions only once every execution in the
session is terminal and older than the retention cutoff, so a retained
execution’s parent stays readable.
Subagents
An agent runs a subagent by creating an execution withspawned_by, which names
the parent execution and one of its executing steps, usually a tool call. The
subagent’s outcome becomes the step’s: its output is the step’s result when it
completes, and otherwise the step fails with reason subagent_failed or
subagent_cancelled.
Starting a subagent leaves the parent running, so concurrent tool calls in one
turn can each start one. Once every in-flight call is waiting, the agent
suspends. If any step still waits, the kernel blocks the parent
and releases its dispatch, so no worker holds it while its subagents run. When
the last waiting step settles, the kernel redispatches the parent and its
handler replays the recorded outcomes.
A subagent’s idempotency_key defaults to the step’s step_id, and a step
starts at most one subagent, so a safe_to_retry step that runs again finds the
subagent it already started. Only an executing step can start a subagent, so an
orphaned at_most_once step that the kernel failed as indeterminate cannot. A
subagent cannot join its parent’s session.
When a parent reaches a terminal state, its live subagents are cancelled with
reason parent_agent_terminal, and so on down the tree. A periodic sweep retries
settlement and cancellation that a crash interrupted.
Forks
A fork reruns an existing execution, up to an event sequence from its record and live after it. It can start a new session, so later turns continue it. The fork keeps the source’s input and parent, so it reads the same previous state the source read. The fork copies the source’s steps that reached a terminal outcome at or before the fork point, with their decisions, results, errors, and usage, and appends their events to the fork’s log ahead ofexecution.started. The agent reruns
from the top: copied steps replay through the normal replay path, and everything
after the fork point runs live under policy. A fork’s log is complete on its
own, so auditing and policy tests read it like any other execution.
A fork inherits the source’s execution-specific policy bundle, or uses the
agent’s bundle when the source has none. Supplying a policy_bundle overrides
the inherited policy and requires the policies:write scope. All live calls,
including at_most_once calls, follow that policy. Copied outcomes replay
without reevaluating policy.
External resources
An agent can register external state, such as a sandbox, as a resource and optionally enable checkpoints. The kernel stores bindings, checkpoint references, and coverage boundaries; captured contents stay with the provider. See Python resources or TypeScript resources for SDK usage. Session turns inherit their completed parent’s resource registrations and bindings. The SDK opens each resource and captures a baseline when checkpointing is enabled. Generations, counts, and checkpoint history belong to each execution. A live step advances the affected resource’s generation before running and ends its checkpoint coverage. Captures require the current generation and no active writer. The kernel commits captures with step outcomes; a failed capture preserves the outcome and leaves the resource uncovered. A fork point is covered when every resource registered there has matching captured state. Executions without resources are covered at every event. Coverage is advisory: forks keep the requested event regardless of coverage. For each resource, a fork selects the newest covering checkpoint, otherwise the newest earlier checkpoint, otherwise the initial environment. The agent creates a separate resource from that state; later dispatches open its existing binding. The fork’srestoration field records each selection and its coverage.
Copied outcomes replay through the fork point. At an uncovered point, they can
describe changes absent from the restored resource. An expired or missing
selected checkpoint fails resource initialization and the forked execution.
Dispatch and delivery
The kernel enqueues a dispatch (a row in thedispatches table) in the same
transaction as the event that triggers it. A background loop on every replica
claims due work with SELECT … FOR UPDATE SKIP LOCKED and POSTs to the agent’s
webhook. The enqueue sends a Postgres NOTIFY that wakes idle loops when the
transaction commits; loops also poll every 250 ms, which picks up delayed
retries and covers a dropped listener:
(execution_id, dispatch_id, dispatch_attempt) and runs each delivery once. Failed deliveries
retry with exponential backoff. Once the attempts run out, the execution fails
with dispatch_exhausted.
A replica claims no more rows than it has idle delivery workers, so it never
holds work another replica could deliver sooner. A claim leases the row for the
length of the agent’s run, renewed by heartbeat. The period is the agent’s own
lease_timeout_seconds or the kernel default, and the payload carries it so the
agent paces its heartbeat against it. A lease from a crashed replica expires, and
any dispatch loop returns it to the queue.
A reclaim cannot tell a crashed agent from a slow one, so the redelivery can run
alongside an agent that is only stalled. Each claim takes the next
dispatch_attempt, and every write an agent makes carries the attempt it was
delivered under, checked in the same transaction as the write. Work from an
attempt the kernel has replaced, or has returned to the queue, is refused with
409 lease_superseded.
Policy governance
Policy is evaluated when a step is first submitted, and skipped on a replay. A rule returnsallow, deny, or require_approval.
A rule can also carry two limits. A rate limit parks the step for a later retry
or refuses it outright, depending on the rule’s max_wait. A token budget turns
an allow into a deny or a require_approval once the execution has spent its
max_tokens.
A rule can match on history: the steps policy allowed earlier in the execution
or session. History is read under the execution lock, and a session runs one
execution at a time, so a decision sees every step allowed before it.
Policy only gates a call. It never rewrites the request. See
Policy.
Storage and high availability
Thesteps table is a projection of the event log, written in the same
transaction as its events. Replay lookups are therefore read-after-write
consistent, and the projection never lags.
An llm_call request carries the conversation so far, and an execution’s
state usually carries it again, so consecutive payloads mostly repeat each
other. Postgres stores both split into content-defined chunks, keyed by hash in
payload_chunks, so a repeated chunk is stored once across every step and
execution. The retention sweep deletes chunks that nothing references and that
went unused for a day.
The HTTP API is stateless. Any replica serves any request and dispatches any
execution, with no connection registry or sticky routing. Two replicas can
therefore touch one execution at once. Every path that mutates an execution runs
in one transaction that first takes a transaction-scoped Postgres advisory lock
on its id, so those writes serialize. Postgres releases the lock when the
transaction commits or rolls back, and a lost connection rolls back the work
done under it.
Every read and write in a locked path, including policy lookups and rate
limiting, uses that transaction’s connection, so a request holds at most one
connection. Policy evaluation runs inside the transaction, so a step that
reaches a judge rule keeps it open for the judge call.
Each replica queues requests for the same execution in memory before acquiring a
database connection and the advisory lock. Only one request per execution per
replica holds a connection; local waiters hold none and leave the queue when
their contexts are cancelled. The advisory lock coordinates writes across
replicas. The waiting queue is transient; execution state remains in Postgres.
An SSE subscriber stays on the replica that accepted it. Deltas are broadcast to
every replica, so nothing needs routing. See Live streaming.
Singleton background work (approval expiry, execution deadlines, cleanup) uses a
second advisory lock, taken on one fixed key rather than per execution. A replica
that fails to take it skips the tick instead of waiting for it.
See Agents, Tools and effects, Policy, and
Events for the details behind each of these.