Start with the core. Take the rest when you need it.
GNL is a full agent framework, durable from the core. You can use all of it, or put only the core into an agent you already have — and go from one to the other a step at a time.
Five steps, none of them a rewrite#
Each step keeps what the one before gave you and adds to it. Stop at any of them.
- The core —
runDurableinstead ofgenerateText: a tool call is never fired twice, and a crashed run resumes where it stopped. - A registry —
createGnl: agents by name, work named by your own id (workKey), runs that outlive the call that started them, approvals. - A server —
createRestApi: REST, OpenAPI and server-sent events for every agent, with auth, limits and budgets. - Studio — every run on a timeline, time-travel and fork, the approval queue, a playground.
- The packages — memory, workflows, RAG, evals, MCP, jobs, channels: each one carries the core's guarantees.
1. The core, inside the agent you already have#
The arguments are generateText's. The additions are an id for this piece of work and a journal in your own database.
import { runDurable, gnlTool } from '@gnldev/durable';
import { SqliteStorage } from '@gnldev/durable/sqlite';
import { openai } from '@ai-sdk/openai';
import { tool } from 'ai';
import { z } from 'zod';
const storage = new SqliteStorage('runs.db');
const refund = gnlTool(
tool({
description: 'Refund an order',
inputSchema: z.object({ orderId: z.string() }),
execute: async ({ orderId }) => ({ refunded: orderId }),
}),
{ sideEffect: true, idempotency: 'args' },
);
const res = await runDurable({
runId: 'refund-order-42', // the id of THIS work — call again with it after a crash
journal: storage.runs,
model: openai('gpt-4o'),
tools: { refund },
prompt: 'Refund order 42',
});Call it again with the same id after a crash: finished steps replay from the journal, the refund is not issued a second time, and only what never finished runs.
2. A registry: agents by name, work by your id#
Name the agent once and each piece of work with your own id — an order, a ticket — and the engine derives the run from it. A run now executes apart from the call that started it: a closed tab or a dropped connection ends the reading, not the run.
import { createGnl } from '@gnldev/durable';
const config = {
storage,
agents: {
support: { model: openai('gpt-4o'), system: 'You handle refunds. Ask before you refund.', tools: { refund } },
},
};
const gnl = createGnl(config);
const r = await gnl.run('support', { workKey: 'order-42', prompt: 'Refund order 42', resourceId: 'customer-7' });3. A server: every agent over HTTP#
The same config becomes a REST API with OpenAPI and server-sent events. In production it refuses to start without auth.
import { createRestApi } from '@gnldev/server';
import { roleAuth } from '@gnldev/auth';
import { serve } from '@hono/node-server';
const api = createRestApi(config, {
title: 'Support agents',
auth: roleAuth({ client: { token: process.env.GNL_CLIENT_TOKEN } }),
});
serve({ fetch: api.fetch, port: 3000 }); // POST /agents/support/run, /agents/support/stream, /openapi.jsonOn Express, Fastify, Koa, Nest or Next.js: the REST API page.
4. Studio: watch, approve, go back in time#
Studio reads the same journal: every run on a timeline, the calls waiting for approval, time-travel and fork, and a playground that runs the agents in your config.
npx gnl studio --config gnl.config.ts # Studio + the playground for the agents in your config5. The packages: take what you need#
Each package works with the rest and on its own, and a tool or a step it adds is journaled like the core's — replayed after a crash, never fired twice.
- @gnldev/memory — conversation history, working memory, recall
- @gnldev/workflow — multi-step processes with retries
- @gnldev/rag — answers from your documents
- @gnldev/evals — quality measured, and a gate in CI
- @gnldev/mcp — external tools over MCP
- @gnldev/queue — background jobs, schedules and events
- @gnldev/channels — agents on Slack and Telegram
- @gnldev/client — a typed client and React hooks
npm create gnl@latest my-agent sets up steps 2 to 4 in one go.Sources#
The samples on this page are typechecked against the packages as built, in the repository: docs/CORE-TO-FRAMEWORK.md