Sub-agents
将子 agent 作为同址(co-located)的 Durable Object 派生出来,它们各自拥有独立的 SQLite 存储。父 agent 会得到一个类型化的 RPC stub,用于调用子 agent 的方法 —— 子类的每一个公开方法都可以作为远程过程调用,返回值会被包装为 Promise。
快速开始
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();
}
}
Explain Code
TypeScript
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: string) {
const results = await fetch(`https://api.example.com/search?q=${query}`);
return results.json();
}
}
Explain Code
两个类都必须从 worker 的入口点导出。无需为子类单独配置 Durable Object binding —— 它们会通过 ctx.exports 被自动发现。
JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
// Set this to today's date
"compatibility_date": "2026-04-29",
"compatibility_flags": [
"nodejs_compat",
"experimental"
],
"durable_objects": {
"bindings": [
{
"class_name": "Orchestrator",
"name": "Orchestrator"
}
]
},
"migrations": [
{
"new_sqlite_classes": [
"Orchestrator"
],
"tag": "v1"
}
]
}
Explain Code
TOML
# Set this to today's date
compatibility_date = "2026-04-29"
compatibility_flags = ["nodejs_compat", "experimental"]
[[durable_objects.bindings]]
class_name = "Orchestrator"
name = "Orchestrator"
[[migrations]]
new_sqlite_classes = ["Orchestrator"]
tag = "v1"
Explain Code
只有父 agent 需要 Durable Object binding 与 migration。子 agent 是父 agent 的 facet —— 它们与父 agent 共享同一台机器,但拥有完全独立的 SQLite 存储。
subAgent
获取或创建一个具名的子 agent。第一次以某个名称调用时,会触发该子 agent 的 onStart()。后续调用会返回已有的实例。
JavaScript
class Agent {}
TypeScript
class Agent {
async subAgent<T extends Agent>(
cls: SubAgentClass<T>,
name: string,
): Promise<SubAgentStub<T>>;
}
| 参数 | 类型 | 描述 |
|---|---|---|
| cls | SubAgentClass<T> | Agent 的子类。必须从 worker 入口点导出,且导出名必须与类名一致。 |
| name | string | 该子实例的唯一名称。同一名称始终返回同一个子 agent。 |
返回一个 SubAgentStub<T> —— 一个类型化的 RPC stub,其中 T 上每一个用户自定义方法都可作为返回 Promise 的远程调用。
SubAgentStub
stub 暴露子类上所有用户自定义的公开实例方法。从 Agent 继承而来的方法(生命周期钩子、setState、broadcast、sql 等)不会出现在 stub 上 —— 只有你的自定义方法会出现。
返回类型若不是 Promise,会被自动包装为 Promise:
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)
Explain Code
TypeScript
class MyChild extends Agent {
greet(name: string): string {
return `Hello, ${name}`;
}
async fetchData(url: string): Promise<unknown> {
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)
Explain Code
要求
- 子类必须继承
Agent - 子类必须从 worker 入口点导出(
export class MyChild extends Agent) - 导出名必须与类名一致 ——
export { Foo as Bar }不被支持
abortSubAgent
强制停止一个正在运行的子 agent。子 agent 会立即停止执行,并在下一次 subAgent() 调用时重新启动。存储会被保留 —— 只是杀掉运行中的实例。
JavaScript
class Agent {}
TypeScript
class Agent {
abortSubAgent(cls: SubAgentClass, name: string, reason?: unknown): void;
}
| 参数 | 类型 | 描述 |
|---|---|---|
| cls | SubAgentClass | 创建该子 agent 时使用的 Agent 子类 |
| name | string | 要中止的子 agent 名称 |
| reason | unknown | 抛给所有正在或后续调用 RPC 的调用者的错误 |
中止具有传递性 —— 如果该子 agent 自己也有子 agent,它们也会被中止。
deleteSubAgent
中止子 agent(若运行中)并永久清除其存储。下一次 subAgent() 调用会创建一个 SQLite 为空的全新实例。
JavaScript
class Agent {}
TypeScript
class Agent {
deleteSubAgent(cls: SubAgentClass, name: string): void;
}
| 参数 | 类型 | 描述 |
|---|---|---|
| cls | SubAgentClass | 创建该子 agent 时使用的 Agent 子类 |
| name | string | 要删除的子 agent 名称 |
删除具有传递性 —— 该子 agent 自己的子 agent 也会被删除。
存储隔离
每个子 agent 都有自己的 SQLite 数据库,与父 agent 以及其他子 agent 完全隔离。父 agent 写入 this.sql 与子 agent 写入 this.sql 操作的是不同的数据库:
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;
}
}
Explain Code
TypeScript
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: string): Promise<number> {
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<{
value: number;
}>`SELECT value FROM counters WHERE key = ${key}`.one();
return row?.value ?? 0;
}
}
Explain Code
命名与身份
两个不同的类可以共用同一个对外名称 —— 它们各自独立解析。内部 key 是类名与 facet 名的组合:
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
TypeScript
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
子 agent 的 this.name 属性返回的是 facet 名(不是父 agent 的名字):
JavaScript
export class Child extends Agent {
getName() {
return this.name; // Returns "shared-name", not the parent's ID
}
}
TypeScript
export class Child extends Agent {
getName(): string {
return this.name; // Returns "shared-name", not the parent's ID
}
}
模式
并行子 agent
并发运行多个子 agent:
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;
}
}
Explain Code
TypeScript
export class Orchestrator extends Agent {
async runAll(queries: string[]) {
const results = await Promise.all(
queries.map(async (query, i) => {
const worker = await this.subAgent(Researcher, `research-${i}`);
return worker.search(query);
}),
);
return results;
}
}
Explain Code
嵌套子 agent
子 agent 可以派生自己的子 agent,形成一棵树:
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 };
}
}
Explain Code
TypeScript
export class Manager extends Agent {
async delegate(task: string) {
const team = await this.subAgent(TeamLead, "team-a");
return team.assign(task);
}
}
export class TeamLead extends Agent {
async assign(task: string) {
const worker = await this.subAgent(Worker, "worker-1");
return worker.execute(task);
}
}
export class Worker extends Agent {
async execute(task: string) {
return { completed: task };
}
}
Explain Code
回调流式传输
将一个 RpcTarget 回调传给子 agent,可以将结果流式返回给父 agent:
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);
}
}
}
Explain Code
TypeScript
import { RpcTarget } from "cloudflare:workers";
class StreamCollector extends RpcTarget {
chunks: string[] = [];
onChunk(text: string) {
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: string, callback: StreamCollector) {
const chunks = ["Once ", "upon ", "a ", "time..."];
for (const chunk of chunks) {
callback.onChunk(chunk);
}
}
}
Explain Code
限制
子 agent 作为父 Durable Object 的 facet 运行,部分 Agent 方法在子 agent 中不可用:
| 方法 | 在子 agent 中的行为 |
|---|---|
| schedule() | 抛出 “not supported in sub-agents” |
| cancelSchedule() | 抛出 “not supported in sub-agents” |
| keepAlive() | 抛出 “not supported in sub-agents” |
| setState() | 正常工作(写入子 agent 自己的存储) |
| this.sql | 正常工作(子 agent 自己的 SQLite) |
| subAgent() | 可用 —— 子 agent 可以派生自己的子 agent |
对于需要调度的工作,可以由父 agent 调度任务,在调度触发时再委派给子 agent。
相关
- Think ——
chat()方法,通过子 agent 流式进行 AI 回合 - Long-running agents —— 在数周生命周期的 agent 场景下进行子 agent 委派
- Callable methods —— 通过
@callable与 service binding 进行 RPC - Chat agents —— 用于进程内 AI SDK 子调用的
ToolLoopAgent