> ## Content Index
> Fetch the complete content index at: https://varops.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to let a Pi Durable AI agent resume after a crash without repeating a half-finished deploy
- URL: https://varops.com/how-to-let-a-pi-durable-ai-agent-resume-after-a-crash-without-repeating-a-half-finished-deploy/
- Published: 2026-10-02T12:58:00.000Z
- Updated: 2026-10-02T12:58:00.000Z
- Description: A kill -9 two seconds into a Pi Durable run: the search ran again, the half-finished deploy did not. The only setup the harness asks for is one word next to each tool.
- Author: Gritt Scott
- Tags: Skill Issue

*Pi Durable shipped on 1 October with the promise that a crashed agent picks up where it stopped.* [***Gritt Scott***](https://varops.com/columnist/gritt/) *cares about one question inside that promise: what happens to the deploy that was halfway out the door when the process died. VarOps hard-killed a scripted run two seconds in to find out. The search ran again. The deploy was reported to the model and never repeated. The setup the harness asks for is a list of tools with one word next to each, and the piece hands over that list. —* [*Muximus*](https://varops.com/columnist/muximus/)

---

Earendil shipped Pi Durable on 1 October, [alongside Pi 1.0](https://varops.com/the-model-sets-the-success-rate-the-harness-sets-the-bill-heres-where-the-money-goes/): an MIT-licensed, experimental package that checkpoints every step of an agent run, so a crashed process picks up where it stopped instead of starting over. An interrupted tool call reruns only if that tool was declared safe to run twice. Every other interrupted call is reported to the model and left alone.

The verdict goes first, because it is short. For a team that has held back a long-running internal agent - a Slack triage bot, a multi-day research job, an ops assistant - because a crash mid-call could repeat a deploy or a charge, this is worth an afternoon. The setup it asks for is not infrastructure. It is a list of the agent's tools with one word next to each.

VarOps ran Pi Durable 1.0.0 against a hard `kill -9` on a scripted test with no model API key. The interrupted search ran again, and the interrupted deploy did not. VarOps did not run Earendil's own demo; the route to it is below, with what to watch for.

## How the resume works

Earendil's description is tight enough to quote whole: "every step of a run is a task that stores a checkpoint before it moves on. If the process dies, a new process opens the same storage, finds the unfinished tasks, and continues each one from its last checkpoint."

Model requests and tool calls get different treatment. A model request that was cut off is sent again, and the partial answer stays in the transcript marked as aborted. A tool call can change something outside the agent, so it gets more care. Before any tool runs, Pi Durable stores its intent: which tool, which arguments, and whether it may be rerun. On recovery, a call marked safe runs again. Anything else gets an error result reading "Tool deploy was interrupted and may have partially run", along with whatever output was saved before the crash, and the model decides what to do next.

The client side is covered too. A submission sent with a `requestId` is exactly-once, so a client that retries after a crash gets the original submission back instead of starting a second job.

## The one decision it hands back: which tools are safe to run twice

Whether a crashed agent repeats a dangerous action is a property of each tool, and somebody on the team has to write it down. Pi Durable makes it one field on the tool definition:

```typescript
const searchIssues = defineTool({
    name: "search_issues",
    replay: "safe", // only reads, so a rerun after a crash is fine
    // ...
});

const deploy = defineTool({
    name: "deploy",
    // No replay: a deploy interrupted by a crash is reported to the model,
    // never repeated.
    // ...
});

```

That example is Earendil's. The rule in the 1.0.0 source is strict in both directions. The default is `unsafe`, so a tool nobody classified is never repeated. Recovery reruns a call only when the declaration stored at call time and the tool's current declaration both say `safe`, so marking a tool safe after a crash does not retroactively rerun the call that was cut off.

So the real setup work is an inventory. For each tool the agent can call, ask one question: if this ran halfway and then ran again from the start, would anyone notice?

- **Safe to rerun:** reads, searches, lookups, anything that only fetches.
- **Not safe:** deploys, payments, sent emails or messages, ticket and CRM writes, file writes and edits, shell commands with side effects.
- **Unsafe, but fixable:** a side effect whose downstream system accepts an idempotency key. Earendil's payment example passes one in a task phase, not a tool, so that "if a crash reruns this phase, the card is only charged once." The same principle should carry to a tool. If the deploy pipeline or the ticketing API dedupes on a key, the tool can be made safe instead of left out.

Two more controls sit on top of the inventory. Each conversation can get its own tool set - Earendil's example lets a Slack thread search but removes `deploy` from it entirely. And a hook can put [an approval step in front of a dangerous call](https://varops.com/how-to-decide-where-claude-codes-auto-mode-belongs-by-the-blast-radius-of-a-wrong-call/) and store the answer, so a restart does not ask the approver twice.

### The bundled tools default to "interrupted", read included

Pi Durable ships four tools - `read`, `write`, `edit` and `bash` \- as a `CodingTools` extension, which nothing installs automatically. This only matters to a team that installs it. In the 1.0.0 source none of the four sets `replay`, and `defineTool()` adds nothing, so all four fall back to unsafe. After a crash, an interrupted `read` comes back to the model as interrupted, exactly like an interrupted `bash`.

That errs toward caution: nothing gets repeated that was not meant to be. It was first raised in GitHub issue #10320, filed by an account that describes itself as an AI agent working from a static read of the code. The GitHub Actions bot closed it as "not planned" eight seconds after it was opened, without triage. The issue has two comments, which VarOps could not read.

VarOps checked the claim against the published 1.0.0 source, and it holds. It was not checked by running the harness, and whether Earendil will change the default is unclear. Either way the move is the same: classify the bundled tools along with your own. Marking `read` safe is one line, `{ ...createReadTool(), replay: "safe" as const }`, which VarOps confirmed installs and type-checks. Leave `bash` alone.

## What VarOps saw on a hard kill

Pi Durable's package README says most of its examples run on a faux model provider, with no API key. That made a crash test cheap. VarOps cloned the repo at the `v1.0.0` tag and wrote a short script modeled on the package's own tool-calling example, `test/examples/17-coding-tools.ts`. One model turn makes three parallel tool calls: `search("weather")` (100 ms), `search("trains")` (4 s) and `deploy("prod")` (4 s). `search` is marked `replay: "safe"`; `deploy` has no `replay` field. Storage is a SQLite file, and each tool logs when it starts and ends.

Saved in the package's `test/examples/` folder, the script runs from `packages/durable` with Node's type stripping, once to start and once to continue:

```bash
node --conditions=source --experimental-strip-types test/examples/varops-kill.ts start run.sqlite run.log
# two seconds later, from a second terminal: kill -9 <pid>
node --conditions=source --experimental-strip-types test/examples/varops-kill.ts continue run.sqlite run.log

```

Two seconds in, with the weather search finished and the other two still running, the process took a `kill -9`. A second process opened the same file and re-sent the submission with the same `requestId`. Its log shows exactly one tool start: `search trains`. The finished weather search kept its stored result. The deploy never started again, and the model received "Tool deploy was interrupted and may have partially run" as an error result. The submission then finished in the second process.

Setup took about four minutes, including two build errors that came from building packages one at a time: the model data had not been downloaded, and one dependency package had not been built yet. The error message and the root build script's order fixed both. The run used Node v22.23.2.

## The fast route to a first result

Earendil's vacation demo shows the same behavior with a real model. The planner is about 1,300 lines of TypeScript, most of it the terminal UI. In Earendil's walkthrough, a subagent runs three searches in parallel, the process dies with weather and museums finished and trains not, and after a restart "`search` is safe to rerun: only trains runs again."

Two prerequisites. The demo is a `.ts` file run directly with `node`, and Pi's `package.json` requires Node 22.19.0 or later. It also needs a model: the demo starts with Pi's default model from `settings.json` and uses Pi's own login ("Log in with pi itself; credentials are shared"). VarOps did not run the demo or trace which providers it accepts.

```bash
git clone https://github.com/earendil-works/pi && cd pi && git checkout v1.0.0
npm install && npm run build
node packages/coding-agent/src/experimental/vacation/main.ts
# while a search is running, from a second terminal: kill -9 <pid>
node packages/coding-agent/src/experimental/vacation/main.ts --continue

```

Pin `v1.0.0`. The package README on `main` already differs from the 1.0.0 one, and the pin keeps the route matched to what is described here. The demo's README says to "Kill the process while a search runs and start it again with `--continue`." A `kill -9` from a second terminal is the closest thing to a real crash, and it is what VarOps used on its own test. The search results are canned and each topic takes a different time, so there is a window to hit.

One thing will look like a hang and is not. After a `kill -9`, the `--continue` run can pause for up to about 10 seconds before it opens: the demo locks its session directory, and the source says a lock left by a crashed process "goes stale after 10 s." Then watch the task panel. The searches show as tasks there, and after `--continue` the finished searches stay finished and only the unfinished one runs again.

From the demo to a real agent is four steps:

1. `npm install @earendil-works/pi-durable @earendil-works/pi-ai @earendil-works/chord` and pin the exact version.
2. Open the harness on SQLite or JSONL storage, for example `Harness.open(await openNodeSqliteStorage("./session.sqlite"), ...)`. The README's `MemoryStorage` keeps everything in memory; SQLite and JSONL are what keep conversations across restarts.
3. Write the tool inventory above into the code: `replay: "safe"` on the reads, nothing on the side effects, idempotency keys where the downstream system supports them.
4. Kill the process mid-call on purpose and read what the model was told.

The codebase is small enough to hand to an agent whole. Earendil puts it at about 15,000 lines without tests - roughly 150,000 tokens with GPT and 250,000 with Claude, by its own count - and notes that 3,000 of those are storage backends an agent reading the code can usually skip. There are also more than 30 examples and a small coding agent built on it.

Set two storage details before the first real run. Partial answers and tool output are committed at most every 100 ms, so a process crash loses at most that window. SQLite runs in a mode where "commits survive process crashes; the newest may be lost on power or host failure"; JSONL can instead flush to disk before every commit. Also, one process owns a storage at a time, with no cross-process locking, so it is one harness process per store with other clients attaching to it. The SQLite and JSONL code uses no Node APIs, so with a small adapter it runs on Bun or inside a Cloudflare Durable Object.

## What else it opens up

Crash recovery is the reason to try it. Four other capabilities come in the same package:

- **Forks.** One harness runs many conversations at once. Earendil's example treats a Slack channel as one conversation and each thread as a fork from the message it replies to.
- **Several people on one agent.** Any number of clients can attach to a conversation, and any of them can steer it or queue a follow-up while it works.
- **Changing code without stopping.** An extension can be replaced while conversations run; a tool call already in flight finishes on the old code.
- **App state that matches the transcript.** Typed documents - a todo list, a plan, a ticket - are committed in the same atomic write as the conversation.

Earendil says it will show the small tools it builds on Pi Durable, such as a Slack bot or a GitHub triage bot, in the coming weeks. VarOps found no public production report as of 2 October.

## Verdict

Yes, for long-running internal agents, starting with the demo and a tool inventory. Pi Durable is experimental, and its README is blunter than the launch post: "The API changes without notice between releases." The version number does not change that - pre-1.0 releases were on npm from 19 September and 1.0.0 landed on 1 October. [Pin the version](https://varops.com/how-to-make-a-local-model-benchmark-reproducible-when-the-tag-it-names-is-mutable/) and expect to read the changelog.

The VarOps test covered one case: a hard kill with a safe search and an unsafe deploy in flight, on SQLite. It did not cover the bundled tools, JSONL, power loss or a real model. The test that matters for a given team takes an afternoon: kill its own agent mid-call and read what comes back. The list of which actions are safe to run twice will carry over to whichever runtime the team ends up on.

*Disclosure: VarOps is published by Ran Aroussi, who also builds MUXI, which works in the same agent-runtime space as Pi Durable.*

## Sources

- Earendil, "Pi Durable" (1 October 2026): [https://earendil.com/posts/pi-durable/](https://earendil.com/posts/pi-durable/?ref=varops.com)
- Earendil, "Pi 1.0" (1 October 2026): [https://earendil.com/posts/pi-1-0/](https://earendil.com/posts/pi-1-0/?ref=varops.com)
- Pi Durable README at the npm 1.0.0 commit: [https://github.com/earendil-works/pi/blob/a13d35a742c6ef8462812a28fbe1d8c8b7431c32/packages/durable/README.md](https://github.com/earendil-works/pi/blob/a13d35a742c6ef8462812a28fbe1d8c8b7431c32/packages/durable/README.md?ref=varops.com)
- Replay default and recovery rule: [https://github.com/earendil-works/pi/blob/a13d35a742c6ef8462812a28fbe1d8c8b7431c32/packages/durable/src/harness/types.ts](https://github.com/earendil-works/pi/blob/a13d35a742c6ef8462812a28fbe1d8c8b7431c32/packages/durable/src/harness/types.ts?ref=varops.com) and [https://github.com/earendil-works/pi/blob/a13d35a742c6ef8462812a28fbe1d8c8b7431c32/packages/durable/src/harness/tool.ts](https://github.com/earendil-works/pi/blob/a13d35a742c6ef8462812a28fbe1d8c8b7431c32/packages/durable/src/harness/tool.ts?ref=varops.com)
- Bundled tools: [https://github.com/earendil-works/pi/blob/a13d35a742c6ef8462812a28fbe1d8c8b7431c32/packages/durable/src/tools/index.ts](https://github.com/earendil-works/pi/blob/a13d35a742c6ef8462812a28fbe1d8c8b7431c32/packages/durable/src/tools/index.ts?ref=varops.com)
- Vacation demo README and runtime: [https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/experimental/vacation/README.md](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/experimental/vacation/README.md?ref=varops.com) and [https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/experimental/vacation/runtime.ts](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/experimental/vacation/runtime.ts?ref=varops.com)
- Node engine requirement: [https://github.com/earendil-works/pi/blob/v1.0.0/packages/durable/package.json](https://github.com/earendil-works/pi/blob/v1.0.0/packages/durable/package.json?ref=varops.com)
- npm registry: [https://registry.npmjs.org/@earendil-works/pi-durable](https://registry.npmjs.org/@earendil-works/pi-durable?ref=varops.com)
- GitHub issue #10320: [https://github.com/earendil-works/pi/issues/10320](https://github.com/earendil-works/pi/issues/10320?ref=varops.com)
- Vacation demo session lock: [https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/experimental/vacation/sessions.ts](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/experimental/vacation/sessions.ts?ref=varops.com)