将子 Agent 生成为同址 Durable Object,拥有隔离的 SQLite 存储。父 Agent 获得带类型的 RPC stub 以调用子 Agent 方法——子类每个公开方法均可作为 Promise 包装返回类型的远程过程调用。
当单个用户或实体拥有开放式长期 Agent 集合(如聊天、文档、会话、分片或项目)时使用子 agent。每个子 agent 并行运行并拥有独立状态,父 Agent 协调发现、访问控制与生命周期。
若希望父聊天 Agent 在单轮次内调度另一具备聊天能力的 Agent 并内联渲染子 Agent 进度,使用 Agent 作为工具。Agent 作为工具基于子 agent,并增加父侧运行注册表、流式 agent-tool-event 帧、重放、取消与清理。
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();
}
}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();
}
}两个类都必须从 Worker 入口点导出。子类无需单独的 Durable Object 绑定 — 子类通过 ctx.exports 自动发现。
{
"$schema": "./node_modules/wrangler/config-schema.json",
// Set this to today's date
"compatibility_date": "2026-08-17",
"compatibility_flags": [
"nodejs_compat"
],
"durable_objects": {
"bindings": [
{
"class_name": "Orchestrator",
"name": "Orchestrator"
}
]
},
"migrations": [
{
"new_sqlite_classes": [
"Orchestrator"
],
"tag": "v1"
}
]
}# Set this to today's date
compatibility_date = "2026-08-17"
compatibility_flags = ["nodejs_compat"]
[[durable_objects.bindings]]
class_name = "Orchestrator"
name = "Orchestrator"
[[migrations]]
new_sqlite_classes = ["Orchestrator"]
tag = "v1"仅顶层父 Agent 需要 Durable Object 绑定与迁移。子 Agent 作为父 Agent 的 facet 创建 — 它们共享同一机器,但拥有完全隔离的 SQLite 存储。
获取或创建命名子 Agent。给定名称的首次调用会触发子 Agent 的 onStart()。后续调用返回现有实例。
class Agent {}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 的远程调用使用。
stub 暴露你在子类上定义的所有公开实例方法。从 Agent 继承的方法(生命周期钩子、setState、broadcast、sql 等)被排除 — 仅你的自定义方法会出现在 stub 上。
若返回类型尚未是 Promise,会自动包装为 Promise:
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)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)- 子类必须继承
Agent - 子类必须从 Worker 入口点导出(
export class MyChild extends Agent) - 导出名称必须与类名一致 — 不支持
export { Foo as Bar } - 顶层父类必须在
wrangler.jsonc中绑定为 Durable Object 命名空间 - 仅 facet 的子类无需在
new_sqlite_classes下注册,除非同一类在其他地方也作为顶层 Durable Object 绑定 - 嵌套 facet 父类无需自己的顶层 Durable Object 绑定;运行时通过根父命名空间解析嵌套子 Agent
- 子类名不能为
Sub,因为/sub/保留为嵌套路由的 URL 分隔符
使用 @cloudflare/vitest-pool-workers 的测试可能需要将 facet 类列为仅测试用的 Durable Object 绑定,以便 ctx.exports 提供 facet 兼容的类值。这些 facet 类应排除在 new_sqlite_classes 之外;额外绑定仅属于测试 wrangler.jsonc 文件,不是生产 Worker 要求。
强制停止正在运行的子 Agent。子 Agent 立即停止执行,并在下次 subAgent() 调用时重启。存储会保留 — 仅终止正在运行的实例。
class Agent {}class Agent {
abortSubAgent(cls: SubAgentClass, name: string, reason?: unknown): void;
}| 参数 | 类型 | 描述 |
|---|---|---|
cls |
SubAgentClass |
创建子 Agent 时使用的 Agent 子类 |
name |
string |
要中止的子 Agent 名称 |
reason |
unknown |
抛给任何挂起或未来 RPC 调用方的错误 |
中止具有传递性 — 若子 Agent 有自己的子 Agent,它们也会被中止。
中止子 Agent(若正在运行)并永久清除其存储。下次 subAgent() 调用会创建具有空 SQLite 的新实例。
class Agent {}class Agent {
deleteSubAgent(cls: SubAgentClass, name: string): void;
}| 参数 | 类型 | 描述 |
|---|---|---|
cls |
SubAgentClass |
创建子 Agent 时使用的 Agent 子类 |
name |
string |
要删除的子 Agent 名称 |
删除具有传递性 — 子 Agent 自己的子 Agent 也会被删除。
检查子 Agent 是否已生成且未被删除。由框架维护的 SQLite 注册表支持。
if (!this.hasSubAgent(Chat, id)) {
return new Response("Not found", { status: 404 });
}if (!this.hasSubAgent(Chat, id)) {
return new Response("Not found", { status: 404 });
}列出已生成的子 Agent,可按类过滤。按创建顺序返回行。
const chats = this.listSubAgents(Chat);
// [{ className: "Chat", name: "chat-abc", createdAt: 1700000000000 }]const chats = this.listSubAgents(Chat);
// [{ className: "Chat", name: "chat-abc", createdAt: 1700000000000 }]在父 Agent 上重写此中间件钩子,以在框架唤醒子 Agent 之前门控、修改或短路传入的 /sub/ 请求。它镜像 onBeforeConnect 与 onBeforeRequest。
钩子可返回:
| 返回值 | 效果 |
|---|---|
void |
将原始请求转发给子 Agent |
Request |
转发修改后的请求 |
Response |
短路响应,不唤醒子 Agent |
export class Inbox extends Agent {
async onBeforeSubAgent(_request, { className, name }) {
// Strict registry gate: only allow clients to reach chats that were created.
if (!this.hasSubAgent(className, name)) {
return new Response(`${className} "${name}" not found`, {
status: 404,
});
}
}
}export class Inbox extends Agent {
override async onBeforeSubAgent(_request, { className, name }) {
// Strict registry gate: only allow clients to reach chats that were created.
if (!this.hasSubAgent(className, name)) {
return new Response(`${className} "${name}" not found`, {
status: 404,
});
}
}
}WebSocket upgrade 请求与 plain HTTP 请求一样流经此 hook。若返回修改后的 Request,请保留原始 WebSocket upgrade 头。
子 Agent 通过 this.parentPath 与 this.selfPath 知道其父 Agent 是谁。
// Inside a Chat spawned by Inbox:
this.parentPath;
// [{ className: "Inbox", name: "user-123" }]
this.selfPath;
// [
// { className: "Inbox", name: "user-123" },
// { className: "Chat", name: "chat-abc" }
// ]// Inside a Chat spawned by Inbox:
this.parentPath;
// [{ className: "Inbox", name: "user-123" }]
this.selfPath;
// [
// { className: "Inbox", name: "user-123" },
// { className: "Chat", name: "chat-abc" }
// ]parentPath 以根优先,因此直接父 Agent 始终是 parentPath.at(-1)。顶层 Agent 的 parentPath === []。
从子 Agent 使用 parentAgent(Cls) 获取其直接父 Agent 的类型化 RPC stub:
const inbox = await this.parentAgent(Inbox);
await inbox.recordTurn(this.name, "...");const inbox = await this.parentAgent(Inbox);
await inbox.recordTurn(this.name, "...");parentAgent() 即使直接父 Agent 本身也是仅 facet 的子 Agent,也能解析直接父 Agent,底层使用根侧 RPC 桥。这样无需将每个嵌套父类都绑定为顶层 Durable Object,即可对直接父 Agent 进行类型化方法调用。
对于祖父及更上层祖先,迭代 this.parentPath 并直接调用 getAgentByName()。若绑定名称与类名不匹配,调用 getAgentByName(env.MY_BINDING, this.parentPath.at(-1)!.name) 而非 parentAgent()。
在任何 useAgent 调用上扩展 sub 链以连接到后代 facet:
const chat = useAgent({
agent: "Inbox",
name: userId,
sub: [{ agent: "Chat", name: chatId }],
});const chat = useAgent({
agent: "Inbox",
name: userId,
sub: [{ agent: "Chat", name: chatId }],
});钩子构建类似 /agents/inbox/user-123/sub/chat/chat-abc 的 URL,并打开到 Chat 子 Agent 的直接 WebSocket。其他所有 useAgent 功能照常工作:状态同步、stub 调用、@callable RPC,以及在返回的套接字上使用 useAgentChat。
对于自行进行顶层 URL 解析的 fetch 处理程序,使用 routeSubAgentRequest() 从已解析的父 stub 将请求分派到子 Agent:
import { getAgentByName, routeSubAgentRequest } from "agents";
export default {
async fetch(request, env) {
const url = new URL(request.url);
const match = url.pathname.match(/^\/api\/u\/([^/]+)(\/.*)$/);
if (!match) return new Response("Not found", { status: 404 });
const [, userId, rest] = match;
const parent = await getAgentByName(env.Inbox, userId);
return routeSubAgentRequest(request, parent, { fromPath: rest });
},
};import { getAgentByName, routeSubAgentRequest } from "agents";
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
const match = url.pathname.match(/^\/api\/u\/([^/]+)(\/.*)$/);
if (!match) return new Response("Not found", { status: 404 });
const [, userId, rest] = match;
const parent = await getAgentByName(env.Inbox, userId);
return routeSubAgentRequest(request, parent, { fromPath: rest });
},
};fromPath 接受子 Agent 尾部路径,例如 /sub/chat/chat-abc。辅助函数解析它、运行父 Agent 的 onBeforeSubAgent 钩子,并将请求转发到 facet。
在父 Durable Object 内部,this.subAgent(Cls, name) 返回类型化 stub。在父 Agent 外部,使用 getSubAgentByName():
import { getAgentByName, getSubAgentByName } from "agents";
const inbox = await getAgentByName(env.Inbox, userId);
const chat = await getSubAgentByName(inbox, Chat, chatId);
await chat.addMessage({ role: "user", content: "hello" });import { getAgentByName, getSubAgentByName } from "agents";
const inbox = await getAgentByName(env.Inbox, userId);
const chat = await getSubAgentByName(inbox, Chat, chatId);
await chat.addMessage({ role: "user", content: "hello" });getSubAgentByName() 返回仅 RPC 的代理。方法调用可用,但 .fetch() 会抛出。HTTP 与 WebSocket 转发请使用 routeSubAgentRequest()。
每个子 Agent 拥有完全独立于父 Agent 及其他子 Agent 的 SQLite 数据库。父 Agent 写入 this.sql 与子 Agent 写入 this.sql 操作的是不同数据库:
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;
}
}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;
}
}两个不同类可共享相同的面向用户的名称 — 它们独立解析。内部键是类名与 facet 名称的组合:
const counter = await this.subAgent(Counter, "shared-name");
const logger = await this.subAgent(Logger, "shared-name");
// These are two separate sub-agents with separate storageconst 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 的名称):
export class Child extends Agent {
getName() {
return this.name; // Returns "shared-name", not the parent's ID
}
}export class Child extends Agent {
getName(): string {
return this.name; // Returns "shared-name", not the parent's ID
}
}并发运行多个子 Agent:
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;
}
}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;
}
}子 Agent 可以生成自己的子 Agent,形成树形结构:
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 };
}
}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 };
}
}传递 RpcTarget 回调,将子 Agent 的结果流式传回父 Agent:
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);
}
}
}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);
}
}
}子 Agent 可以调度自己的回调并运行持久 fiber:
| 方法 | 子 Agent 中的行为 |
|---|---|
schedule() / scheduleEvery() |
正常工作,在子 Agent 内运行回调 |
cancelSchedule() |
适用于调用子 Agent 拥有的调度 |
getScheduleById() / listSchedules() |
正常工作,返回限定于调用子 Agent 的调度 |
keepAlive() / keepAliveWhile() |
通过将心跳委托给顶层父 Agent 工作 |
runFiber() |
正常工作,fiber 行与快照存储在子 Agent 的 SQLite 数据库中 |
setState() |
正常工作,写入子 Agent 自己的存储 |
this.sql |
正常工作,指向子 Agent 自己的 SQLite 数据库 |
subAgent() |
正常工作,子 Agent 可以生成自己的子 Agent |
顶层父 Agent 仍拥有物理 Durable Object 闹钟,因为 facet 没有独立的闹钟槽。Agents SDK 记录哪个子 Agent 拥有每个已调度回调或恢复检查,唤醒父 Agent,并将工作路由回子 Agent。回调仍以子 Agent 作为 this 运行,因此使用子 Agent 的状态、SQLite 存储与 getCurrentAgent() 上下文。
较旧的同步 getSchedule() 与 getSchedules() API 在子 Agent 内会抛出,因为已调度行存储在顶层父 Agent 上。请改用 getScheduleById() 与 listSchedules()。
在子 Agent 内调用 this.destroy() 会将清理委托给父 Agent。父 Agent 取消该子 Agent 的调度、移除子 Agent 及其后代的恢复元数据、移除注册表条目,并请求运行时清除子 Agent 存储。将 this.destroy() 视为即发即忘,因为删除子 Agent 可能在方法正常返回前中止其 isolate。
子 Agent 也可使用 this.runWorkflow() 启动 Workflows。Workflow 跟踪限定于子 Agent 的 SQLite 数据库,AgentWorkflow.agent 将 RPC、回调、状态更新与广播路由回发起的子 Agent。父 Agent 不会自动列出或控制子 Agent 启动的 Workflow。
由于 SubAgentStub<T> 仅暴露用户定义的子方法,为 getWorkflow()、approveWorkflow() 或 terminateWorkflow() 等控制添加子包装方法,然后通过 await this.subAgent(Child, name) 调用这些包装。若从子 Agent 传递 runWorkflow(..., { agentBinding }),使用根 Agent 绑定名称,而非子绑定名称。
对于子 Agent Workflow 来源,AgentWorkflow.agent 仅支持 RPC。用它调用 Agent 方法,但外部 HTTP 或 WebSocket 路由请使用 routeSubAgentRequest() 或嵌套 URL 形状 /agents/{parent}/{name}/sub/{child}/{name},而非 this.agent.fetch()。
多会话聊天示例
- Think — 通过子 Agent 流式传输 AI 轮次的
chat()方法 - 长期运行 Agent — 多周 Agent 生命周期中的子 Agent 委托
- 可调用方法 — 通过
@callable与服务绑定的 RPC - Agent 作为工具 — 将 Think 或
AIChatAgent子 Agent 作为保留、流式的工具运行 - 调度任务 — 顶层 Agent 与子 Agent 的调度原语