Skip to content

Research review 1 paper

Pi Harness v2: Crash Recovery and Tool Replay

What Pi preserves across a crash, when tool calls can replay, and where exactly-once execution remains impossible.

By AgentsPulse Editorial Team 17 min read Published September 7, 2026 Updated September 7, 2026
Review scope Pi Agent · AgentHarness · Crash recovery

Primary papers, system diagrams, benchmarks, and stated limitations.

Pi Agent Harness crash recovery across saved session state, effect boundaries, and parallel tool results

Pi’s historical Harness v2 proposal evolved into the current unified AgentHarness. This review uses Harness v2 for the design lineage, while distinguishing the older operation-log proposal from the current durable operation-state implementation.

Short answer: Pi can preserve the conversation, branch position, operation state, staged tool outcomes, and settled usage when a persistent backend is used. It cannot always prove whether an external side effect happened during the gap between invoking a tool and committing its outcome. Recovery therefore replays only calls whose stored policy and current tool declaration are safe, unless the batch was cancelled.

Recovery question What the harness can say
What survives a process restart? Persistent Session data: conversation entries, branch tips, operation state, pending outcomes, and settled usage.
What does not survive? The process-local Drive; Memory-backed Sessions also disappear with the process.
Can an unfinished tool run again? Only when both the stored replay policy and current declaration are safe, and the batch is not cancelled.
Can it guarantee exactly-once external effects? No. A crash after the action but before outcome commit leaves the external result unknown.
Who resumes execution? The host reopens the Session and schedules a new Drive.

For the broader runtime boundary around state, tools, retries, and observability, see The Agent Framework Is Not the Runtime.

Suppose a coding agent is deleting a directory when its process crashes. After restarting, it can read the conversation and find the tool call. But did the deletion start? Did it finish? Would running the call again repeat an action that already happened?

These questions motivated Pi’s harness v2 design: make ongoing agent work recoverable beyond the conversation itself. The design evolved into the current AgentHarness, which stores the state of an operation and uses it to decide how work can continue. We will follow a tool call through that process, from the first saved intent to the result that eventually appears in the conversation. 1

The difficult part is the interval between performing an external action and saving its outcome. A runtime can preserve the evidence around that interval. It cannot always determine what happened inside it.

What survives a restart?

Pi separates the history of a conversation from the state of the work being performed over it. Four objects make that separation easier to follow:

  • A Session holds the shared conversation, execution data and usage records.
  • A Branch names a path through the conversation’s entry tree. Its tip identifies the current end of that path.
  • An AgentLane adds model and tool configuration, an inbox and at most one active operation to a branch.
  • An Operation represents accepted work, such as an agent run, with its own identity and current state.

A Drive is the process-local execution pass that advances an operation. With a persistent storage backend, the operation can survive the loss of its Drive. Loading the Session restores the saved state; the host then schedules a new drive to continue it. 2

Figure 1 shows two branches sharing the same prefix. The branch tips and operations are saved data. The Drives below them belong to the running process.

A tree with a shared prefix and two branch tips. Each branch has its own lane and operation inside the Session; a matching process-local Drive sits outside the saved state.Select diagram to enlarge

Figure 1. Branches share conversation entries while their lanes maintain separate operations. With persistent storage, the data inside the Session survives process loss; the Drives must be recreated. The host assigns one writable owner to the whole Session.

The Session stores three kinds of data with different lifetimes. Conversation entries are immutable nodes linked to their parents. Bound values and lists hold changing information: branch tips, current operation state, pending output and inbox contents. The usage ledger retains settled accounting records independently of operation cleanup.

When an operation finishes, a terminal transaction removes its temporary state and retains an immutable result record. Conversation entries and usage remain. Conversation compaction changes the context presented to the model; it does not erase the underlying history. 3

How Harness v2 evolved

The original v2 proposal and the current harness recover work differently. In the early design, each lane had an operation log. Recovery reduced those records and resolved unfinished intents.

The current design saves the operation’s complete current state. Each durable transition replaces it atomically, along with the entries, values, lists and usage rows needed to make that new state true. After a restart, the driver reads the state’s at field and enters the corresponding procedure.

This makes the restart point explicit. If a transition publishes a response and advances execution, both changes commit together. Recovery does not have to guess which step follows from a partially updated transcript.

The JSONL backend still reads physical records to reconstruct its storage maps when opening a file. That is how it decodes the saved data; the harness then continues from the resulting operation state. The two uses of a log should not be confused. 4

