GNL, hand-rolling it, and the agent frameworks
Three ways to build the same thing: write the durability yourself, adopt a full agent framework and add durability on top of it, or start from a durability layer that sits on the Vercel AI SDK agent loop you already run. GNL is the third — and on that core it also carries RAG, workflows and dynamic agent networks, each one journal-backed and replayable rather than a separate subsystem. No product is named on this page. Which framework suits you depends on your team and your problem, and a comparison written by one of the candidates is not where that gets settled — go read theirs. What is worth writing down is the axis that actually differs, and what GNL deliberately does not do.
GNL doesn't replace the Vercel AI SDK — it sits underneath as a thin durability strip.
Feature matrix
We line up all three approaches side by side on the axes where GNL's core distinction shows most clearly — durability, exactly-once, and deterministic replay. Parity on the orchestration surface (RAG/workflow/network) is covered separately in the "closed gaps" table below.
| Axis | Hand-rolled | Agent frameworks | GNL |
|---|---|---|---|
| Durability (crash → resume where it left off) | Built by hand — checkpoint/state management is on you | Usually orchestration-first (workflow/RAG/memory); durability added on top when it is offered at all | Core guarantee — every step is written to the journal; under synchronous replication 30/30 acknowledged writes survived a live failover, backed by 4,900+ tests |
| The same side effect never happening twice | You design the idempotency keys — and you have to notice when the model re-plans the same work | Rarely a core guarantee — and anything keyed on toolCallId is blind to the re-planned call | Default: keyed by toolCallId, proven with multi-worker CAS race tests. Opt into `idempotency: 'args'` and it keys on the ARGUMENTS instead — the tool runs once even when the model re-plans it under a brand-new toolCallId (the dominant real-world duplicate case) |
| A repeat of the same work in different words (where a hash is blind) | Nothing — “create product ABC” written two ways is two jobs | Nothing — we found no declared core guarantee on this axis | A candidate is found; the DECISION comes from comparing deterministic fields, and an unclear case becomes a question for a human. A score never decides — the order is deterministic > human gate > probabilistic. Off entirely unless both declarations are made (opt-in) |
| Deterministic replay / time-travel | Usually absent — manual logging/debugging | Varies — depends on that framework's own tooling | Timeline + time-travel replay in Studio |
| Edge-native footprint | Depends on your own dependencies | Full framework (workflow/RAG/memory) — larger surface area | 32.4 KiB gzip core, no cold-start cost |
| BYO-DB (your data stays with you) | Whatever you write to is what you get — it stays with you | Unspecified; depends on the framework integration | SQLite/Postgres/Redis journal — data always stays with you |
| Telemetry / phone-home | None (it's your own code) | Unspecified | None — as a principle. The B/C plans only send a signed AGGREGATE usage check-in |
| Governance (approval queue, audit, retention/GDPR, policy, eval gate) | Built by hand | Unspecified | Ships out of the box with Studio |
“Varies” means the axis is not a stated core guarantee across the frameworks we looked at — it differs by product and by version. Check the documentation of the one you are weighing rather than trusting a table we wrote.
The orchestration surface
What people expect from a full agent framework, carried on the same journal — so each of these is exactly-once and replayable rather than a subsystem with its own rules. Every row maps to a named API in the source.
| Capability | GNL API | Note |
|---|---|---|
| Dynamic multi-agent routing (Supervisor/.network()) | createGnl networks: + runNetwork | Router decisions are frozen into the journal via CAS → the router is never called again on resume; capped by maxIterations plus a mandatory final step. |
| Built-in scorer library | @gnldev/evals — 8 scorers | faithfulness, hallucination, answerRelevancy, toxicity, bias, completeness, contextPrecision, toneConsistency — all wrap llmJudge, journal-memoized. |
| Dataset & experiment management | createDatasetsManager | Content-hashed version history, idempotent experiment recording, compare (aggregate deltas + regression/improvement). |
| GraphRAG (indirect-relevance retrieval) | @gnldev/rag — GraphRag | Threshold/hops/decay over a similarity graph; a VectorStore drop-in → exactly-once inside createRagTool. |
| Chunking + persistent vector DB | chunkText/chunkDocuments + PostgresVectorStore | recursive/markdown/character strategy, deterministic id#i; pgvector (HNSW/ivfflat) shares the same interface as InMemory. |
| Semantic tool search | @gnldev/processors — toolSearch | Embedding-based topK tool selection; the selection is journaled → embedding never runs again on resume (always/minScore). |
| Named SSO providers | createAuth0Sso, createWorkOsSso | Auth0 (OIDC code + JWKS RS256) and WorkOS; centralized signature verification, CSRF state hook. |
| Named observability presets | @gnldev/otel — otlpPresets | Langfuse/LangSmith/Braintrust/Honeycomb/Datadog/Collector in a single line; plus live mode (@gnldev/otel/live). |
| Redis storage adapter + distributed pubsub | @gnldev/durable/redis — RedisStorage | Atomic CAS via SET NX, native TTL cache; two-worker fan-out with @gnldev/events (tested against a real Redis integration). |
Evidence — not a claim, a passing test
GNL's exactly-once/deterministic-replay claim isn't just talk — it was tested against real infrastructure.
Live failover test — 30/30 writes survived
In a real Postgres primary-SIGKILL → promote scenario with synchronous replication, all 30/30 acknowledged CAS writes survived and exactly-once held; atomicity on the new primary was proven with 10 rounds of race testing (test/failover-real.test.ts).
Multi-worker CAS race tests
CAS does not assume a single connection; 13 scenarios verify it stays atomic when multiple instances try to write to the same run (test/multi-worker.test.ts). A split-brain edge case that was found was CLOSED with putIfMatch CAS — verified against a real PG+Redis takeover race.
4,900+ tests / 548 files green
5,004 tests pass across 548 files (measured 2026-09-14), including real Postgres and Redis integrations; every package typechecks clean. 65 tests and 5 files are skipped by default because they need an environment the suite cannot assume — among them the live failover scenario, which is a separate reproducible experiment (docker compose + GNL_FAILOVER=1) rather than part of the green count. Journal growth has been characterized, and rolloverRun solves compaction for long-lived runs.
Causal proof for the taint guard — 2/5 → 0/N
Same model, same prompt-injection scenario: with the guard off, the attacker's transfer succeeds in 2 of 5 attempts; with the guard on (block or taintGuardian) it never succeeds (gnl-eval, measured 2026-08-29).
Verified across 8 different runtimes
The same HTTP/Studio handler was actually mounted and kept working on Hono, node:http, Express, Fastify, Koa, Nest, Bun, and Deno (server-matrix, measured 2026-08-29).
Near-linear scaling under load
Under PM2 cluster mode with real Postgres and SSE, throughput went from 24.5 turns/s on 1 worker to 87.3 turns/s on 4 (3.38x) and 135.8 turns/s on 8; 600 concurrent booking attempts produced exactly 200 reservations and 200 ledger rows, no more (performance-test, measured 2026-08-29).
BYO-DB: SQLite to Postgres in one line
The same agent code, installed from a real npm package (not a symlink), ran against SQLite and Postgres storage with only a single line changed (trial-sqlite/trial-pg, measured 2026-08-29).
What the frameworks do that GNL does not
Honesty cuts both ways. If you need any of these, a full agent framework is the better tool and you should use one — these are out of scope here, or simply not built yet:
Voice (TTS/STT)
Speech input/output. Several frameworks have it built in; GNL leaves it out of scope.
Channels (Slack/WhatsApp)
Ready-made Slack/WhatsApp-style channel integrations. GNL deliberately skips these — it is a layer under your application, and your application already owns its channels.
No-code / visual agent editor
A visual, drag-and-drop agent builder. GNL is code-first and offers none.
~16 storage adapters
Breadth of storage backends. GNL has 4 (InMemory/SQLite/Postgres/Redis); some frameworks offer many more. The guarantee is the same on all four, but the range of choices is narrower.
ClickHouse observability adapter
Native adapters for observability backends like ClickHouse. GNL goes through OTLP/OTel with named presets, which reaches most of them, but there is no direct adapter.
What GNL is NOT
Setting expectations is part of positioning — areas GNL does not cover:
Not a framework replacement
It wraps the Vercel AI SDK's agent loop; it does not replace it or change generateText/streamText.
Not a no-code / visual builder
GNL is a code-first layer. Durable RAG (@gnldev/rag), workflows, and agent networks now ship out of the box — but it provides no no-code agent editor, voice, or ready-made chat channels.
Not a telemetry/analytics platform
No phone-home; as a principle, no run data or content is ever sent to GNL.
Not mandatory hosting
The journal stays in your own database — GNL never hosts your data.
A layer underneath, not a replacement
GNL's distinguishing value is the exactly-once, deterministically replayable core. The orchestration surface on top of it exists so those guarantees reach the whole application — not as a claim to have replaced anything you are already using happily.