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 turnTo 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.
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.
// 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
- Submissions: The drive submits each pending message to nanocode's turn inbox with the submission id as its request id, then commits a
nanocode.placestep. A crash between the two resubmits the message, and nanocode answersexisting. - Tools: Host tools are durable-harness
HostTools. Each call runs as atool:<call id>step with the tool'sreplayrule, and nanocode reports an interrupted call to the model as an unknown outcome. - Models: A session keeps the harness and model the visitor picks with
/model: Claude Haiku or Sonnet, GPT-6 Luna or Sol, or the open-weight GLM-5.3 or Kimi K3. Requests reach the AI Gateway with the deployment's OIDC token, Codex ones asopenai/,zai/andmoonshotai/ids. - Events: nanocode's events reach the session's live stream as
text,reasoning,tool_start,tool_end,turnandidleevents, which the home page renders.
Durability Modes
| Mode | A write returns | The drive waits for the log |
|---|---|---|
on (default) | After the local transaction | Before a never tool and when the drive ends |
strict | After its batch commits | At 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.
| Turn | Before | After |
|---|---|---|
| First turn, "Say ok." | 2.97 s | 2.21 s |
A turn with bash and read_file | 5.26 s | 3.74 s |
| Follow-up, "Say done." | 2.72 s | 1.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
- Session size: A Workflow run holds at most 10,000 steps and 25,000 events, and a turn takes about 5 to 12 steps.
- Replay: Every cold drive reads the whole log and replays every batch. Nothing snapshots the database yet.
- Steering: A message sent during a turn becomes the next turn.
- Demo policy: A prompt holds at most 2,000 characters and a session at most 20 prompts. The firewall allows 30 requests a minute per address.