# Durable Nanocode

Processes die, and agents should not die with them. nanocode journals every turn, model call, and tool call to storage the host already has. A turn finishes after its process dies, and a call that was running then is reported, never run twice.

**An agent that spends money must never spend it twice.** Retrying a tool after a crash is how a $5 charge becomes $10. nanocode does not retry what it cannot prove finished. It reports the outcome as unknown and lets the model decide.

## TL;DR

* **Constant cost per turn:** a three-cell turn writes 71 KB at turn 1 and 92 KB at turn 10,000.
* **Fast reopens:** a new process reopens a session of 10,000 turns in 188 ms.
* **Recovery in milliseconds:** after a kill in the middle of a charge, the turn finished 206 to 212 ms later on SQLite, 245 ms after a Durable Object reset, and 650 ms after a Vercel Function died.
* **Exactly one charge:** the ledger held one charge after every one of those crashes.
* **Cheap at scale:** a million one-reply turns on a Durable Object cost $11 of row writes.

All of these were measured on 2026-11-09.

## The Format

A session is a head, records, and pages.

* **Head:** one small record that says where everything is. Every commit rewrites it, at 0.7 to 3.4 KB.
* **Records:** inputs, outputs, checkpoints, and receipts. Each is written once and named by the hash of its content, so a prompt that 10,000 agents share is stored once.
* **Pages:** the index over records, 16 keys to a page, sealed when full.

A commit is one transaction: its records and the head land together or not at all. A crash between two commits leaves the session exactly as the last commit left it.

## Constant Writes

On SQLite with a scripted model, a message and a reply is 2 commits and 3.8 to 6.4 KB. A turn with three cells is 17 commits. **A turn costs the same at turn 10,000 as at turn 10:**

```mermaid
xychart-beta
  title "KB written by one turn, by its position in the session"
  x-axis "turn" [1, 10, 100, 250, 500, 1000, 2000, 5000, 10000]
  y-axis "KB" 0 --> 120
  bar [71, 73, 73, 96, 95, 94, 96, 96, 92]
```

## Fast Reopens

A new process reads the head, then the pages it names, one level per round trip. It writes nothing.

| Session      | Round trips | Bytes read |   Time |
| ------------ | ----------: | ---------: | -----: |
| 10 turns     |           7 |    0.18 MB |   7 ms |
| 100 turns    |          11 |    1.75 MB |  23 ms |
| 1,000 turns  |          15 |    5.56 MB |  57 ms |
| 10,000 turns |          17 |    19.7 MB | 188 ms |

Round trips grow with the logarithm of the session, and ten times the turns read about 3.5 times the bytes. Nothing rewrites history, so a long session pays for its length only when a process opens it.

## Unknown, Not Retried

Our test tool, charge, appends to a ledger, waits 3 s, and appends again. The parent kills the process 1.5 s into a $5 charge. Over three runs, a second process opened the journal in 45 to 66 ms and finished the turn 206 to 212 ms later.

```mermaid
sequenceDiagram
  participant P1 as Process 1
  participant J as Journal
  participant P2 as Process 2
  P1->>J: the cell, and a call to charge, running
  Note over P1: SIGKILL, 1.5 s into the charge
  P2->>J: open, and raise the fence
  J-->>P2: a call with no result
  P2->>J: result: outcome unknown
  P2->>J: turn completed
```

* **A call with a committed result:** replays that result. If the kill lands after the charge, the model receives the first process's receipt.
* **A call without one:** reaches the model as an unknown outcome, and the model decides. The ledger held one charge in every run.
* **The fence:** each open raises it, so a process that still holds the old fence cannot write.

## On Cloudflare

Each session is one Durable Object, and the journal is the object's own SQLite, so every agent has a single writer without locks. The session waits for one storage sync before a model request leaves, so every earlier write is durable before the outside sees its effect.

A deploy or an eviction resets the object and drops its memory. So the object stores an alarm before a turn runs, and the next instance replays each Code Mode cell from the journal. The [durable-cloudflare](https://github.com/nanocodexyz/durable-cloudflare) drill passed all three faults:

| Fault                                             | Result                                                                 |
| ------------------------------------------------- | ---------------------------------------------------------------------- |
| A reset 1.5 s into a $42 charge                   | Completed 245 ms after the reset, one charge, outcome reported unknown |
| A reset while a cell sleeps before its first call | Completed, one charge                                                  |
| A reset during each of five model calls           | Completed after five resets                                            |

## On Vercel

A Function has no database and no long life. So the session's Workflow run is the journal's write-ahead log: every committed SQL batch is a step of the run, keyed by its position. The next Function replays the batches into an in-memory SQLite and finishes the turn.

The Workflow kernel keeps the first step for each key, so two drives of one session agree on batch n. The [durable-vercel](https://github.com/nanocodexyz/durable-vercel) drill killed a Function halfway through a charge and passed for both harnesses in both modes, with one charge each:

| Run                                 | Recovery | Second turn |
| ----------------------------------- | -------: | ----------: |
| Codex, writes return before commit  |   650 ms |      236 ms |
| Claude, writes return before commit | 1,327 ms |      581 ms |
| Codex, writes wait for commit       | 1,399 ms |      663 ms |
| Claude, writes wait for commit      | 2,715 ms |      908 ms |

## What It Costs

A Durable Object bills $1 per million rows written and $0.20 per GB a month. nanocode-ts writes 11 rows for a turn with one reply and 17 for a turn with a cell and a tool call.

| Workload                                           |                 Cost |
| -------------------------------------------------- | -------------------: |
| A million turns with one reply each                |                  $11 |
| A million turns with a cell and a tool call        |                  $17 |
| Keeping a session of 1,000 three-cell turns, 42 MB | Under 1 cent a month |
| Keeping 100,000 finished subagents, 6.2 GB         |        $1.23 a month |

The rows of a one-reply turn cost about a tenth of its cached prompt on the cheapest model in the demos. The same statements run on a SQLite file, sqlite-wasm in a tab, and a Workflow log. Rust hosts also have JSONL and Postgres stores.

## Try It Today

Open [nanocode on Cloudflare](https://nanocode.xyz/cloudflare/) or [on Vercel](https://nanocode.xyz/vercel), select "Kill the next tool call", and ask for a charge. The process dies 1.5 s into the charge, the next one takes the turn over, and the agent reports that the charge may have gone through.

We think every agent that touches money needs this, and that it should cost nothing to get. If you run agents in production and lose turns to deploys or crashes, we would love to hear from you on [GitHub](https://github.com/nanocodexyz).

- [Durability](/docs/guides/durability): Journal turns, reopen sessions, and replay receipts.

- [Cloudflare Workers](/docs/guides/workers): Run sessions in Durable Objects, step by step.

- [Vercel Functions](/docs/guides/vercel): Run a durable agent on Vercel, step by step.

- [session_store](/docs/api/rust/session_store): The Rust stores and their contract.
