← Vytvářejte agenty na Cloudflare / agents / runtime / execution
Podřízení agenti
Vytvářejte podřízené agenty jako společně umístěné Durable Objects s vlastním izolovaným úložištěm SQLite. Nadřazený agent získá typovaný RPC stub pro volání metod na podřízeném agentovi: každá veřejná metoda třídy podřízeného agenta je volatelná jako vzdálené volání procedury (RPC) s návratovými typy zabalenými v Promise.
Použijte podřízené agenty, pokud jeden uživatel nebo entita vlastní otevřenou množinu dlouhotrvajících agentů, například chaty, dokumenty, relace, shardy nebo projekty. Každý podřízený agent běží paralelně s vlastním stavem, zatímco nadřazený agent koordinuje objevování, řízení přístupu a životní cyklus.
Pokud chcete, aby nadřazený chatovací agent v rámci jednoho kola spustil dalšího agenta schopného chatu a průběžně zobrazoval postup tohoto potomka přímo v konverzaci, použijte Agents jako nástroje. Agents jako nástroje jsou postavené na podřízených agentech, ale přidávají registr běhů na straně nadřazeného agenta, streamování agent-tool-event frame, přehrávání, zrušení a úklid.
Rychlý start
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();
}
}Obě třídy musí být exportovány ze vstupního bodu workeru. Pro třídy určené pouze pro potomky nejsou potřeba samostatné vazby Durable Object: třídy potomků se automaticky zjišťují pomocí 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"Vazbu a migraci Durable Object potřebuje pouze nadřazený agent nejvyšší úrovně. Podřízení agenti se vytvářejí jako facety rodiče: sdílejí stejný stroj, ale mají zcela izolované úložiště SQLite.
subAgent
Získat nebo vytvořit pojmenovaného podřízeného agenta. První volání pro daný název spustí potomkův onStart(). Následující volání vracejí existující instanci.
class Agent {}class Agent {
async subAgent<T extends Agent>(
cls: SubAgentClass<T>,
name: string,
): Promise<SubAgentStub<T>>;
}| Parametr | Typ | Popis |
|---|---|---|
cls |
SubAgentClass<T> |
Podtřída Agent. Musí být exportována ze vstupního bodu Workeru a název exportu se musí shodovat s názvem třídy. |
name |
string |
Jedinečný název této podřízené instance. Stejný název vždy vrátí stejnou podřízenou instanci. |
Vrátí SubAgentStub<T> : Typovaný RPC stub, kde každá uživatelem definovaná metoda na T je k dispozici jako vzdálené volání vracející Promise.
SubAgentStub
Stub zpřístupňuje všechny veřejné metody instance, které definujete na podřízené třídě. Metody zděděné z Agent (háčky životního cyklu, setState, broadcast, sql, a tak dále) jsou vyloučeny, na stubu se zobrazí jen vaše vlastní metody.
Návratové typy jsou automaticky zabaleny do Promise pokud ještě nejsou:
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)Požadavky
- Třída potomka musí rozšiřovat
Agent - Třída potomka musí být exportována ze vstupního bodu workeru (
export class MyChild extends Agent) - Název exportu se musí shodovat s názvem třídy:
export { Foo as Bar }není podporováno - Nejvyšší nadřazená třída musí být svázána jako jmenný prostor Durable Object v
wrangler.jsonc - Třída potomka určená pouze pro facety nemusí být registrována pod
new_sqlite_classespokud stejná třída není jinde svázána i jako Durable Object na nejvyšší úrovni - Vnořené nadřazené facety nepotřebují vlastní vazby Durable Object na nejvyšší úrovni; runtime přeloží vnořené podřízené facety prostřednictvím jmenného prostoru kořenového nadřazeného prvku
- Název třídy potomka nesmí být
Sub, protože/sub/je vyhrazen jako oddělovač URL pro vnořené trasy
Poznámky k testování
Testy, které používají @cloudflare/vitest-plugin možná bude nutné uvést třídy facetů jako testovací vazby Durable Object, aby ctx.exports poskytuje hodnotu třídy kompatibilní s facetem. Tyto třídy facetu nedávejte do new_sqlite_classes; extra vazba patří pouze do testovací wrangler.jsonc soubory a není to požadavek pro produkční Worker.
abortSubAgent
Násilně zastavte běžícího podřízeného agenta. Potomek okamžitě přestane běžet a znovu se spustí při dalším subAgent() volání. Úložiště zůstane zachováno, ukončí se jen běžící instance.
class Agent {}class Agent {
abortSubAgent(cls: SubAgentClass, name: string, reason?: unknown): void;
}| Parametr | Typ | Popis |
|---|---|---|
cls |
SubAgentClass |
Podtřída Agent použitá při vytváření potomka |
name |
string |
Název potomka, který se má přerušit |
reason |
unknown |
Chyba vyvolaná u čekajících nebo budoucích volajících RPC |
Přerušení je tranzitivní: pokud má potomek vlastní podřízené agenty, přeruší se i oni.
deleteSubAgent
Přeruší potomka (pokud běží) a natrvalo vymaže jeho úložiště. Další subAgent() volání vytvoří novou instanci s prázdnou SQLite databází.
class Agent {}class Agent {
deleteSubAgent(cls: SubAgentClass, name: string): void;
}| Parametr | Typ | Popis |
|---|---|---|
cls |
SubAgentClass |
Podtřída Agent použitá při vytváření potomka |
name |
string |
Název potomka, který se má odstranit |
Odstranění je tranzitivní: smažou se i vlastní podřízení agenti daného potomka.
Introspekce a řízení přístupu
hasSubAgent
Zkontroluje, zda byl potomek vytvořen a není smazaný. Vychází přitom z registru SQLite spravovaného frameworkem.
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
Vypsat spuštěné podřízené agenty, volitelně filtrované podle třídy. Řádky se vrací v pořadí vytvoření.
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
Přepište tento middleware hook na nadřazeném prvku, abyste řídili přístup, upravovali nebo zkratem přerušili příchozí /sub/ požadavků, než framework podřízenou instanci probudí. Odpovídá onBeforeConnect a onBeforeRequest.
Hook může vrátit:
| Návratová hodnota | Účinek |
|---|---|
void |
Předání původního požadavku potomkovi |
Request |
Předání upraveného požadavku |
Response |
Short-circuit a neprobouzejte podřízeného prvku |
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,
});
}
}
}Požadavky na upgrade WebSocket procházejí tímto hookem stejně jako běžné požadavky HTTP. Pokud vrátíte upravený Request, zachovejte původní hlavičky pro upgrade WebSocketu.
Identita nadřazeného a podřízeného agenta
Podřízení agenti vědí, kdo je jejich nadřazený agent, prostřednictvím this.parentPath a 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 je řazen od kořene, takže přímý rodič je vždy parentPath.at(-1). Agenti nejvyšší úrovně mají parentPath === [].
Použijte parentAgent(Cls) ze sub-agenta pro získání typovaného RPC stubu jeho přímého nadřazeného prvku:
const inbox = await this.parentAgent(Inbox);
await inbox.recordTurn(this.name, "...");const inbox = await this.parentAgent(Inbox);
await inbox.recordTurn(this.name, "...");parentAgent() vyřeší přímého nadřazeného agenta i v případě, že je tento nadřazený agent sám jen podřízeným agentem existujícím pouze jako facet, a to pomocí RPC mostu na straně kořene fungujícího pod kapotou. Díky tomu získáte typovaná volání metod na přímého nadřazeného agenta, aniž by musela být každá vnořená nadřazená třída svázána jako Durable Object nejvyšší úrovně.
Pro prarodiče a další předky iterujte this.parentPath a volejte getAgentByName() přímo. Pokud se název vazby neshoduje s názvem třídy, zavolejte getAgentByName(env.MY_BINDING, this.parentPath.at(-1)!.name) místo parentAgent().
Směrování klientů
useAgent({ sub })
Rozšiřte libovolný useAgent volání s sub řetězec pro připojení k podřízenému facetu:
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 sestaví URL adresu jako /agents/inbox/user-123/sub/chat/chat-abc a otevírá přímé připojení WebSocket k Chat potomek. Každý další useAgent funkce funguje jako obvykle: synchronizace stavu, stub volání, @callable RPC a useAgentChat nad vráceným socketem.
Vlastní směrování HTTP
Pro fetch handlery, které si samy provádějí parsování URL na nejvyšší úrovni, použijte routeSubAgentRequest() k odeslání požadavku do podřízeného agenta z již vyřešeného nadřazeného stubu:
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 přebírá poslední zprávy podřízeného agenta, například /sub/chat/chat-abc. Pomocná funkce ho zpracuje a spustí nadřazený onBeforeSubAgent hook a požadavek přepošle do facetu.
Externí typované RPC
Zevnitř nadřazeného Durable Object this.subAgent(Cls, name) vrátí typovaný stub. Mimo nadřazeného agenta použijte 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() vrátí proxy pouze pro RPC. Volání metod fungují, ale .fetch() vyvolá výjimku. Použijte routeSubAgentRequest() pro přeposílání HTTP a WebSocket.
Izolace úložiště
Každý podřízený agent má vlastní databázi SQLite, zcela izolovanou od nadřazeného agenta i od ostatních podřízených agentů. Nadřazený agent, který zapisuje do this.sql a podřízeného procesu zapisujícího do this.sql pracují s různými databázemi:
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;
}
}Pojmenování a identita
Dvě různé třídy mohou sdílet stejný uživatelsky viditelný název, přesto se řeší nezávisle na sobě. Interní klíč je složen z názvu třídy a názvu facetu:
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 storagePotomkův this.name vlastnost vrací název facetu (nikoli název nadřazeného prvku):
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
}
}Vzory
Paralelní sub-agenti
Spouštějte více podřízených agentů souběžně:
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;
}
}Vnoření podřízení agenti
Podřízení agenti mohou vytvářet vlastní podřízené agenty a tvořit tak strom:
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 };
}
}Streamování callbacku
Předejte RpcTarget callback pro streamování výsledků z dílčího agenta zpět do nadřazeného agenta:
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);
}
}
}Plánování a trvalá práce
Podřízení agenti mohou plánovat vlastní callbacky a spouštět trvalé fibery:
| Metoda | Chování u podřízeného agenta |
|---|---|
schedule() / scheduleEvery() |
Pracuje normálně a callbacky spouští uvnitř podřízeného agenta |
cancelSchedule() |
Funguje pro plány vlastněné volajícím sub-agentem |
getScheduleById() / listSchedules() |
Pracuje a vrací plány vázané na volajícího podřízeného agenta |
keepAlive() / keepAliveWhile() |
Pracuje tak, že heartbeat deleguje na nadřazeného agenta nejvyšší úrovně |
runFiber() |
Funguje, přičemž řádky fiber a snímky jsou uloženy v databázi SQLite podřízeného |
setState() |
Funguje normálně a zapisuje do vlastního úložiště podřízeného |
this.sql |
Funguje normálně a odkazuje na vlastní databázi SQLite podřízeného |
subAgent() |
Funguje, takže sub-agenti mohou vytvářet vlastní podřízené |
Nejvyšší nadřazený prvek stále vlastní fyzický alarm Durable Object, protože facety nemají vlastní nezávislé alarmové sloty. Agents SDK eviduje, který podřízený prvek vlastní jednotlivý naplánovaný callback nebo kontrolu obnovení, probudí nadřazený prvek a práci přesměruje zpět do podřízeného prvku. Callback stále běží se sub-agentem jako this, takže využívá stav podřízeného agenta, jeho úložiště SQLite a getCurrentAgent() kontext.
Starší synchronní getSchedule() a getSchedules() API uvnitř podřízených agentů vyvolávají výjimku, protože naplánované řádky se ukládají u nadřazeného agenta nejvyšší úrovně. Použijte getScheduleById() a listSchedules() místo toho.
Volání this.destroy() uvnitř podřízeného agenta deleguje úklid na nadřazeného agenta. Ten zruší plánované úlohy daného podřízeného agenta, odstraní metadata obnovy pro podřízeného agenta i jeho potomky, odstraní záznam v registru a požádá runtime o vymazání úložiště podřízeného agenta. Považujte this.destroy() jako fire-and-forget, protože smazání podřízeného agenta může přerušit jeho izolát ještě předtím, než se metoda čistě vrátí.
Workflows ze sub-agentů
Podřízení agenti mohou také spustit Workflows hodnotou this.runWorkflow(). Sledování workflow je lokální vůči databázi SQLite podřízeného agenta a AgentWorkflow.agent směruje RPC, callbacky, aktualizace stavu a vysílání zpět k původnímu podřízenému agentovi. Nadřazení agenti automaticky nevypisují ani neřídí workflow spuštěná podřízenými agenty.
Protože SubAgentStub<T> zpřístupňuje pouze uživatelsky definované podřízené metody, přidejte podřízené wrapper metody pro ovládací prvky, jako je getWorkflow(), approveWorkflow(), nebo terminateWorkflow(), poté tyto wrappery zavolejte prostřednictvím await this.subAgent(Child, name). Pokud předáte runWorkflow(..., { agentBinding }) ze sub-agenta použijte název vazby kořenového Agenta, nikoli název vazby potomka.
Pro původ z workflow podřízeného agenta, AgentWorkflow.agent je pouze pro RPC. Použijte ji k volání metod Agent, ale pro routeSubAgentRequest() nebo vnořený /agents/{parent}/{name}/sub/{child}/{name} tvar URL pro externí směrování HTTP nebo WebSocket namísto this.agent.fetch().
Příklad
Související
- Think,
chat()metoda pro streamování tahů AI přes subagenty - Dlouhodobě běžící agenti : delegování na podřízené agenty v kontextu vícetýdenních životních cyklů agenta
- Volatelné metody : RPC přes
@callablea service bindings - Agents jako nástroje : spustí Think nebo
AIChatAgentpodřízené agenty jako zachované streamovací nástroje - Plánování úloh : plánovací primitiva pro agenty nejvyšší úrovně a podřízené agenty