The implementation reached this point through several distinct stages. Dates below follow the 2026 commit history in UTC.

  • July 29 — The v2 design appears. The first v2 document organizes Sessions around a conversation tree, lanes and per-lane operation logs, with recovery expressed as reduction.

  • August 3 — The effects design is selected. The project chooses the effects variant over the generator variant. Explicit intents, outcomes and crash-site behavior become the chosen basis for v2.

  • August 4–5 — Storage and recovery foundations land. In-memory sessions arrive first, followed by a JSONL v4 backend and the durable lane reducer. These are early implementations of the log-based design.

  • August 11 — The specification is consolidated. The design documents merge into harness.md, giving the storage, Session and runtime contracts a shared reference.

  • August 14 — The first runtime gains execution. The earlier runtime gains a minimal model run and tool execution. This is the implementation replaced by runtime2 in the next stage.

  • August 17–18 — The runtime is replaced, then acceptance is rebuilt. WP00 switches the public factory to runtime2 and removes the old runtime while execution is still incomplete. Atomic acceptance and coherent observation follow as a separate step.

  • August 26 — The durable execution graph and public lane API land. The direct driver advances saved state through generation, tools, waits, structural work and cancellation. The public lane operations expose that execution flow.

  • August 31 — SQLite ownership moves to the host. WP07 removes the backend ownership mechanism and adds read-only snapshots for forking live source Sessions. The host remains responsible for assigning the writable owner.

  • September 1 — Forks gain explicit named-branch selection. The fork contract requires a scope and source branch. This is the first slice of WP08; bounded-memory copies remain unfinished.

  • September 2 — Settled tools stay visible until placement. WP09 closes a display gap: a completed tool result now remains in the lane snapshot while waiting for its position in the conversation.

Between intent and outcome

Atomic transactions can keep local state consistent, but a tool may act on a filesystem or remote service outside that transaction. Pi therefore separates an effect into four steps:

  1. Prepare the inputs.
  2. Commit the intent, including the arguments and reserved output identities.
  3. Perform the external action.
  4. Commit the outcome and the next operation state together.

The intent survives a crash. So does a committed outcome. The gap between them can leave the external result unknown. Consider the two paths in Figure 2: one process dies before performing the action; another dies after the action but before saving its result. Both can reopen with the same saved intent. 5

An intent forks into two possible histories: crash before the action, or action followed by crash. Both converge on the same saved state with an unknown outcome. A separately committed outcome can be kept.Select diagram to enlarge

Figure 2. Different external histories can leave identical local evidence. A committed outcome removes this particular uncertainty; an intent alone does not.

This is why the harness cannot promise exactly-once external effects. Saving a marker before a call does not prove the call happened, and saving one afterward leaves a window in which the action may have happened without that marker. Hooks with side effects face a related issue: if their consuming transaction did not commit, they may run again and need their own idempotency discipline.

Which tools can run again?

Return to the directory deletion. Suppose the tool’s stored policy is replay: "never". It starts work and may save progress, then crashes before committing a final result.

On recovery, the harness preserves the latest durable progress snapshot, if one exists, and synthesizes an interruption result. It leaves the external outcome unknown. This is useful information for the next agent step: some work may have completed, but the transcript cannot claim that the deletion succeeded. Skipping a second execution also does not undo the first one. 6

For an orphaned call to run again, both its stored policy and the current tool declaration must be safe, and the batch must not be cancelled. Checking the current declaration matters: the tool implementation or its replay policy may have changed since the original call was accepted. The recovery condition checks all three conditions.

A safe declaration is the tool author’s contract. The harness does not prove that an arbitrary action is idempotent. If the declaration is unsafe or the implementation is missing, recovery records interruption rather than invoking the tool. A restored cancelled batch follows cancellation reconciliation instead of ordinary replay.

When completion order differs from conversation order

Now suppose the assistant requests tools A and B in that order, and the runtime executes them in parallel. B finishes first. Its result cannot yet appear as the next tool entry because A precedes it, but there is no reason to leave B’s completed work unsaved.

Pi first stages B’s outcome at a pending address and marks it outcome_ready. As earlier calls become ready, the runtime materializes the contiguous ready prefix into the conversation. Saving a result and placing it in conversation order are separate steps. 7

Figure 3 adds a crash while A is still running. Assume A is eligible for safe replay: its stored and current declarations are safe, and the batch is not cancelled. After restart, A can run again while B’s saved outcome is retained.

Before the crash, B finishes and is staged while A is still running. After restart, only A safely replays. A and the retained B result then enter the conversation in source order.Select diagram to enlarge

Figure 3. B waits for its place in the conversation without losing its result. In this example only A is replayed; if A were unsafe to replay, its interruption result could instead settle that position. The trace illustrates ordering, not measured durations.

The staging commit is the decisive boundary for B. Once its complete outcome is durable, recovery can place it without resolving or executing that tool again. The repository’s tool runtime tests cover out-of-order staging and materializing already saved outcomes.

An interrupted model response

A provider stream has a different recovery policy. If the process dies during generation, the harness reconstructs the latest committed assistant-frame prefix and settles a synthetic error response under the reserved identifiers. Partial tool calls from that response never execute. 8

