GNL 0.18: early and moving fast — APIs can change between releasesJoin us

GNL
Agent framework · on the Vercel AI SDK

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.

exactly-once57.4 KiB gzipyour data stays with youSQLite · Postgresserver · Studio · evals · RBAC
01 · One call

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.

src/agent.tsdiff
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,
+ });
02 · Exactly-once tools

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.

generateText — called again after the crashmodel:0tool:call-3tool:call-7 · chargeCardmodel:0tool:call-3chargeCard · again× 2runDurable — called again with the same runIdmodel:0tool:call-3tool:call-7 · chargeCardreplayedreplayedreplayednext step runsserved from the journal× 1

Two workers. One winner.

Append-only journal, putIfAbsent, single winner. The loser finds the call already recorded and skips it.

Journal · append-only
model:0✓
tool:call-3✓
tool:call-7 chargeputIfAbsent → single winner
worker A · call-7✓
worker B · call-7already recorded → skipped
card isn't charged twice — side effect exactly 1
03 · How it works

From request to crash to replay.

01

A request comes in

runDurable is called — the agent loop starts with a runId and a prompt.

02

Appended to the journal

The model step and the tool call (keyed by toolCallId) are appended to the journal (Sqlite/Postgres).

03

The process crashes

Process kill, deploy, or timeout — everything in memory is lost.

04

Replay & resume

Called again with the same runId; the journal is replayed deterministically, and any tool that already completed never runs again.

Who it's for · GNL steps in for agent work where the side effects can't be undone.
chargeCard

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.

sendEmail · sendNotification

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.

deploy · restart · timeout

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.

payments · email · database writes

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.

04 · Studio · time travel

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 support-desk example · one rundeterministic mock model · no API key
Step 3 of 4
issueRefund is called, the guard suspends the run

The run is suspended before any money moves. The approval banner is up, and Fork @3 branches a what-if from exactly here.

Time-travel
Fork @3
Cost
$0.0000
approval queueaudit logretention / GDPR purgepolicy editorbudget & quotaOpen the Studio docs →
05 · Guards

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.

Duplicate guard · our own measurement · generated scenarios, real engine
The user asked for something new. The model re-emitted the previous turn's payment.
13 of 126 turns
all 13 stopped at the gate and went to a human. It also asked 3 times when it should not have.
Generated scenarios, real engine, a single tool model. Not a rate for your deployment.
Taint guard · prompt injection
Same model, same injection. The attacker's transfer, guard off, then on.
untrusted inputtools
2/50/5the attacker's transfer landed — taint guard off, then on · gnl-eval, measured 2026-08-29
measured in a live test
Your AI said "created". Did it?

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.

replayDisclosure: 'explain'

The model said "order created" three times. The real counter stopped at 2.

06 · Measured

We measured our limits and we publish them.

57.4KiB
gzip core · 1 dependency in the core runtime
11,900+
tests pass; the real-database gate (Postgres, MySQL, MongoDB) is measured apart
11,989 tests across 1,266 files, v0.18.0
0
outbound calls — no telemetry, no phone-home
Duplicate guard · cost of a lookup · log scale
per exact dedup read — practically free on the hot path0.25 ms
semantic scan in a conversation of 100 records36 ms
the same scan at 2,000 records — linear, thread-local~650 ms

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

30/30
writes survived a live failover

Real Postgres, primary killed, replica promoted, synchronous replication. Exactly-once held.

3.38×
throughput on 4 workers

24.5 to 87.3 turns/s, then 135.8 on 8. 600 concurrent booking attempts produced exactly 200 reservations. Measured 2026-08-29.

8
runtimes, one handler

Hono, node:http, Express, Fastify, Koa, Nest, Bun and Deno. Measured 2026-08-29.

1 line
from SQLite to Postgres

The same agent code, installed from a real npm package, ran on both with a single line changed. Measured 2026-08-29.

07 · Lab
Experimental

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.

A recorded run · parts left out one by one
prompt section
earlier message
tool result
earlier message
tool result
the call that was made
The result is each part's share, with its noise beside it.
01

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.

02

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.

03

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.

08 · Works with

Your stack. Not a new one.

Vectorspgvector · Qdrant · Chroma · Weaviate
OTelLangfuse · LangSmith · Braintrust · Honeycomb · Datadog
SSOAuth0 · WorkOS
09 · What GNL is not
  • 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.

10 · The name

GNL is Gönül.

My mother's name is Gönül. The framework is named after her.

$ npm i @gnldev/durable
Read why →

Crash it. Call it again.

Design partners
Using the AI SDK in a real project? Let's shape GNL around it.

We ask one thing: use it for real and tell us what breaks.

Design partner application

› loading form

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