Skip to content

Sub-agents

Spawn child agents as co-located Durable Objects with their own isolated SQLite storage. The parent gets a typed RPC stub for calling methods on the child — every public method on the child class is callable as a remote procedure call with Promise-wrapped return types.

Quick start

JavaScript
import { Agent } from "agents";
export class Orchestrator extends Agent {
async delegateWork() {
const researcher = await this.subAgent(Researcher, "research-1");
const findings = await researcher.search("cloudflare agents sdk");
return findings;
}
}
export class Researcher extends Agent {
async search(query) {
const results = await fetch(`https://api.example.com/search?q=${query}`);
return results.json();
}
}

Both classes must be exported from the worker entry point. No separate Durable Object bindings are needed — child classes are discovered automatically via ctx.exports.

JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
// Set this to today's date
"compatibility_date": "2026-04-16",
"compatibility_flags": [
"nodejs_compat",
"experimental"
],
"durable_objects": {
"bindings": [
{
"class_name": "Orchestrator",
"name": "Orchestrator"
}
]
},
"migrations": [
{
"new_sqlite_classes": [
"Orchestrator"
],
"tag": "v1"
}
]
}

Only the parent agent needs a Durable Object binding and migration. Child agents are created as facets of the parent — they share the same machine but have fully isolated SQLite storage.

subAgent

Get or create a named sub-agent. The first call for a given name triggers the child's onStart(). Subsequent calls return the existing instance.

JavaScript
class Agent {}
ParameterTypeDescription
clsSubAgentClass<T>The Agent subclass. Must be exported from the worker entry point, and the export name must match the class name.
namestringUnique name for this child instance. The same name always returns the same child.

Returns a SubAgentStub<T> — a typed RPC stub where every user-defined method on T is available as a Promise-returning remote call.

SubAgentStub

The stub exposes all public instance methods you define on the child class. Methods inherited from Agent (lifecycle hooks, setState, broadcast, sql, and so on) are excluded — only your custom methods appear on the stub.

Return types are automatically wrapped in Promise if they are not already:

JavaScript
class MyChild extends Agent {
greet(name) {
return `Hello, ${name}`;
}
async fetchData(url) {
return fetch(url).then((r) => r.json());
}
}
// On the stub:
// greet(name: string) => Promise<string> (sync → wrapped)
// fetchData(url: string) => Promise<unknown> (already async → unchanged)

Requirements

  • The child class must extend Agent
  • The child class must be exported from the worker entry point (export class MyChild extends Agent)
  • The export name must match the class name — export { Foo as Bar } is not supported

abortSubAgent

Forcefully stop a running sub-agent. The child stops executing immediately and restarts on the next subAgent() call. Storage is preserved — only the running instance is killed.

JavaScript
class Agent {}
ParameterTypeDescription
clsSubAgentClassThe Agent subclass used when creating the child
namestringName of the child to abort
reasonunknownError thrown to any pending or future RPC callers

Abort is transitive — if the child has its own sub-agents, they are also aborted.

deleteSubAgent

Abort the child (if running) and permanently wipe its storage. The next subAgent() call creates a fresh instance with empty SQLite.

JavaScript
class Agent {}
ParameterTypeDescription
clsSubAgentClassThe Agent subclass used when creating the child
namestringName of the child to delete

Deletion is transitive — the child's own sub-agents are also deleted.

Storage isolation

Each sub-agent has its own SQLite database, completely isolated from the parent and from other sub-agents. A parent writing to this.sql and a child writing to this.sql operate on different databases:

JavaScript
export class Parent extends Agent {
async demonstrate() {
this.sql`INSERT INTO parent_data (key, value) VALUES ('color', 'blue')`;
const child = await this.subAgent(Child, "child-1");
await child.increment("clicks");
// Parent's SQL and child's SQL are completely separate
}
}
export class Child extends Agent {
async increment(key) {
this
.sql`CREATE TABLE IF NOT EXISTS counters (key TEXT PRIMARY KEY, value INTEGER DEFAULT 0)`;
this
.sql`INSERT INTO counters (key, value) VALUES (${key}, 1) ON CONFLICT(key) DO UPDATE SET value = value + 1`;
const row = this.sql`SELECT value FROM counters WHERE key = ${key}`.one();
return row?.value ?? 0;
}
}

Naming and identity

Two different classes can share the same user-facing name — they are resolved independently. The internal key is a composite of class name and facet name:

JavaScript
const counter = await this.subAgent(Counter, "shared-name");
const logger = await this.subAgent(Logger, "shared-name");
// These are two separate sub-agents with separate storage

The child's this.name property returns the facet name (not the parent's name):

JavaScript
export class Child extends Agent {
getName() {
return this.name; // Returns "shared-name", not the parent's ID
}
}

Patterns

Parallel sub-agents

Run multiple sub-agents concurrently:

JavaScript
export class Orchestrator extends Agent {
async runAll(queries) {
const results = await Promise.all(
queries.map(async (query, i) => {
const worker = await this.subAgent(Researcher, `research-${i}`);
return worker.search(query);
}),
);
return results;
}
}

Nested sub-agents

Sub-agents can spawn their own sub-agents, forming a tree:

JavaScript
export class Manager extends Agent {
async delegate(task) {
const team = await this.subAgent(TeamLead, "team-a");
return team.assign(task);
}
}
export class TeamLead extends Agent {
async assign(task) {
const worker = await this.subAgent(Worker, "worker-1");
return worker.execute(task);
}
}
export class Worker extends Agent {
async execute(task) {
return { completed: task };
}
}

Callback streaming

Pass an RpcTarget callback to stream results from a sub-agent back to the parent:

JavaScript
import { RpcTarget } from "cloudflare:workers";
class StreamCollector extends RpcTarget {
chunks = [];
onChunk(text) {
this.chunks.push(text);
}
}
export class Parent extends Agent {
async streamFromChild() {
const child = await this.subAgent(Streamer, "streamer-1");
const collector = new StreamCollector();
await child.generate("Write a poem", collector);
return collector.chunks;
}
}
export class Streamer extends Agent {
async generate(prompt, callback) {
const chunks = ["Once ", "upon ", "a ", "time..."];
for (const chunk of chunks) {
callback.onChunk(chunk);
}
}
}

Limitations

Sub-agents run as facets of the parent Durable Object. Some Agent methods are not available in sub-agents:

MethodBehavior in sub-agent
schedule()Throws "not supported in sub-agents"
cancelSchedule()Throws "not supported in sub-agents"
keepAlive()Throws "not supported in sub-agents"
setState()Works normally (writes to the child's own storage)
this.sqlWorks normally (child's own SQLite)
subAgent()Works — sub-agents can spawn their own children

For scheduled work, have the parent schedule the task and delegate to the sub-agent when the schedule fires.

  • Thinkchat() method for streaming AI turns through sub-agents
  • Long-running agents — sub-agent delegation in the context of multi-week agent lifetimes
  • Callable methods — RPC via @callable and service bindings
  • Chat agentsToolLoopAgent for in-process AI SDK sub-calls