Recovery does not reconnect to the old stream. A retry policy may subsequently schedule a new numbered attempt, which is a new provider request. This separates preserving the partial response from deciding whether to try again.

The synthetic settlement records zero usage. That value cannot tell us what the provider charged for the interrupted request: the runtime may never have received the final accounting. The append-only ledger preserves settled usage and accepts caller-supplied adjustments so that accounting can be reconciled separately. 9

Who continues the work?

Saving work and running it are separate responsibilities in the API as well. accept persists an operation without invoking a hook, provider or tool. drive installs or joins the lane-owned execution pass. A host can therefore accept work before a worker is ready and schedule execution later. 10

The separation is visible in these consecutive AgentLane declarations:

getResult(operationId: string, context: Context): Promise<OperationResultRecord | undefined>;
accept(request: OperationRequest, context: Context): Promise<OperationAdmissionResult>;
drive(options: DriveOptions, context: Context): Promise<DriveResult>;
requestAbort(operationId: string, context: Context): Promise<AbortRequestResult>;
inspectExecution(context: Context): Promise<LaneExecutionInfo>;

TypeScript interface excerpt from AgentLane, MIT licensed.

Several events that look like “stop” to a user have different effects:

  • A caller cancels its Context after Drive installation. That caller stops observing; the operation is not durably cancelled. Other callers can still observe the same pass.
  • The host requests an abort. requestAbort(operationId) commits cancellation for the matching operation. A drive performs the remaining reconciliation; an old operation ID cannot cancel newer work.
  • The harness closes. New mutations are sealed and admitted mutations drain. Close writes neither cancellation nor terminal state.
  • The Session reopens. Saved state is restored, but execution waits for a later drive.

Cancellation before Drive installation starts no work. These lifecycle rules let the host distinguish a lost connection from an explicit stop request. They also make restart scheduling and single-writer Session ownership part of the host’s job. 11

Implementation progress

At the September 7, 2026 source snapshot, the work-package list marks WP00–WP07 and WP09 complete. WP08 is still in progress. The numbers identify work packages; they do not imply a strict completion order.

Package Status Result or remaining goal
WP00 Complete Replace runtime1 and switch the public factory to runtime2.
WP01 Complete Introduce bound values and lists across Session and storage backends.
WP02 Complete Accept work atomically and capture coherent lane observations.
WP03 Complete Remove wall-clock drive deadlines and the non-durable yielded outcome.
WP04 Complete Publish committed mutations and their events through the Session.
WP05 Complete Finish the durable operation graph, cancellation, results and public lane API.
WP06 Complete Separate Session, Branch, AgentLane and AgentHarness responsibilities.
WP07 Complete Align SQLite with host ownership and support live-source forks.
WP08 In progress Add named-branch/tree semantics and bounded-memory fork copies.
WP09 Complete Keep settled tool results visible until conversation placement.

WP08 has already added explicit scope and branch selection, ancestry checks, configured-lane enforcement and scalar-value fork rules. List copying, sequence preservation, direct Memory construction and bounded JSONL/SQLite transfer still remain. Current slice.

Work still open

The implementation status and roadmap identify several remaining gaps:

  • JSONL storage cleanup. Snapshot compaction is specified but unimplemented, so superseded operation state and deleted pending payloads can remain in the file. Conversation compaction does not reclaim those physical bytes.
  • Session-wide observation and search. Lane watches exist, but watchSession still throws SliceNotImplemented. Search has a design and a conflicting public API skeleton, with no implementation.
  • Telemetry. The span vocabulary is declared, while production instrumentation starts only the tool-hook span. RPC trace propagation remains open.
  • Remote Session access. The product uses process-local Sessions and routed semantic services. Some specification text still requires a raw remote mutation interface; that contract needs a decision before implementation.
  • Contract and storage follow-up. Operation status and abort event ordering need reconciliation. SQLite branch divergence can still copy an entire uncompacted history. Format 4 remains in development, with schema migration machinery reserved for a future stabilized-format change.

Recovery also depends on the chosen backend. Memory storage loses its state with the process. The JSONL contract gives resolved commits process-crash durability, with no fsync promise. Storage contract.

For an integration, start with one real interruption point. Identify what committed, which external outcome is still unknown, whether replay is permitted, and who schedules the next drive. Those four questions connect the saved data to the behavior the application will actually have after a restart.

For a broader comparison of the two tools, see DeepSeek Harness vs Pi Agent.

Sources and version

The mechanisms above follow Pi’s AgentHarness specification and implementation at revision 9767ba2, reviewed on September 7, 2026. The historical v2 proposal uses the earlier log-based recovery model. For changes after this snapshot, consult the current harness documentation and the Pi repository. Here, “v2” names a design stage in the project’s history, not a package release, and it does not refer to third-party packages that also use “harness” in their names.