Akshay Parkhi's Weblog

pi-durable, from the ground up

20th October 2026

I built a runnable example of pi-durable and tested every lesson in it. The code lives at avparkhi/pi-durable-hello. The example has five scripts and one SQLite file. It uses a free fake model by default. It needs no server, no web UI, and no cloud. This post explains what the library does, and why each piece exists.

The problem

A normal agent loop keeps its work in process memory. The loop calls the model, runs a tool, and calls the model again. If the process dies, the work dies with it.

A chat resume does not solve this. It restores the text of the chat. It does not restore:

  • which tool call was running when the process died,
  • what that tool already changed on disk,
  • what input waited in the queue behind it,
  • what your application believed was true at that moment.

So the window that started the agent owns it. Close the laptop, and the agent stops.

The idea

pi-durable commits every step to storage before it shows that step to anyone. The process becomes disposable. If it dies, you open the same storage again. The harness then knows where every piece of work stopped.

Once a run survives its own process, three things become possible:

  • The agent can live on a machine, not in a terminal window.
  • Many clients can watch and steer the same agent.
  • The agent can wait hours for a human and then continue.

That is the whole frontier in one sentence: the agent outlives the client.

The building blocks

Each term below is a real type in the library. You will see all of them in the lessons.

BlockWhat it isWhy it exists
StorageA SQLite file, a JSONL folder, or memoryThe place where durability lives. One process owns a file at a time.
HarnessThe object that opens one storage and runs agents on itAll writes go through one line of atomic commits.
ConversationA transcript. The first one is the root.Each agent is one conversation.
EntryOne immutable record in the transcriptpi.user, pi.assistant, pi.tool-result, pi.system, pi.reset, pi.compaction
CommitOne atomic write of entries, documents, and tasks togetherThe harness stores all of it, or none of it.
DocumentTyped JSON stored next to the transcriptThe agent config, the inbox, the live stream state, usage, and your own state.
TaskA durable state machine with a checkpoint at every phaseA restart continues from the last checkpoint.
SubmissionInput you give to a conversation, with an optional requestIdThe harness admits each requestId exactly once.
ExtensionA named bundle of tools, prompt sections, hooks, and tasksThe conversation stores the name. The code can change while the agent runs.
WatchA subscription to committed stateA UI is only a watcher. A late joiner gets a snapshot.

One input flows through these blocks like this:

submit(input)            → pi.user entry
  pi.generation task     → pi.assistant entry (with tool calls)
    pi.tool task × n     → pi.tool-result entry × n
  pi.generation task     → pi.assistant entry (the answer)
                         → the submission is "done"

Each line above is a commit. Each task saves a checkpoint. That is why a crash at any line is safe.

The one rule that makes crashes safe

Every tool declares a replay policy.

  • A tool marked replay: "safe" runs again after a crash. A tool that reads a file is safe to repeat.
  • Any other tool returns an interrupted result after a crash. bash is one of these.

The interrupted result tells the model: “this tool was interrupted and may have partially run.” The model then decides what to do. It does not repeat a side effect by accident.

This is also the honest limit of durability. Durable means checkpointed, not exactly once. A payment or a deploy still needs its own idempotency key.

Run the five lessons

Clone the repo and run the lessons in order. Each lesson takes less than ten seconds. The scripts use a free fake model named faux. It echoes your text. If your text starts with !, it runs one bash tool with the rest of the line. To use a real model, copy .env.example to .env. Then set PROVIDER=openrouter and your key.

git clone https://github.com/avparkhi/pi-durable-hello
cd pi-durable-hello
npm install
npm run hello     # lesson 1: one question, one answer, stored in .data/agent.sqlite
npm run hello     # run it again and compare
npm run crash     # lesson 2: the process kills itself while a tool runs
npm run resume    # lesson 3: the same run finishes in a new process
npm run twice     # lesson 4: the same requestId twice, the same submission
npm run watch     # lesson 5: stream events while a steer joins the run
npm run clean     # delete .data/ to start over

Lesson 1: hello

The script opens .data/agent.sqlite, sends one question, waits for the answer, and prints every stored entry.

Run it twice. The second run prints the entries of the first run too. The SQLite file is the conversation.

  #7   pi.user         What is a durable agent?
  #10  pi.system
  #11  pi.assistant    Echo: What is a durable agent?

The pi.system entry stores the system prompt and the tool list of that request. Later requests store only what changed. This keeps provider prompt caches warm.

Lesson 2: crash

The script sends !sleep 4 && echo FINISHED. The model calls bash. The tool runs for four seconds. After 1.5 seconds the script calls process.exit(1). There is no close and no cleanup.

Nothing is lost. The user entry, the assistant entry with the tool call, and the tool task with its checkpoint are already in SQLite.

Lesson 3: resume

The script opens the same file and calls harness.resume(). The scheduler finds the pending tool task. bash is not replay-safe, so the task writes an interrupted result. The model reads it and answers.

  #16  pi.user         !sleep 4 && echo FINISHED
  #19  pi.assistant    Running it. [tool bash {"command":"sleep 4 && echo FINISHED"}]
  #21  pi.tool-result  <harness> [error] Tool bash was interrupted and may have partially run </harness>
  #23  pi.assistant    Tool finished: ...

The first process died after it wrote line 19. A new process wrote lines 21 and 23 from the same file.

The script also shows harness.submission(id). You can wait for a submission from a different process than the one that sent it.

Lesson 4: twice

The script sends the same input with requestId: "greeting-1" two times. Both calls return the same submission id.

first.id=25  second.id=25  same=true

A retry after a timeout, or after a restart, never sends the message twice.

Lesson 5: watch

The script opens an event stream with watchEvents(). Then it sends a tool-using input and a steer while the run is busy. The steer joins after the current tool round. You see inbox_update, then turn_start, then the answer to the steer.

The web UI and the terminal UI of Durable Pi use exactly this stream. A UI is a watcher of committed state, nothing more.

Where this leads

Durable Pi is an app built on this library. It puts a small HTTP server around the harness. The server runs on each machine you own. It exposes sessions and agents over /v1 with a bearer token. The mapping from the lessons to the server is direct.

You learned hereIt becomes there
One SQLite fileOne file per session on a device
root.submit() with a requestIdPOST /v1/sessions/:s/agents/:a/messages
watchEvents()An SSE stream: hello, snapshot, deltas
harness.resume() on reopenA device restart that resumes every session
A steer while busyThe “Steer” button in the web composer
An interrupted bash resultThe same result, shown in any client after a device restart

Three patterns follow from this:

  1. Proactive agents. An event, not a person, starts the run. Cheap code decides when. The durable agent does the first hour of work.
  2. Multi-client agents. You reach one agent from a browser, a terminal, a script, or another agent.
  3. Human near the loop. An approval is a durable task. The agent waits for hours, and a restart finds the same task still waiting.

Limits to remember

  • Durable is not exactly-once. External effects need their own idempotency keys.
  • One process owns a storage file at a time. There is no cross-process lock.
  • A tool like bash has the full power of the user that runs the process.
  • pi-durable is experimental. The example pins version 1.0.3.

References

More recent articles

This is pi-durable, from the ground up by Akshay Parkhi, posted on 20th October 2026.

Previous: Hermes Agent: The Self-Improving Story