How It Works

nanocode on Vercel runs a nanocode agent in Vercel Functions on durable-harness. Every SQLite write of the agent is a step in the session's Workflow run, so a turn that loses its Function continues in another one from the log. Its source is gakonst/nanocode-vercel-durable.

Overview

A message to the chat route becomes a submission in the session's run. The route drives the turn in its own Function. The agent is the nanocode SDK compiled to WebAssembly, and its model requests go to the Vercel AI Gateway.

When a Function dies, a queue message delivers the session to the flow route. That drive reads the log, rebuilds the agent's database from it, and finishes the turn.

browser ─POST─▶ chat route ─drive─▶ nanocode-ts ─▶ AI Gateway
                    │                   │
                    │ send              │ SQL batches, tool steps
                    ▼                   ▼
             Workflow run: the session's event log
                    ▲
                    │ queue delivery when a Function dies
                flow route ─drive─▶ replays the log, ends the turn

To try it, open the home page, press "Kill the next tool call", then ask for a charge. The Function dies during the charge, a new one takes the turn over, and the agent reports that the charge may have gone through.

The Agent

nanocodeHarness from nanocode-ts/vercel is a durable-harness harness: a name and a drive function that the kernel calls for each delivery. durableAgent adds the chat route, the queue consumer and the session API.

lib/agent.ts
import { createNanocode } from 'nanocode-ts/vercel'
import { chargeTool } from './tools'

// A nanocode agent with Code Mode, subagents and a bash whose files
// live in the session's journaled SQLite, under /workspace
export const nanocode = createNanocode({
  harnesses: [{ id: 'codex', label: 'Codex', models: [glm] }],
  tools: [chargeTool(record)],
})

The Journal

nanocode keeps its state in SQLite through the SqlHost interface of nanocode-ts. The harness runs each transaction on an in-memory database and records every write in a step named nanocode.sql, keyed by its position in the log.

nanocode-ts/src/vercel/journal.ts
// 1. Run the transaction on the drive's in-memory database
const before = sql.version()
const rows = sql.run(statements)

// 2. Queue it for the log only if it changed the database
if (sql.version() !== before) queue.push({ kind, operation, statements })

// 3. Commit everything queued as the next batch, keyed by its position
await ctx.effect(`sql:${n}`, {
  name: 'nanocode.sql',
  replay: 'safe',
  input: { transactions },
}, async () => ({ writer }))

A new drive replays the committed batches in order before it opens nanocode. The kernel keeps the first step for each key and ignores the steps of a stale drive, so every reader replays the same sequence.

Turns and Tools

Durability Modes

ModeA write returnsThe drive waits for the log
on (default)After the local transactionBefore a never tool and when the drive ends
strictAfter its batch commitsAt every write

With on, a crash loses the writes after the last durable one, never a write in between. nanocode then repeats the model calls whose results were lost, and a never tool runs only after its receipt is in the log.

Crash Recovery

These are the live events of a turn on this deployment, with the Function killed 1.5 s into the charge. The second chaos event comes from the drive that took the turn over.

{"type": "tool_start", "name": "charge",
 "args": {"amount": 42, "memo": "order 1001"}}
{"type": "chaos", "kind": "process", "effect": "tool.charge"}
{"type": "chaos", "effect": "tool.charge", "reannounced": true}
{"type": "tool_end", "name": "charge", "status": "unknown"}
{"type": "turn", "status": "completed"}

The kernel records the charge step as interrupted, because its replay rule is never. The next drive replays the batches and nanocode resumes the turn from its checkpoint. The model receives "Tool execution interrupted by a host crash; outcome unknown." and does not charge again, and the deployment's logs hold one charge.

Measurements

Four interleaved rounds on 2026-10-20, from San Francisco against two previews in iad1 with Sonnet 5.5, measured the time from a message to its idle event before and after run-ahead durability and live deltas.

TurnBeforeAfter
First turn, "Say ok."2.97 s2.21 s
A turn with bash and read_file5.26 s3.74 s
Follow-up, "Say done."2.72 s1.25 s

After the change, a drive took its model calls plus about 0.1 s. Replaying the log took 0 to 5 ms, opening nanocode 9 to 17 ms, and about 6,200 of each call's 6,400 input tokens came from the gateway's prompt cache.

Limits

More