INTEGRITY Документация

Дочерние агенты

Порождайте дочерних агентов в виде совместно размещённых Durable Objects с собственным изолированным хранилищем SQLite. Родитель получает типизированную RPC-заглушку для вызова методов дочернего агента: каждый публичный метод класса потомка становится доступен как удалённый вызов процедуры с типами возврата, обёрнутыми в Promise.

Используйте дочерних агентов, если один пользователь или сущность владеет открытым набором долгоживущих агентов, таких как чаты, документы, сессии, шарды или проекты. Каждый дочерний агент выполняется параллельно с собственным состоянием, пока родительский агент координирует обнаружение, контроль доступа и жизненный цикл.

Если вы хотите, чтобы родительский чат-агент в рамках одного хода запускал другого агента с поддержкой чата и отображал прогресс этого дочернего агента прямо в потоке, используйте Agents как инструменты. Агенты как инструменты строятся на основе суб-агентов, но добавляют реестр запусков на стороне родителя, стриминг 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-28",
  "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-28"
compatibility_flags = ["nodejs_compat"]

[[durable_objects.bindings]]
class_name = "Orchestrator"
name = "Orchestrator"

[[migrations]]
new_sqlite_classes = ["Orchestrator"]
tag = "v1"

Только агенту верхнего уровня, родителю, нужны привязка и миграция Durable Object. Дочерние агенты создаются как грани родителя: они используют ту же машину, но имеют полностью изолированное хранилище SQLite.

subAgent

Получить или создать именованного дочернего агента. Первый вызов для заданного имени запускает у дочернего агента 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> : типизированная RPC-заглушка, где каждый определённый пользователем метод у T доступен как удалённый вызов, возвращающий Promise.

SubAgentStub

Stub предоставляет доступ ко всем публичным методам экземпляра, которые вы определяете в дочернем классе. Методы, унаследованные от Agent (хуки жизненного цикла, setState, broadcast, sql, и так далее) исключены: на stub отображаются только ваши собственные методы.

Типы возврата автоматически оборачиваются в 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)

Требования

Заметки для тестирования

Тесты, которые используют @cloudflare/vitest-plugin может понадобиться указать классы граней как тестовые привязки Durable Object, чтобы ctx.exports предоставляет значение класса, совместимое с гранью. Не держите такие классы граней в new_sqlite_classes; дополнительная привязка относится только к тестовой wrangler.jsonc файлов и не является обязательным требованием для продакшен-Worker.

abortSubAgent

Принудительно остановите работающего дочернего агента. Потомок немедленно прекращает выполнение и перезапускается при следующем subAgent() вызов. Хранилище сохраняется: завершается только запущенный экземпляр.

class Agent {}
class Agent {
	abortSubAgent(cls: SubAgentClass, name: string, reason?: unknown): void;
}
Параметр Тип Описание
cls SubAgentClass Подкласс Agent, используемый при создании дочернего агента
name string Имя дочернего элемента, который нужно прервать
reason unknown Ошибка, передаваемая ожидающим или будущим вызывающим RPC

Прерывание транзитивно: если у дочернего агента есть собственные sub-agents, они тоже прерываются.

deleteSubAgent

Прервите дочерний агент (если он выполняется) и полностью очистите его хранилище. Следующий subAgent() вызов создаёт новый экземпляр с пустой SQLite.

class Agent {}
class Agent {
	deleteSubAgent(cls: SubAgentClass, name: string): void;
}
Параметр Тип Описание
cls SubAgentClass Подкласс Agent, используемый при создании дочернего агента
name string Имя дочернего элемента, который нужно удалить

Удаление транзитивно: собственные дочерние агенты потомка также удаляются.

Интроспекция и контроль доступа

hasSubAgent

Проверяет, был ли создан дочерний агент и не удалён ли он. В основе лежит реестр 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 });
}

listSubAgents

Выводит список порождённых дочерних агентов, опционально отфильтрованных по классу. Строки возвращаются в порядке создания.

const chats = this.listSubAgents(Chat);
// [{ className: "Chat", name: "chat-abc", createdAt: 1700000000000 }]
const chats = this.listSubAgents(Chat);
// [{ className: "Chat", name: "chat-abc", createdAt: 1700000000000 }]

onBeforeSubAgent

Переопределите этот хук middleware на родительском агенте, чтобы ограничивать доступ, изменять или прерывать входящие /sub/ запросов, прежде чем фреймворк разбудит дочерний агент. Он повторяет onBeforeConnect и onBeforeRequest.

Hook может возвращать:

Возвращаемое значение Эффект
void Пересылка исходного запроса потомку
Request Пересылка изменённого запроса
Response Short-circuit и не пробуждайте дочерний процесс
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() разрешает прямого родителя, даже если этот родитель сам является sub-agent, состоящим только из граней, используя под капотом RPC-мост на стороне root. Это даёт типизированные вызовы методов непосредственного родителя без необходимости привязывать каждый вложенный родительский класс как Durable Object верхнего уровня.

