Concepts: Pi Durable
Last updated 29 October 2026
Read this before you run Pi Durable on Tonbo: it names what the Harness keeps, how another machine resumes it, and why one process runs it at a time.
Pi Durable on Tonbo is Pi’s own SqliteStorage, unmodified. The difference is that its SQLite file is a Tonbo database, so the conversation is not owned by the machine that runs it.
How it differs from Pi’s own storage
MemoryStorage | SQLite on this disk | A Tonbo database | |
|---|---|---|---|
| A commit resolves | In memory | On this disk | Once two of three replicas, each in its own zone, hold it |
| The process dies | The conversation is gone | The next process on this machine resumes it | The next process on any machine resumes it |
| The machine is lost | The conversation is gone | The conversation is gone | The next process on any machine resumes it |
The conversation
Pi Durable commits an agent’s conversation, its model turns and its tool calls, to its storage as it goes. On Tonbo that storage is one database, named when you open it, such as harness-1: give each Harness a database of its own (see Concepts for what a database is).
A turn and when it is durable
Every Pi transaction resolves only once synced() did, so a turn the Harness has recorded is durable: Tonbo holds it, in two of three replicas in different zones. From Pi 1.2, a long outage makes a Harness call wait rather than reject; on Pi 1.0 and 1.1 it rejects with Pi’s StorageRejected (see backlog_full). A reset rejects it (see Resets in a Harness).
Resume
harness.resume() continues any run the last process left unfinished. A new process, on this machine or another, opens the database by name, the local copy rebuilds from Tonbo where its disk has none, and resume() continues the run. Record a tool call’s effect outside the process, such as an email, before making it, so a resumed run does not repeat it (see Run side effects once).
One writer
One process runs a Harness at a time: the last one to open its database. The one before it is refused from then on: its next Harness call rejects with UrsulaResetError, action stop, and it should stop and alert rather than open the database again, which would take it back from the new process. Run one instance per database (see Rules: one writer).
How it reaches your program
openUrsulaPiStorage("harness-1") opens the database by name with your API key and hands Pi its SqliteStorage; Harness.open() runs on it as on any storage, and harness.close() closes it too (see the Pi Durable quickstart).
MACHINE A MACHINE B, later
+------------------------------+ +------------------------------+
| the Harness takes a turn | | opens "harness-1" by name |
| --commit--> the local copy | | harness.resume(): the run |
| it resolves once Tonbo | | continues from every turn |
| holds it | | A recorded |
+--------------+---------------+ +--------------+---------------+
| ^
v |
TONBO, in the database's region: two of three zones hold every turn
A, if it still runs once B opens: its next call rejects with
action stop, and A stopsA turn resolves only once two of three replicas, each in its own zone of the region, hold it, which is why a process on another machine that opens the same database by name and calls harness.resume() continues from every turn the Harness recorded (see Guarantees).