The process crashed. The card wasn't charged twice.
A full agent framework on the Vercel AI SDK — runtime, HTTP server, Studio, evals, auth, RAG, workflows. Use all of it, or take only the durable core into the agent you already have: call it again with the same runId, and completed tools never run again.
Swap one call. Keep every tool.
Same arguments as generateText. The only additions are a runId and a journal — your tool definitions stay exactly as they are.
import { generateText } from 'ai';+ import { runDurable } from '@gnldev/durable';- const res = await generateText({ model, tools, prompt });+ const res = await runDurable({+ runId, journal, model, tools, prompt,+ });
Same crash. Two very different recoveries.
Tool calls are keyed by the toolCallId the AI SDK generates. On replay the same id comes up, so a completed tool never runs again — the card isn't charged twice.
Two workers. One winner.
Append-only journal, putIfAbsent, single winner. The loser finds the call already recorded and skips it.
From request to crash to replay.
A request comes in
runDurable is called — the agent loop starts with a runId and a prompt.
Appended to the journal
The model step and the tool call (keyed by toolCallId) are appended to the journal (Sqlite/Postgres).
The process crashes
Process kill, deploy, or timeout — everything in memory is lost.
Replay & resume
Called again with the same runId; the journal is replayed deterministically, and any tool that already completed never runs again.
Agents that process payments
A tool like chargeCard runs exactly once: even if the process crashes mid-run and gets called again with the same runId, the card isn't charged a second time.
Agents that send email or notifications
Tools like sendEmail and sendNotification are keyed by toolCallId — on resume, the user never gets the same notification twice.
Long-running workflows
Multi-step agent tasks resume from the last journal step even if they're interrupted by a deploy, restart, or timeout. In dynamic agent networks, routing decisions freeze into the same journal via CAS too — the router isn't called again on resume.
Durable tool calls
External API calls (payments, email, database writes) are appended to the journal and replayed idempotently — this guarantee holds for every tool you define.
See every run. Step back to any point.
Studio runs on top of the journal — there is no separate collection layer. That is why it stays complete after a crash: traces are derived from the journal after the fact, rather than collected live by a library that dies with the process.
The run is suspended before any money moves. The approval banner is up, and Fork @3 branches a what-if from exactly here.
You can miss it. Your model can miss it. GNL doesn't — it stops and asks.
The score never decides — deterministic fields decide, a human has the last word.
The gap between the side-effect counter and what the model narrated was a lie the user could not see — an answer served from the record was indistinguishable from a fresh success. In GNL the model now says it itself: "the operation was not performed again — this is the record of the earlier one." Second live proof: "KLIMA-5" was ordered; when "klima-5" came in the hash missed — the semantic identity layer caught it and the question went to a human.
The model said "order created" three times. The real counter stopped at 2.
We measured our limits and we publish them.
11,989 tests across 1,266 files, v0.18.0
Semantic cost is thread-local: it grows with the number of side-effect jobs in ONE conversation, not with users, tenants, or total volume. A deployment with millions of users still pays tens of milliseconds per gated call; scaling out is the ordinary horizontal kind. Measured 6 September 2026 · real Postgres · 2048-dim vectors · bench-scale.ts
Real Postgres, primary killed, replica promoted, synchronous replication. Exactly-once held.
24.5 to 87.3 turns/s, then 135.8 on 8. 600 concurrent booking attempts produced exactly 200 reservations. Measured 2026-08-29.
Hono, node:http, Express, Fastify, Koa, Nest, Bun and Deno. Measured 2026-08-29.
The same agent code, installed from a real npm package, ran on both with a single line changed. Measured 2026-08-29.
Why did the agent do that? Play the run again with parts left out.
Lab opens from Studio's Inspector and answers by measurement: each part of a recorded run is left out in turn, the run is replayed with every tool call answered from the recording, and each part's share is counted. It is open source, and experimental — the API will change.
What brought a call about
Prompt sections, earlier messages and tool results are left out one by one and the run replayed. No tool runs. The result is each part's share, with its noise beside it.
What it would do, changed
A copy of a run with another prompt or tool set is played beside the run as it was, so a difference is read against chance. Nothing is written.
Why a turn of a thread went wrong
Against an expectation you write, the diagnosis names a likely cause and then plays it to prove it. A cause is named only when plays bear it out.
Your stack. Not a new one.
- Voice (TTS/STT)
- A no-code visual editor
- A replacement for the AI SDK
- A telemetry platform
- Mandatory hosting
It has 3 storage adapters (InMemory, SQLite, Postgres), where some frameworks offer around 16. If you need what a full agent framework does better, use one.
GNL is Gönül.
Crash it. Call it again.
We ask one thing: use it for real and tell us what breaks.
The form opens here and runs on Google Forms; we don't keep personal data on our own servers.
- Direct access to the person who writes the code
- One-on-one help with the integration
- A say in the roadmap: what you need moves up