Docs menu
Dynamic agent networks
A router LLM decides at runtime which sub-agent runs; every decision freezes into the journal via CAS → resume/replay never re-calls the router, the same path is taken.
What it's for / when to use it#
With the static agent_<name> tool, you can give one agent to another as a tool — but which sub-agent gets called is fixed in the prompt flow. A network is the dynamic version of this: a router LLM (supervisor) decides on every turn, looking at the intermediate results so far, which one of the registered agents gets which task — or writes the final answer once enough has been gathered. It is the supervisor.network() pattern.
GNL's difference is determinism: every decision the router makes freezes into the <runId>:net:route:<i> key via CAS (applying the "freeze the winner" pattern from withModelFallbackto routing). When a run crashes and is resumed, the router is not called again — frozen decisions are read back, the same path is taken. Sub-agent step results also freeze to <runId>:net:step:<i>, so a completed sub-agent is skipped entirely on resume (the same synergy as the two-level durability in agent-as-tool).
Setup / definition#
Networks are defined in the createGnl configuration, under networks: . Each network takes a router model plus a list of registered agent names it can route to — when picking an agent, the router sees each agent's description(single source of truth; the same one used for agent-as-tool).
import { createGnl } from '@gnldev/durable';
const gnl = createGnl({
storage,
agents: {
researcher: { model, description: 'Gathers facts from the web / a knowledge base' },
writer: { model, description: 'Toplanan olgulardan taslak yazar' },
critic: { model, description: 'Reviews the draft and asks for corrections' },
},
networks: {
editorial: {
router: candidateModels, // spec / obje / fallback zinciri / dinamik
agents: ['researcher', 'writer', 'critic'],
system: 'Write a good article; loop with the critic when needed.',
maxIterations: 6, // ceiling on routing turns (default 6)
},
},
});Running it — runNetwork#
A network runs durably via gnl.runNetwork(name, ...) . Sub-agent call semantics are identical to agent-tool: model fallback freezes to a nested runId, run limits are inherited exactly the same way. Each sub-agent writes to its own nested journal (net:<runId>:<i>) → mid-crash resume is covered at the sub-agent level too.
const res = await gnl.runNetwork('editorial', {
runId: 'r1',
task: 'Write three paragraphs explaining the GNL exactly-once guarantee.',
});
// res => { runId, text, steps: [{ i, agent, task, text }], iterations, interrupts }
If a sub-agent hits a guard and is suspended (interrupts populated), that step is not frozen — the interruption propagates upward and gets marked suspended: { i, agent, task } . When called again after approval with the same runId , the router doesn't run since the route decision is already frozen; the sub-agent resumes from its own journal, and the step freezes only once it completes.
maxIterations cap (default 6) is exceeded, the router is forced to finalize — an unbounded router loop is structurally impossible. If the router still can't produce a valid final after 2 attempts, the (frozen) text of the last step is returned deterministically instead of crashing.Studio — the dynamic tree#
Studio's GET /runs/:id/network endpoint reads the route and step records from the journal and returns the network's dynamic tree (the Networks view). This is derived after the fact: getNetworkTrace extracts router decisions and executed steps from the journal — no live instrumentation needed, complete even after a crash.
import { getNetworkTrace } from '@gnldev/durable';
const tree = await getNetworkTrace(journal, 'r1');
// tree => { routes: [{ i, decision }], steps: [{ i, agent, task, text }] }API reference#
createGnl({ networks })networks: Record<string, NetworkConfig> — each network takes a router model + the routable agent names + optional system/maxIterations.
gnl.runNetwork(name, { runId, task, context?, limits?, approvals? }) → NetworkResult. The router picks a sub-agent or writes the final answer on every turn; decisions are CAS-frozen.
runNetworkRegistry-free core: freezes the route decision to <runId>:net:route:<i>, the step result to <runId>:net:step:<i>; interrupts propagate upward.
getNetworkTraceExtracts a network run's dynamic tree (routes + steps) from the journal — the source for Studio's Networks view.
NetworkResult{ runId, text, steps, iterations, stopped?, interrupts, suspended? }.
NetworkConfig{ router, agents, system?, maxIterations? } — a createGnl networks entry.
net:<runId>:<i> ) also show up as separate top-level runs in Studio's Runs list (the nested-run ontology) — the network tree links them hierarchically, and in the single-run inspector each one can be examined with its own journal timeline.