GNL
Docs menu
Core · Free@gnldev/durable

Agent registry (createGnl)

The central entry point that defines agents, tools, and workflows in a single configuration and runs them durably; the server and Studio always consume this same registry.

What it's for / when to use it#

createGnl gathers every agent, shared tool, and workflow in an app into a single configuration object and turns it into a durable, callable-by-name runner. Storage (journal) is tied to this configuration; the returned gnl object is used the same way both directly from code (gnl.run(...)) and through createRestApi and createStudioApp.

Concrete scenario: you want to build a Star Wars encyclopedia agent — it's defined in a single place together with its model, system prompt, and tools; both the REST API and Studio read this definition to run the same agent and write results to the same journal.

Setup / import#

The main export comes from the @gnldev/durable package. Storage requires a sub-export (not the root package) — if you're using SQLite, import SqliteStorage from @gnldev/durable/sqlite.

import
import { SqliteStorage } from '@gnldev/durable/sqlite';
import { createGnl, toJournal } from '@gnldev/durable';
import type { CreateGnlConfig } from '@gnldev/durable';

Step-by-step usage#

1) Set up your storage, 2) define your agents under agents (model, system, tools, maxSteps), 3) build the registry with createGnl(config), 4) run it under a runId with gnl.run(name, opts).

swapi-free/src/index.ts (real usage)
const storage = new SqliteStorage(process.env.DB_PATH ?? 'swapi-free.db');
const config: CreateGnlConfig = {
  storage,
  agents: {
    starwars: { model: buildModel(), system: SYSTEM, tools: makeSwapiTools(), maxSteps: 4 },
  },
};

const gnl = createGnl(config);
await gnl.run('starwars', { runId: 'demo-1', prompt: 'Who is Luke Skywalker?' });

The same config object is also given to the REST API and Studio — all three share the same journal and the same agent definition:

app.route('/api', createRestApi(config, { title: 'SWAPI Free', auth }));

app.route('/studio', createStudioApp({
  reader: toJournal(storage.runs),
  apiBase: '/studio',
  gnl: createStudioRunner(gnl, config, { toJsonSchema: aiToolSchema }),
  auth,
}));

The runId passed to gnl.run is also the key to the exactly-once and resume guarantee: if called again with the same runId, the run continues where it left off (see deterministic replay).

API reference#

fncreateGnl

Returns { agent, run, stream, listWorkflows, runWorkflow } from a configuration — an app's entire agent/workflow surface.

typeCreateGnlConfig

storage or journal, agents (Record<name, AgentConfig>), tools, memory/memoryFactory, processors, workflows fields.

typeAgentConfig

model, tools, system, guard, maxSteps, processors, agents (sub-agent names), scorers — all of these can be a DynamicArg.

typeRunOptions

gnl.run/gnl.stream parameters: runId, prompt|messages, threadId, resourceId, approvals, context, model/temperature/topP/system override.

typeRequestContext

The request context (org/role/user) — passed to DynamicArg functions.

typeDynamicArg<T>

Either a fixed value OR a function that receives RequestContext — used for model/system/tools.

typeWorkflowLike

The structural interface of workflows registered under config.workflows (build/run/runResumable).

fncreateAgentTool

Internal helper that turns the sub-agents listed in AgentConfig.agents into agent_<name> tools (agent-as-tool).

Sub-agent network
If you give an agent's agents field the names of other registered agents, each one is automatically exposed as a tool named agent_&lt;name&gt; (handoff); this runs exactly-once with a two-level journal.
Default
If maxSteps isn't given, 12 steps are used (stepCountIs). If model is given as an array instead of a single value, it automatically falls back to the deterministic model fallback chain (see Model routing & fallback).

Related topics#