INTEGRITY Dokumentace

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

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

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

Příklad chatu s více relacemi

Sestavte doručenou poštu, kde je každý chat podřízeným agentem AIChatAgent s izolovaným stavem a přímým směrováním ke klientovi.