The executor contract, the durable layer, the live channel for interventions, transcripts, the three harnesses.
An Attempt goes through six layers. All of them run inside the orchestrator process except the children. The table lists them from the engine down.
| Layer | Code | Does |
|---|---|---|
| Run fiber | run-driver.ts:288 | Starts or reattaches the Attempt, gives the executor a transcript sink, records the outcome. |
StepExecutor |
scheduler.ts:163 | Builds a CheckoutRequest from the Run and the settings, then calls prepare and launch in one scope. |
Executor |
executor.ts:172 | Checkout, credentials, guard, salvage, harvest on one Worker kind. |
| Durable session | durable-session.ts:73 | Shared by every Worker kind: runs the parent task, stops it, routes interventions, implements isLive. |
AttemptRuntime |
attempt-runtime.ts:70, implemented in runtime.ts:36 | The Pi parent as a pi-durable task, its subagent tool, the envelope relay. |
| Child backends | children.ts | Claude Code, Codex, or Pi, on the Worker the executor chose. |
The Effect workflow and cluster modules of ADR 0003 no longer exist. A migration dropped their tables (20261011153856_drop_workflow_tables), and the Run lifecycle is the fiber described in Run lifecycle.
One Attempt end to end
Section titled “One Attempt end to end”sequenceDiagram participant F as Run fiber participant S as StepExecutor participant X as Executor participant P as Pi parent participant C as Child F->>F: beginAttempt F->>S: execute S->>X: prepare Note over X: credentials,<br/>worktree S->>X: launch X->>P: execute P->>C: subagent C-->>P: session events P-->>F: envelopes C-->>P: result P-->>X: report Note over X: harvest,<br/>recordResult Note over X: release the<br/>checkout X-->>F: StepResult F->>F: finishAttempt
The fiber begins the Attempt and gives StepExecutor a sink for envelopes. StepExecutor calls the executor to prepare the checkout with new credentials, then to launch the Attempt, which runs the Pi parent task through AttemptRuntime.execute. The parent starts children with its subagent tool. Every envelope from the parent and its children goes through the runaway guard to the fiber’s sink, which appends it to the transcript. When the parent reports, the executor harvests the checkout, stores the result with the task, and releases the checkout before launch returns, so the worktree is removed or its salvage is recorded before the fiber calls finishAttempt.
The executor contract
Section titled “The executor contract”Executor (executor.ts:172) is the interface that ADR 0006 defines:
| Operation | Does |
|---|---|
kind |
The Worker kind that the executor handles. StepExecutor refuses a configured Worker of another kind. |
preflight(worker, brains) |
Verifies Git and each rostered brain where the Worker runs children. Doesn’t install anything. |
prepare(request) |
Needs a Scope. Checks the Attempt’s brain again, gets repository access, creates or reattaches the checkout, and returns a PreparedAttempt with workspace and launch(io). |
cancel(attemptId) |
Stops preparation, or cancels the live parent task, waits a grace period, then interrupts. |
intervene(attemptId, intervention) |
Delivers an operator action to the live session. |
isLive, outcome |
Tell a restarted engine whether to reattach an open Attempt or finish it with a stored result. |
recover(attempt, record) |
Salvages or removes a checkout that an earlier process left. |
/** Prepares isolated checkouts, runs Attempts on a Worker, and releases their resources (ADR 0006). */export class Executor extends Context.Service< Executor, { /** Reconciles an old checkout before new Attempts start; records its salvage before releasing any work. */ readonly recover: <E>( attempt: AttemptRecovery, record: (salvage: AttemptSalvage) => Effect.Effect<void, E>, ) => Effect.Effect<void, E | ExecutorError>; readonly kind: WorkerKind; /** * Whether the Attempt's parent task survived an orchestrator restart and can be reattached: * it still runs, or it reported success before its checkout was harvested. */ readonly isLive: (attemptId: AttemptId) => Effect.Effect<boolean, AttemptFailure>; /** * The result an Attempt's finished parent task left in durable storage before an * orchestrator restart: the harvested result, or the parent's failure. None when * nothing was stored, or when `isLive` says the Attempt is reattached instead. */ readonly outcome: ( attemptId: AttemptId, ) => Effect.Effect<Option.Option<Result.Result<StepResult, AttemptFailure>>, AttemptFailure>; /** Verifies Git and each rostered brain where the Worker runs children; never installs tools or logs in. */ readonly preflight: ( worker: WorkerName, brains: ReadonlyArray<BrainSpec>, ) => Effect.Effect<void, ExecutorError>; /** * Verifies the assigned brain before acquiring the Attempt in the caller's scope. Clone, fetch, and lock waits * remain interruptible; partial preparation releases everything already acquired * before reporting failure. Each request identifies a fresh Attempt, or a live one * to reattach when `reattach` is set. */ readonly prepare: ( request: CheckoutRequest, ) => Effect.Effect<PreparedAttempt, ExecutorError, Scope.Scope>; /** * Stops preparation and releases partial resources, or cancels the live * parent task with a grace period before forced termination. A canceled prepared Attempt * cannot launch. Repeated cancellation after release is a no-op. */ readonly cancel: (attemptId: AttemptId) => Effect.Effect<void, ExecutorError>; /** Delivers an attributed operator action to the active Attempt's live session. */ readonly intervene: ( attemptId: AttemptId, intervention: AttemptChannel.Intervention, ) => Effect.Effect<void, AttemptFailure>;CheckoutRequest (executor.ts:93) has the Attempt id, Worker, repository, revision, Event payload, task text, Agent, parent model and effort, maxChildren, the two optional ceilings, the salvage ref, the salvage to resume from, and reattach. The revision is HEAD for issue and schedule Runs (trigger-submission.ts:17) and the head commit for pull request Runs (loop-evaluation.ts:78).
Preparing the checkout
Section titled “Preparing the checkout”provisionWorktree (provision.ts:233) is the same for every Worker kind. Under a per-repository lock it clones into a staging directory (git clone --bare --filter=blob:none) and renames it to the cache, or updates the cache’s remote URL. Then it fetches the revision, records FETCH_HEAD as the base commit, fetches the previous Attempt’s salvage when the request resumes one, and runs git worktree add --detach. The worktree is <worker directory>/attempts/<attempt id> and the cache is <worker directory>/repos/<repository>.git, where both names replace every character other than letters, digits, _, ., and - with _ (provision.ts:21).
Launch, guard, release
Section titled “Launch, guard, release”launch (prepared-attempt.ts) wraps the sink with the runaway guard (prepared-attempt.ts:114) and races the parent task against the guard’s wall-clock trip. The guard (guard.ts:139) arms only the ceilings that usine.yaml sets. The token meter sums the highest SessionTokensUpdated count per session (guard.ts:32). The wall clock pauses while any child is held (guard.ts:64). At 80% of either ceiling (guard.ts:8) the guard sends the parent one Steer from operator engine (prepared-attempt.ts:119). When a ceiling is reached, the guard stops the Attempt and fails it with RunawayFailure.
When the parent reports success, the executor harvests the checkout with harvestFacts (harvest.ts:135): the commits from the base commit to HEAD, then the pull requests whose head is the final commit, from gh api repos/<repo>/commits/<head>/pulls. Pull request discovery has a ten-second timeout and never fails the Attempt. StepResult also allows a Comment fact (result.ts:20), but no code produces one.
If the Attempt ends any other way, salvageWorktree (salvage.ts:56) runs before the worktree is removed. Uncommitted changes become a wip(usine): salvage attempt <id> commit, and git push --force pushes HEAD to usine/salvage/<run>/<number>. If the push fails, the executor keeps the worktree on the Worker and records its path as Retained. The next Attempt of the Run checks out the newest pushed salvage (ADR 0013, amendment of 2026-09-18).
The durable layer
Section titled “The durable layer”AttemptRuntime (attempt-runtime.ts:70) runs the Pi parent as a pi-durable task called usine.attempt (task.ts:46). The task is stored in .usine/durable.sqlite (storage-path.ts:5), which @earendil-works/pi-durable 1.1.0 opens. Building the layer (live.ts:74) resumes every unfinished task (runtime.ts:50).
| Operation | Does |
|---|---|
execute(request, io) |
Creates the task, or finds it after a restart, forwards its activity to io, and waits for the parent’s report. |
isLive, finished |
Whether the task is still open, and what a finished task left: ParentReported, ResultRecorded, or ParentFailed. |
recordResult |
Stores the harvested StepResult with the task. |
request |
The stored request of a task, read for a reattachment. |
intervene |
Applies an operator action to the parent or a child. |
cancel |
Aborts children, then the parent conversation. |
The stored request contains the RunSpec, the Worker, and the worktree path (attempt-runtime.ts:14). The task doesn’t store the child host or the environment; every execute call passes them again, because the environment contains the Attempt’s credential configuration (ADR 0011).
The parent’s first turn is the prompt from assemblePrompt (prompt.ts:58): the repository, the salvage it resumes from, the Event payload as JSON, the Agent persona, the task text, completion rules, and GitHub rules (review with COMMENT only, and end every comment, issue, pull request, and review with the signature line from githubSignature, which ends in usine agent: <name>, prompt.ts:28). The parent’s only tool is subagent (subagent.ts:31). Each call starts one child of the Agent’s harness in the checkout. The tool is declared replay: "unsafe" (subagent.ts:271): after a restart, the interrupted call ends with the output committed so far, and the parent decides whether to delegate again. A final answer that starts with [usine:blocked] fails the Attempt with AgentFailure (task.ts:58). usine enforces max_children with the children count in the Attempt record; one more spawn fails the Attempt with ChildSessionBudgetFailure.
The parent’s billing pool depends on its provider: a model from openai-codex uses the ChatGPT plan and every other model uses API keys (authentication.ts:30, ADR 0021).
Reattach after a restart
Section titled “Reattach after a restart”-
RunDriver.openAttempt(run-driver.ts:234) callsisLive. The durable session returns true when the task is live, or when it finished withParentReportedand its checkout isn’t harvested yet (durable-session.ts:396). -
beginAttemptreturnsReattachAttempt, with the same Attempt id and number, and no retry used (lifecycle.ts:132). -
preparewithreattachreads the stored RunSpec, issues new credentials, and reuses the existing worktree. -
executefinds the task in storage and continues its relay from the stored sequence.
An Attempt whose task no longer exists fails with an InfraFailure, and the next Attempt starts from the newest pushed salvage. If a launch is interrupted and no one canceled it, the process is shutting down, so the task stays live and the worktree stays on disk (durable-session.ts:193).
The Attempt channel and the transcript
Section titled “The Attempt channel and the transcript”channel.ts defines the envelopes, at protocol version 7. Outbound payloads are Started, SessionEvent, Final, Failed, Interrupted, InterventionApplied, and InterventionRejected. Inbound payloads are Steer, Interrupt, OperatorMessage, Release, and Cancel, each with a target: the parent or a child session id. admit (channel.ts:221) enforces the order: one Started or Failed first, contiguous sequences, nothing after the terminal envelope.
/** Nonterminal operator controls; cancellation is an Attempt operation. */export const OperatorAction = Schema.Union([Steer, Interrupt, OperatorMessage, Release]);export type OperatorAction = typeof OperatorAction.Type;export const InboundPayload = Schema.Union([OperatorAction, Cancel]);export type InboundPayload = typeof InboundPayload.Type;// ...export const OutboundPayload = Schema.Union([ Started, SessionEvent, Final, Failed, Interrupted, InterventionApplied, InterventionRejected,]);export type OutboundPayload = typeof OutboundPayload.Type;relayAttempt (relay.ts:171) sends Started, then the parent’s committed entries and the children’s session events in the order they happened, then one terminal envelope. The next sequence numbers are in the durable Attempt record, so a relay that restarts continues the same sequence.
The sink that the Run fiber gives to the executor calls RunStore.appendTranscript (store.ts:546). Each envelope becomes one row of usine_attempt_transcripts. A unique index on Attempt, direction, and channel sequence keeps the first copy when a reattached relay sends an envelope again. A Started envelope writes the parent model the SDK chose to the Attempt row, and an InterventionApplied envelope sets operator_intervened. Envelopes of protocol versions 3 and 4 (transcript.ts:21) and 5 and 6 (transcript.ts:43) still decode.
flowchart TB
child["Child: Claude Code, Codex, Pi"]
parent["Pi parent task, pi-durable"]
guard["Runaway guard sink<br/>executor/src/guard.ts"]
store[("RunStore.appendTranscript<br/>usine_attempt_transcripts")]
reader["RunReader.transcript<br/>GET /api/runs/:runId"]
drawer["Run drawer<br/>apps/web/src/run-drawer.tsx"]
child -- "normalized AgentSessionEvent" --> parent
parent -- "relayAttempt: envelopes in order" --> guard
guard -- "meters tokens, tracks Holds" --> store
store --> reader
reader -- "newest 500 rows per Attempt,<br/>then the tail" --> drawer
The operator’s first read returns the newest 500 rows of each Attempt (projection.ts:37, projection.ts:192), later reads return the rows after a sequence, at most 500 rows across all Attempts (projection.ts:39), and the drawer loads earlier pages of one Attempt on request. The drawer polls every second while the Run can be canceled (run-drawer.tsx:91).
The live channel for interventions
Section titled “The live channel for interventions”Operator input never becomes an Event (ADR 0018). It takes this path:
sequenceDiagram participant O as Operator participant E as RunEngine participant D as Durable session participant R as AttemptRuntime Note over O: POST intervene,<br/>attributed O->>E: intervene Note over E: Running, latest<br/>open Attempt? E->>D: intervene Note over D: live session<br/>in this process? D->>R: intervene Note over R: record inbound,<br/>apply to target R-->>E: Applied or<br/>Rejected
-
POST /api/runs/:runId/intervenewith the Attempt id, the target, and the action. -
Operator.intervene(operator.ts:471) attributes it tooperatorfromusine.yaml, or tolocal operator. -
RunEngine.intervene(engine.ts:120) refuses unless the Run isRunningand the Attempt is its latest open one. -
The executor’s durable session (durable-session.ts:312) refuses with
AgentFailurewhen the Attempt has no live session in this process (for example, before a reattachment). -
AttemptRuntime.intervene(runtime.ts:305) records the inbound envelope, applies the action, and recordsInterventionAppliedorInterventionRejected. Both go to the transcript through the same sink as the other envelopes.
On the parent, Interrupt aborts the current turn and marks the parent paused in its HoldDoc. An OperatorMessage to a paused parent resumes it with the message, and one to a busy parent is sent as a steer (runtime.ts:225). Release resumes a paused parent. On a child, an OperatorMessage places a Hold and Release ends it (runtime.ts:205). The subagent call waits for the Hold to end before it returns the held child’s result to the parent (subagent.ts:223).
Harnesses
Section titled “Harnesses”| Harness | Driven by | Where it runs | Preflight | Pool |
|---|---|---|---|---|
| Claude Code | @anthropic-ai/claude-agent-sdk 0.3.261, executable claude, permissionMode: "bypassPermissions", user settings only (claude.ts:183) |
Locally, or one SSH command with a pid file through spawnClaudeCodeProcess (claude.ts:160) |
claude --version; claude auth status --json must report a claude.ai first-party login (preflight.ts:74) |
Claude plan |
| Codex | codex app-server JSON-RPC over stdio, approvalPolicy: "never", sandbox danger-full-access (codex.ts:132) |
Locally, or one SSH command with a pid file (app-server.ts:121) | codex --version must be 0.144.0 or 0.153.4; account check through the app-server (preflight.ts:20, preflight.ts:51) |
ChatGPT plan |
| Pi | @earendil-works/pi-coding-agent 1.1.0 in the orchestrator process |
Local Workers only; it refuses a remote host (pi.ts:69) | Starts a session to check the model and its API-key authentication (preflight.ts:86) | API keys |
childDispatcherLayer picks the backend from the Agent’s harness (children.ts:31). Every child gets only the Attempt’s environment, with no variables from the orchestrator’s own environment (runtime.ts:38). On the local Worker that is HOME, LANG, LC_ALL, PATH, SHELL, TMPDIR, USER, the credential variables, and TURBO_CACHE_DIR (local.ts:72, local.ts:280). On a remote Worker, the variables are added to the machine’s own environment. Before a remote checkout is kept or removed, CHILD_KILL_SCRIPT stops every process listed in a <worktree>.child-*.pid file (child-host.ts:106), because the exit of an SSH client doesn’t mean that its remote command stopped.
Pi children on a local Worker run their tools in the orchestrator process. Their grep and find tools inherit the orchestrator’s environment (#181).