Для прародителей и более дальних предков перебирайте this.parentPath и вызвать getAgentByName() напрямую. Если имя привязки не совпадает с именем класса, вызовите getAgentByName(env.MY_BINDING, this.parentPath.at(-1)!.name) вместо parentAgent().

Клиентская маршрутизация

useAgent({ sub })

Расширить любой 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 }],
});

Hook формирует URL вида /agents/inbox/user-123/sub/chat/chat-abc и открывает прямое соединение WebSocket с Chat потомок. Каждый следующий useAgent функция работает как обычно: синхронизация состояния, stub вызовы, @callable RPC и useAgentChat поверх возвращённого сокета.

Пользовательская маршрутизация HTTP

Для fetch-обработчиков, которые самостоятельно разбирают URL на верхнем уровне, используйте 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. Вспомогательная функция разбирает его и запускает родительский onBeforeSubAgent хук и перенаправляет запрос в грань.

Внешний типизированный RPC

Изнутри родительского Durable Object this.subAgent(Cls, name) возвращает типизированный stub. Снаружи родителя используйте 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() выбрасывает исключение. Используйте routeSubAgentRequest() для пересылки HTTP и WebSocket.

Изоляция хранилища

Каждый дочерний агент имеет собственную базу данных 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;
	}
}

Именование и идентичность

Два разных класса могут иметь одинаковое видимое пользователю имя: они разрешаются независимо друг от друга. Внутренний ключ представляет собой сочетание имени класса и имени грани:

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
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

Дочернего класса 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);
		}
	}
}

Планирование и устойчивая работа

Дочерние агенты могут планировать собственные колбэки и запускать устойчивые fiber:

Метод Поведение у дочернего агента
schedule() / scheduleEvery() Работает в обычном режиме и запускает колбэки внутри дочернего агента
cancelSchedule() Работает для расписаний, принадлежащих вызывающему дочернему агенту
getScheduleById() / listSchedules() Работает и возвращает расписания в рамках вызывающего дочернего агента
keepAlive() / keepAliveWhile() Работает, делегируя heartbeat родительскому агенту верхнего уровня
runFiber() Работает, при этом строки fiber и снимки сохраняются в базе данных SQLite дочернего агента
setState() Работает как обычно и записывает данные в собственное хранилище дочернего агента
this.sql Работает как обычно и указывает на собственную базу данных SQLite дочернего агента
subAgent() Работает, поэтому дочерние агенты могут порождать собственных потомков

Верхнеуровневый родитель по-прежнему владеет физическим alarm Durable Object, поскольку у фасетов нет собственных независимых слотов для alarm. Agents SDK запоминает, какой дочерний агент владеет каждым запланированным колбэком или проверкой восстановления, пробуждает родителя и направляет работу обратно в дочерний агент. Колбэк по-прежнему выполняется с дочерним агентом в роли this, чтобы использовать состояние дочернего агента, его хранилище SQLite и getCurrentAgent() контекст.

Более старый синхронный getSchedule() и getSchedules() API выбрасывают исключение внутри дочерних агентов, поскольку строки расписания хранятся в родительском агенте верхнего уровня. Используйте getScheduleById() и listSchedules() взамен.

Вызов this.destroy() внутри дочернего агента делегирует очистку родительскому агенту. Тот отменяет запланированные задачи этого дочернего агента, удаляет метаданные восстановления для дочернего агента и его потомков, удаляет запись в реестре и просит рантайм очистить хранилище дочернего агента. Считайте this.destroy() как fire-and-forget, поскольку удаление дочернего агента может прервать его isolate до того, как метод корректно завершится.

Workflows от дочерних агентов

Дочерние агенты также могут запускать Workflows с this.runWorkflow(). Отслеживание Workflow ведётся локально, в базе данных SQLite этого sub-agent, и AgentWorkflow.agent направляет RPC, callbacks, обновления состояния и broadcasts обратно к исходному sub-agent. Родительские агенты не отображают и не управляют автоматически workflows, запущенными дочерними агентами.

Поскольку SubAgentStub<T> предоставляет только пользовательские дочерние методы, добавьте дочерние методы-обёртки для элементов управления, таких как getWorkflow(), approveWorkflow(), или terminateWorkflow(), затем вызовите эти обёртки через await this.subAgent(Child, name). Если вы передадите runWorkflow(..., { agentBinding }) из дочернего агента используйте имя привязки корневого Agent, а не имя дочерней привязки.

Для источников от workflow дочернего агента, AgentWorkflow.agent доступен только по RPC. Используйте его для вызова методов Agent, но используйте routeSubAgentRequest() или вложенный /agents/{parent}/{name}/sub/{child}/{name} структуру URL для внешней маршрутизации по HTTP или WebSocket вместо this.agent.fetch().

Пример

Пример чата с несколькими сессиями

Создайте инбокс, где каждый чат - это дочерний агент AIChatAgent с изолированным состоянием и прямой маршрутизацией к клиенту.