子エージェントを、独自の分離された SQLite ストレージを持つコロケーション Durable Objects として起動します。親は、子のメソッドを呼び出す型付き RPC スタブを受け取ります。子クラスのすべての公開メソッドは、Promise でラップされた戻り値型を持つリモートプロシージャコールとして呼び出せます。
1 人のユーザーまたは 1 つのエンティティが、チャット、ドキュメント、セッション、シャード、プロジェクトなど、開かれた集合の長寿命エージェントを所有する場合にサブエージェントを使います。各サブエージェントは独自の状態で並行実行され、親が検出、アクセス制御、ライフサイクルを調整します。
親チャットエージェントに、1 ターンの間に別のチャット対応エージェントをディスパッチさせ、その子の進行状況をインラインで描画したい場合は Agents as tools を使います。Agents as tools はサブエージェントの上に構築されますが、親側の実行レジストリ、ストリーミングの 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-09-20",
"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-09-20"
compatibility_flags = ["nodejs_compat"]
[[durable_objects.bindings]]
class_name = "Orchestrator"
name = "Orchestrator"
[[migrations]]
new_sqlite_classes = ["Orchestrator"]
tag = "v1"トップレベルの親エージェントだけが Durable Object バインディングとマイグレーションを必要とします。子エージェントは親のファセットとして作成されます。同じマシンを共有しますが、SQLite ストレージは完全に分離されます。
名前付きサブエージェントを取得または作成します。同じ名前への最初の呼び出しは、子の 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 |
この子インスタンスの一意な名前です。同じ名前は常に同じ子を返します。 |
SubAgentStub<T> を返します。T のすべてのユーザー定義メソッドを Promise を返すリモート呼び出しとして使える型付き RPC スタブです。
スタブは、子クラスに定義したすべての公開インスタンスメソッドを公開します。Agent から継承したメソッド(ライフサイクルフック、setState、broadcast、sql など)は除外されます。カスタムメソッドだけがスタブに現れます。
戻り値型は、まだ 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 名前空間としてバインドする必要があります - ファセット専用の子クラスは、同じクラスが他の場所でトップレベル Durable Object としてもバインドされない限り、
new_sqlite_classesへの登録は不要です - 入れ子のファセット親は、独自のトップレベル Durable Object バインディングを必要としません。ランタイムはルート親名前空間経由で入れ子の子を解決します
- 子クラス名を
Subにすることはできません。/sub/は入れ子ルートの URL 区切りとして予約されているためです
@cloudflare/vitest-plugin を使うテストでは、ctx.exports がファセット互換のクラス値を提供するように、ファセットクラスをテスト専用 Durable Object バインディングとして列挙する必要があることがあります。それらのファセットクラスは new_sqlite_classes に入れないでください。追加のバインディングはテスト用 wrangler.jsonc ファイルだけに置き、本番 Worker の要件ではありません。
実行中のサブエージェントを強制停止します。子はすぐに実行を止め、次の subAgent() 呼び出しで再起動します。ストレージは保持されます。殺されるのは実行中のインスタンスだけです。
class Agent {}class Agent {
abortSubAgent(cls: SubAgentClass, name: string, reason?: unknown): void;
}| パラメーター | 型 | 説明 |
|---|---|---|
cls |
SubAgentClass |
子の作成時に使った Agent サブクラス |
name |
string |
中止する子の名前 |
reason |
unknown |
保留中または将来の RPC 呼び出し元にスローされるエラー |
中止は推移的です。子が独自のサブエージェントを持っている場合、それらも中止されます。
子を中止し(実行中なら)、そのストレージを永続的に消去します。次の subAgent() 呼び出しは、空の SQLite を持つ新しいインスタンスを作成します。
class Agent {}class Agent {
deleteSubAgent(cls: SubAgentClass, name: string): void;
}| パラメーター | 型 | 説明 |
|---|---|---|
cls |
SubAgentClass |
子の作成時に使った Agent サブクラス |
name |
string |
削除する子の名前 |
削除は推移的です。子自身のサブエージェントも削除されます。
子が起動済みで、削除されていないかを確認します。フレームワークが維持する 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 });
}起動済みのサブエージェントを一覧します。任意でクラスでフィルタできます。行は作成順で返されます。
const chats = this.listSubAgents(Chat);
// [{ className: "Chat", name: "chat-abc", createdAt: 1700000000000 }]const chats = this.listSubAgents(Chat);
// [{ className: "Chat", name: "chat-abc", createdAt: 1700000000000 }]親でこのミドルウェアフックをオーバーライドし、フレームワークが子を起こす前に、受信する /sub/ リクエストをゲート、変更、または短絡します。onBeforeConnect と onBeforeRequest をミラーします。
フックは次を返せます。
| 戻り値 | 効果 |
|---|---|
void |
元のリクエストを子へ転送する |
Request |
変更したリクエストを転送する |
Response |
短絡し、子を起こさない |
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 アップグレードリクエストは、通常の HTTP リクエストと同じようにこのフックを通ります。変更した Request を返す場合は、元の WebSocket アップグレードヘッダーを保持します。
サブエージェントは、this.parentPath と this.selfPath を通じて親が誰かを知ります。
// 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 はルートが先なので、直接の親は常に parentPath.at(-1) です。トップレベルエージェントでは parentPath === [] です。
サブエージェントから parentAgent(Cls) を使い、直近の親への型付き RPC スタブを取得します。
const inbox = await this.parentAgent(Inbox);
await inbox.recordTurn(this.name, "...");const inbox = await this.parentAgent(Inbox);
await inbox.recordTurn(this.name, "...");parentAgent() は、その親自身がファセット専用サブエージェントでも、ルート側の RPC ブリッジを使って直接の親を解決します。これにより、入れ子の親クラスすべてをトップレベル Durable Object としてバインドせずに、直近の親へ型付きメソッド呼び出しができます。
祖父母とそれより上の祖先では、this.parentPath を反復し、getAgentByName() を直接呼び出します。バインディング名がクラス名と一致しない場合は、parentAgent() の代わりに getAgentByName(env.MY_BINDING, this.parentPath.at(-1)!.name) を呼び出します。
任意の useAgent 呼び出しに sub チェーンを付けて、子孫ファセットに接続します。
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 子への直接 WebSocket を開きます。その他の useAgent 機能は通常どおり動きます。状態同期、stub 呼び出し、@callable RPC、返されたソケット上の useAgentChat です。
buildAgentPath() を使い、Agent ID 向けの正規パス名を作成します。同じパス名が HTTP リクエストと WebSocket 接続に対応します。
import { buildAgentPath } from "agents";
const path = buildAgentPath(
[
{ className: "Inbox", name: userId },
{ className: "Chat", name: chatId },
],
{ leafPath: "/callbacks/job" },
);
// /agents/inbox/{userId}/sub/chat/{chatId}/callbacks/jobimport { buildAgentPath } from "agents";
const path = buildAgentPath(
[
{ className: "Inbox", name: userId },
{ className: "Chat", name: chatId },
],
{ leafPath: "/callbacks/job" },
);
// /agents/inbox/{userId}/sub/chat/{chatId}/callbacks/jobAgent 内では、this.selfPath を直接渡します。ルート Durable Object のバインディング名がクラス名と異なる場合は、オプションで rootBinding も渡します。コールバック、webhook、承認、非同期ジョブ完了向けに公開オリジンを追加するには buildAgentUrl() を使います。
import { buildAgentUrl } from "agents";
export class Chat extends Agent {
callbackUrl() {
return buildAgentUrl(this.env.PUBLIC_ORIGIN, this.selfPath, {
leafPath: "/callbacks/job",
});
}
async onRequest(request) {
if (new URL(request.url).pathname === "/callbacks/job") {
return this.handleJobCallback(request);
}
return new Response("Not found", { status: 404 });
}
}import { buildAgentUrl } from "agents";
export class Chat extends Agent<Env> {
callbackUrl() {
return buildAgentUrl(this.env.PUBLIC_ORIGIN, this.selfPath, {
leafPath: "/callbacks/job",
});
}
override async onRequest(request: Request) {
if (new URL(request.url).pathname === "/callbacks/job") {
return this.handleJobCallback(request);
}
return new Response("Not found", { status: 404 });
}
}受信リクエストを routeAgentRequest() に渡します。各祖先は、宛先がリクエストを受け取る前に onBeforeSubAgent を実行します。サブエージェント宛先では、ルーティングが入れ子の /sub/ セグメントを取り除くため、パス名は leafPath サフィックスになります。
buildAgentUrl() は HTTP(S) または WS(S) オリジンを受け付けます。オリジンに資格情報、パス名、クエリ、フラグメントを含めることはできません。コールバックのクエリパラメーターは、返された URL の searchParams プロパティで追加します。
ルート Agent 名は、すでに有効なパス名セグメントである必要があります。sub セグメントは、ルーティングプレフィックス、クラス名とバインディング名、ルート Agent 名で予約されています。ヘルパーは子孫名を URL エンコードします。スペース、Unicode 文字、/、その他の URL 予約文字も含みます。
独自にトップレベル URL 解析をする fetch ハンドラーでは、routeSubAgentRequest() を使い、すでに解決した親スタブからサブエージェントへリクエストをディスパッチします。
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 は、/sub/chat/chat-abc のようにサブエージェントの末尾を含む任意のパス名を取ります。buildAgentPath() の結果を直接渡せます。ヘルパーはそれを解析し、親の onBeforeSubAgent フックを実行し、リクエストをファセットへ転送します。
親 Durable Object の内側では、this.subAgent(Cls, name) が型付きスタブを返します。親の外側では 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() を使います。
各サブエージェントは独自の SQLite データベースを持ち、親および他のサブエージェントから完全に分離されます。親が this.sql に書き、子が 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;
}
}2 つの異なるクラスは同じユーザー向け名前を共有できます。独立して解決されます。内部キーはクラス名とファセット名の複合です。
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子の this.name プロパティはファセット名を返します(親の名前ではありません)。
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
}
}複数のサブエージェントを同時に実行します。
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;
}
}サブエージェントは独自のサブエージェントを起動でき、ツリーを形成します。
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 コールバックを渡し、サブエージェントから親へ結果をストリーミングします。
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);
}
}
}サブエージェントは独自のコールバックをスケジュールし、耐久ファイバーを実行できます。
| メソッド | サブエージェントでの動き |
|---|---|
schedule() / scheduleEvery() |
通常どおり動き、サブエージェント内でコールバックを実行する |
cancelSchedule() |
呼び出し元サブエージェントが所有するスケジュールに対して動く |
getScheduleById() / listSchedules() |
動き、呼び出し元サブエージェントにスコープされたスケジュールを返す |
keepAlive() / keepAliveWhile() |
ハートビートをトップレベル親へ委譲して動く |
runFiber() |
動く。ファイバー行とスナップショットは子の SQLite データベースに保存される |
setState() |
通常どおり動き、子自身のストレージに書く |
this.sql |
通常どおり動き、子自身の SQLite データベースを指す |
subAgent() |
動く。サブエージェントは独自の子を起動できる |
トップレベル親は、物理 Durable Object アラームを引き続き所有します。ファセットには独立したアラスロットがないためです。Agents SDK は、どの子が各スケジュール済みコールバックまたはリカバリーチェックを所有するかを記録し、親を起こし、作業を子へ戻します。コールバックはサブエージェントを this として実行するため、子の状態、SQLite ストレージ、getCurrentAgent() コンテキストを使います。
古い同期 API の getSchedule() と getSchedules() は、スケジュール行がトップレベル親に保存されるため、サブエージェント内ではスローします。代わりに getScheduleById() と listSchedules() を使います。
サブエージェント内で this.destroy() を呼び出すと、クリーンアップは親へ委譲されます。親はそのサブエージェントのスケジュールをキャンセルし、サブエージェントとその子孫のリカバリーメタデータを削除し、レジストリエントリを削除し、ランタイムに子ストレージの消去を依頼します。this.destroy() は fire-and-forget として扱います。サブエージェントの削除は、メソッドがきれいに戻る前にその isolate を中止することがあるためです。
サブエージェントは this.runWorkflow() で Workflows を開始することもできます。Workflow の追跡はサブエージェントの SQLite データベースにローカルで、AgentWorkflow.agent は RPC、コールバック、状態更新、ブロードキャストを元のサブエージェントへ戻します。親エージェントは、子が開始した Workflow を自動では一覧または制御しません。
SubAgentStub<T> はユーザー定義の子メソッドだけを公開するため、getWorkflow()、approveWorkflow()、terminateWorkflow() などの制御向けに子のラッパーメソッドを追加し、await this.subAgent(Child, name) 経由でそれらのラッパーを呼び出します。サブエージェントから runWorkflow(..., { agentBinding }) を渡す場合は、子のバインディング名ではなくルート Agent のバインディング名を使います。
サブエージェントの Workflow 起点では、AgentWorkflow.agent は RPC 専用です。Agent メソッドの呼び出しには使えますが、外部の HTTP または WebSocket ルーティングには this.agent.fetch() ではなく、routeSubAgentRequest() または入れ子の /agents/{parent}/{name}/sub/{child}/{name} URL 形状を使います。
マルチセッションチャットの例
- Think — サブエージェント経由で AI ターンをストリーミングする
chat()メソッド - 長時間実行エージェント — 数週間にわたるエージェント寿命の文脈でのサブエージェント委譲
- 呼び出し可能メソッド —
@callableとサービスバインディング経由の RPC - Agents as tools — Think または
AIChatAgentサブエージェントを保持・ストリーミングするツールとして実行する - タスクをスケジュールする — トップレベルエージェントとサブエージェント向けのスケジューリングプリミティブ