@gnldev/durable has 34 recorded weekly downloads on npm, a decrease of 51.4% over the past 30 days, ranking #1790 of 2,256 tracked listings.
About @gnldev/durable
@gnldev/durable
Call-scoped exactly-once effect + deterministic-replay durability for Vercel AI SDK agents.
Wraps the AI SDK's agent loop (generateText/streamText) without changing it. After a crash/restart a completed tool call is never executed a second time, and the agent makes the same decisions (deterministic replay). The one thing neither other agent frameworks nor plain AI SDK give you structurally: correctness.
The precise guarantee is at-most-once for a side effect, because the crash window between executing a tool and journaling its result cannot be closed by any client-side library — see the callout below. gnl's answer to that window is to refuse to guess: it blocks and asks, rather than silently re-running or silently continuing.
Install: pnpm add @gnldev/durable — the storage adapters ship inside the same package. (Or clone the repo and pnpm install && pnpm -r build.)
Durability in 2 lines
If the process crashes mid-turn, call again with the same runId → it resumes where it left off, the card is never charged a second time:
One id is one JOB, never one session. runDurable is the raw surface: the string you pass is the identity, so a sessionId or a conversation id there makes every later turn replay the first one forever. Through createGnl you name the work instead (workKey) and the engine derives the id — see Work identity.
What "never charged twice" actually means: the guarantee is at-most-once. If a step already completed, it's replayed and skipped. But if the crash lands in the narrow window after the charge executed and before its success was journaled, resume can't know whether the charge went through — so for a side-effect tool with no recover() and no idempotent/approval, it does not silently re-run and does not silently continue: it blocks and asks a human. Safe direction (never a double charge), but resume is not always seamless.
Concretely, runDurable throws SideEffectRetryBlockedError — it does not come back in result.interrupts, which carries approval suspensions only. Catch it:
recover() is the way to avoid the human: it re-checks the provider and must return { done: true, output } or { done: false }. Any other shape is treated as "could not determine" and lands back on this same gate rather than re-running the effect.
If you know generateText, you already know this — same arguments, same return type. The only addition: runId + journal.
How it works
• durableTool — wraps a tool's execute. The key is the toolCallId the AI SDK assigns; on replay the model response is reproduced verbatim, so the same id is generated → a tool that already succeeded never runs again. • withDurableModel — via a wrapLanguageModel middleware, records every model response to the journal and replays it on resume → the agent reproduces the same tool chain. • Journal — an append-only log. InMemoryJournal (dev) · SqliteStorage().runs (prod, node:sqlite, zero native dependencies).
Guarantee: a side effect is never repeated for the same toolCallId (call-scoped, replay/resume-safe). Not "the LLM always produces the exact same text" — if there's a recorded entry, it's replayed; everything past the crash point runs live. A call whose outcome is unknown (after a crash/timeout) is first checked against the provider via tool.recover?(); if it's still unknown, the system blocks and asks for approval instead of silently retrying (see "Human-in-the-loop").
Scope caveat (important): the default dedup is keyed by toolCallId. If the model re-plans the same business action under a new toolCallId (with identical or slightly different args — a documented AI SDK pattern), the call-scoped key does not collapse it. With identical args the default limits.sideEffectDuplicates: 'suspend' stops the repeat and asks a human — without a key nothing can tell "the same work again" from "two of the same, on purpose". With different args it is a new call. For "this action happens once" without a question, give the tool its identity: idempotency: 'args' / idempotencyKey (next section). A tool that declares no sideEffect is treated as one — declare sideEffect: false on a read tool.
A run outlives its caller (createGnl)
A registry run — gnl.run(), gnl.stream(), and every door built on them — executes apart from the call that started it, on the process's executor (one per process per store). The caller follows it.…
Description from the npm listing.
- ai
- agent
- llm
- typescript
- ai-sdk
- durable
- exactly-once
- journal
- replay
- idempotency
- crash-recovery
README badge
Show your rank in a README or on your website. The badge updates automatically.
Charts
Images: 30d · 1y · all time · 1600×900 dark
Adoption over time
7/30/90-day growth
Ranking history
Listing placement
Listing placement
Filled points are observed values; hollow points come from Internet Archive snapshots of the listing page. Dashed segments span periods with no observations — values between points are not interpolated.
Each band is one page of 20. Weekly downloads on a linear scale. Placement is projected from weekly downloads, so it can differ from the marketplace's own sort order. Hover a dot to see the listing.
Recent observations
Full history (121)Similar npm AI SDKs plugins
All 20 @gnldev/durable alternativesData source
Weekly downloads (trailing 7 complete days) come from the official npm downloads API. Downloads include CI and mirrors and are not equivalent to installs or users. npm publishes no ratings.
Listing: https://www.npmjs.com/package/@gnldev/durable. First seen Oct 16, 2026; last successful collection Oct 19, 2026. See the methodology.