INTEGRITY Dokumentace

Vnitřní fungování třídy Agent

Jádro agents knihovna je Agent třída. Rozšíříte ji, přepíšete několik metod a získáte správu stavu, WebSockets, plánování, RPC a další věci zdarma. Tato stránka vysvětluje, jak Agent se vytváří vrstvu po vrstvě, abyste rozuměli tomu, co se děje pod kapotou.

Zde uvedené úryvky slouží jako ilustrace a nemusí nutně odpovídat osvědčeným postupům. Úplné API najdete v Referenční dokumentace API a zdrojový kód ↗.

Co je Agent?

Agent třída je rozšířením DurableObject : agents jsou Durable Objects. Pokud Durable Objects neznáte, přečtěte si Co jsou Durable Objects nejprve. V jádru jsou Durable Objects globálně adresovatelné (každá instance má unikátní ID), jednovláknové výpočetní instance s dlouhodobým úložištěm (typu klíč-hodnota a SQLite).

Agent nerozšiřuje DurableObject přímo. Rozšiřuje Server z partyserver ↗ balíček, který rozšiřuje DurableObject. Představte si to jako vrstvy: DurableObject > Server > Agent.

Vrstva 0: Durable Object

Krátce se podívejme, jaké primitivy Durable Objects nabízejí, abychom pochopili, jak je využívají vnější vrstvy. Třída Durable Object obsahuje:

constructor

constructor(ctx: DurableObjectState, env: Env) {}

Runtime Workers vždy interně volá konstruktor. To znamená dvě věci:

  1. Konstruktor se sice volá pokaždé, když se Durable Object inicializuje, ale jeho signatura je pevně daná. Vývojáři nemohou parametry konstruktoru přidávat ani upravovat.
  2. Místo ručního vytváření instance třídy musí vývojáři použít binding API a provést to prostřednictvím DurableObjectNamespace.

RPC

Napsáním třídy Durable Object, která dědí od vestavěného typu DurableObject, veřejné metody se zpřístupní jako metody RPC, které mohou vývojáři volat pomocí DurableObjectStub z Workeru.

// This instance could've been active, hibernated,
// not initialized or maybe had never even been created!
const stub = env.MY_DO.getByName("foo");

// We can call any public method on the class. The runtime
// ensures the constructor is called if the instance was not active.
await stub.bar();

fetch()

Durable Objects mohou k probuzení potřebovat Request z Workeru a odeslat Response zpět. To lze provést pouze prostřednictvím fetch metodu (kterou musí implementovat vývojář).

WebSockets

Durable Objects zahrnují plnohodnotnou podporu pro WebSockets. Durable Object může přijmout WebSocket, který obdrží od Request v fetch a dále se o to nestarat. Základní třída poskytuje metody, které mohou vývojáři implementovat a které se volají jako callbacky. Ty fakticky nahrazují potřebu posluchačů událostí.

Základní třída poskytuje webSocketMessage(ws, message), webSocketClose(ws, code, reason, wasClean) a webSocketError(ws , error) (API).

export class MyDurableObject extends DurableObject {
	async fetch(request) {
		// Creates two ends of a WebSocket connection.
		const webSocketPair = new WebSocketPair();
		const [client, server] = Object.values(webSocketPair);

		// Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
		this.ctx.acceptWebSocket(server);

		return new Response(null, {
			status: 101,
			webSocket: client,
		});
	}

	async webSocketMessage(ws, message) {
		ws.send(message);
	}
}

alarm()

Požadavky HTTP a RPC nejsou jediným vstupním bodem pro Durable Object. Alarmy umožňují vývojářům naplánovat spuštění události v pozdějším čase. Jakmile nastane čas dalšího alarmu, runtime zavolá alarm() metodu, jejíž implementace je ponechána na vývojáři.

Chcete-li naplánovat alarm, můžete použít this.ctx.storage.setAlarm() metodu. Další informace najdete na Alarmy.

this.ctx

Základní DurableObject třída nastavuje DurableObjectState do this.ctx. Nabízí spoustu zajímavých metod a vlastností, my se ale zaměříme na this.ctx.storage.

this.ctx.storage

DurableObjectStorage je hlavní rozhraní k mechanismům perzistence Durable Object, které zahrnují jak KV, tak SQLITE synchronní API.

const sql = this.ctx.storage.sql;

// Synchronous SQL query
const rows = sql.exec("SELECT * FROM contacts WHERE country = ?", "US");

// Key-value storage
const token = this.ctx.storage.get("someToken");

this.ctx.env

Nakonec stojí za zmínku, že Durable Object má také Worker Env v this.env. Další informace najdete v Bindings.

Vrstva 1: Server (partyserver)

Nyní, když jste viděli, co Durable Objects nabízejí ihned po instalaci, Server třída z partyserver ↗ bude dávat větší smysl. Jde o účelově navržený DurableObject wrapper, který nahrazuje nízkoúrovňové primitivy callbacky přívětivými pro vývojáře.

Server nepřidává žádné vlastní operace ukládání, pouze obaluje životní cyklus Durable Object.

Adresování

partyserver zpřístupňuje pomocné funkce pro adresování Durable Objects podle jména namísto ručního procházení přes vazby. Patří sem i schéma směrování URL adres (<your-worker>/servers/:durableClass/:durableName) na které vrstva Agent staví.

// Note the await here!
const stub = await getServerByName(env.MY_DO, "foo");

// We can still call RPC methods.
await stub.bar();

Schéma URL zároveň umožňuje směrování požadavků. Ve vrstvě Agent je toto opětovně exportováno jako routeAgentRequest:

  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const res = await routeAgentRequest(request, env);

    if (res) return res;

    return new Response("Not found", { status: 404 });
  }

onStart

Adresovací vrstva umožňuje Server ke zpřístupnění onStart callback, který se spustí pokaždé, když se Durable Object spustí (po vyřazení z paměti, hibernaci nebo prvním vytvoření), a to ještě před jakýmkoli fetch nebo volání RPC.

class MyServer extends Server {
	onStart() {
		// Some initialization logic that you wish
		// to run every time the DO is started up.
		const sql = this.ctx.storage.sql;
		sql.exec(`...`);
	}
}

onRequest a onConnect

Server již implementuje fetch pro podkladový Durable Object a poskytuje dva různé callbacky, které mohou vývojáři využít, onRequest a onConnect pro HTTP požadavky, respektive příchozí WS připojení (WebSocket připojení jsou ve výchozím nastavení přijímána).

class MyServer extends Server {
	async onRequest(request: Request) {
		const url = new URL(request.url);

		return new Response(`Hello from ${url.origin}!`);
	}

	async onConnect(conn, ctx) {
		const { request } = ctx;
		const url = new URL(request.url);

		// Connections are a WebSocket wrapper
		conn.send(`Hello from ${url.origin}!`);
	}
}

WebSockets

Stejně jako onConnect je callback pro každé nové připojení, Server také poskytuje wrappery nad výchozími callbacky z DurableObject třída: onMessage, onClose a onError.

K dispozici je také this.broadcast která odešle WS zprávu všem připojeným klientům (žádná magie, jen cyklus přes this.getConnections()!).

this.name

Je obtížné zjistit u Durable Object name zevnitř něj. partyserver se snaží zpřístupnit ho v this.name ale není to dokonalé řešení. Více se dozvíte v tento GitHub issue ↗.

Vrstva 2: Agent

Nyní nakonec Agent třída. Agent rozšiřuje Server a poskytuje účelově navržené primitivy pro stavové, plánovatelné a pozorovatelné agenty, kteří mohou komunikovat přes RPC, WebSockets a (dokonce!) e-mail.

this.state a this.setState()

Jednou z klíčových funkcí Agent je automatické uchovávání stavu. Vývojáři definují tvar svého stavu pomocí generického parametru a initialState (ten se použije pouze tehdy, pokud v úložišti neexistuje žádný stav), a Agent se stará o načítání, ukládání a odesílání změn stavu (viz Server: this.broadcast() výše).

this.state je getter, který stav líně načítá z úložiště (SQL). Stav zůstává zachován i po vyřazení Durable Object z paměti, pokud je aktualizován pomocí this.setState(), který automaticky serializuje stav a zapisuje ho zpět do úložiště.

K dispozici je také this.onStateChanged kterou můžete přepsat, abyste reagovali na změny stavu.

class MyAgent extends Agent<Env, { count: number }> {
	initialState = { count: 0 };

	increment() {
		this.setState({ count: this.state.count + 1 });
	}

	onStateChanged(state, source) {
		console.log("State updated:", state);
	}
}

Stav se ukládá v cf_agents_state SQL tabulce. Stavové zprávy se odesílají s type: "cf_agent_state" (jak od klienta, tak od serveru). Protože agents poskytuje Klienti JS a React, aktualizace stavu v reálném čase jsou k dispozici bez dalšího nastavování.

this.sql

Agent poskytuje pohodlný sql tagged template pro spouštění dotazů nad SQL úložištěm Durable Object. Sestavuje parametrizované dotazy a spouští je. Používá se přitom synchronní SQL API z this.ctx.storage.sql.

class MyAgent extends Agent {
	onStart() {
		this.sql`
      CREATE TABLE IF NOT EXISTS users (
        id TEXT PRIMARY KEY,
        name TEXT
      )
    `;

		const userId = "1";
		const userName = "Alice";
		this.sql`INSERT INTO users (id, name) VALUES (${userId}, ${userName})`;

		const users = this.sql<{ id: string; name: string }>`
      SELECT * FROM users WHERE id = ${userId}
    `;
		console.log(users); // [{ id: "1", name: "Alice" }]
	}
}

RPC a volatelné metody

agents posouvá RPC Durable Objects o krok dál tím, že implementuje RPC přes WebSockets, takže klienti mohou volat metody Agenta přímo. Aby byla metoda volatelná přes WebSocket, použijte @callable() dekorátor. Metody mohou vracet serializovatelnou hodnotu nebo stream (při použití @callable({ streaming: true })).

class MyAgent extends Agent {
	@callable({ description: "Add two numbers" })
	async add(a: number, b: number) {
		return a + b;
	}
}

Klienti mohou tuto metodu vyvolat odesláním zprávy WebSocket:

{
	"type": "rpc",
	"id": "unique-request-id",
	"method": "add",
	"args": [2, 3]
}

Například s poskytnutým React klienta je to stejně jednoduché jako:

const { stub } = useAgent({ name: "my-agent" });
const result = await stub.add(2, 3);
console.log(result); // 5

this.queue a další podobné

Agents obsahují vestavěnou frontu úloh pro odložené provádění. To se hodí pro přenesení práce jinam nebo opakování operací. Dostupné metody jsou this.queue, this.dequeue, this.dequeueAll, this.dequeueAllByCallback, this.getQueue, a this.getQueues.

class MyAgent extends Agent {
	async onConnect() {
		// Queue a task to be executed later
		await this.queue("processTask", { userId: "123" });
	}

	async processTask(payload: { userId: string }, queueItem: QueueItem) {
		console.log("Processing task for user:", payload.userId);
	}
}

Úlohy se ukládají do cf_agents_queues SQL tabulce a automaticky se v pořadí zpracovávají. Pokud je úloha úspěšná, automaticky se odstraní z fronty.

this.schedule a další podobné

Agents podporují naplánované spouštění metod obalením alarmu Durable Object alarm(). Dostupné metody jsou this.schedule, this.getSchedule, this.getSchedules, this.cancelSchedule. Plány mohou být jednorázové, s odloženým spuštěním nebo opakující se (pomocí cron výrazů).

Protože Durable Objects povolují najednou jen jeden alarm, Agent třída to řeší tak, že spravuje více plánů v SQL a používá jediný alarm.

class MyAgent extends Agent {
	async foo() {
		// Schedule at a specific time
		await this.schedule(new Date("2025-12-25T00:00:00Z"), "sendGreeting", {
			message: "Merry Christmas!",
		});

		// Schedule with a delay (in seconds)
		await this.schedule(60, "checkStatus", { check: "health" });

		// Schedule with a cron expression
		await this.schedule("0 0 * * *", "dailyTask", { type: "cleanup" });
	}

	async sendGreeting(payload: { message: string }) {
		console.log(payload.message);
	}

	async checkStatus(payload: { check: string }) {
		console.log("Running check:", payload.check);
	}

	async dailyTask(payload: { type: string }) {
		console.log("Daily task:", payload.type);
	}
}

Plány jsou uloženy v cf_agents_schedules SQL tabulce. Plány typu cron se po spuštění automaticky znovu naplánují, zatímco jednorázové plány se po spuštění smažou.

this.mcp a další podobné

Agent zahrnuje klienta MCP pro více serverů. Díky tomu může váš agent komunikovat s externími službami, které zpřístupňují rozhraní MCP. Klient MCP je podrobně zdokumentován v API klienta MCP.

class MyAgent extends Agent {
	async onStart() {
		// Add an HTTP MCP server (callbackHost only needed for OAuth servers)
		await this.addMcpServer("GitHub", "https://mcp.github.com/mcp", {
			callbackHost: "https://my-worker.example.workers.dev",
		});

		// Add an MCP server via RPC (Durable Object binding, no HTTP overhead)
		await this.addMcpServer("internal-tools", this.env.MyMCP);
	}
}

Práce s e-maily

Agents mohou přijímat e-maily a odpovídat na ně pomocí Email Routing.

class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		console.log("Received email from:", email.from);
		console.log("Subject:", email.headers.get("subject"));

		const raw = await email.getRaw();
		console.log("Raw email size:", raw.length);

		// Reply to the email
		await this.replyToEmail(email, {
			fromName: "My Agent",
			subject: "Re: " + email.headers.get("subject"),
			body: "Thanks for your email!",
			contentType: "text/plain",
		});
	}
}

Chcete-li e-maily směrovat do svého Agenta, použijte routeAgentEmail v e-mailovém handleru vašeho Workeru:

export default {
	async email(message, env, ctx) {
		await routeAgentEmail(message, env, {
			resolver: createAddressBasedEmailResolver("my-agent"),
		});
	},
} satisfies ExportedHandler<Env>;

Správa kontextu

agents obaluje všechny vaše metody pomocí AsyncLocalStorage k udržování kontextu po celou dobu životního cyklu požadavku. Díky tomu můžete z libovolného místa ve svém kódu přistupovat k aktuálnímu agentovi, připojení, požadavku nebo e-mailu (v závislosti na tom, jaká událost se zpracovává):

import { getCurrentAgent } from "agents";

function someUtilityFunction() {
	const { agent, connection, request, email } = getCurrentAgent();

	if (agent) {
		console.log("Current agent:", agent.name);
	}

	if (connection) {
		console.log("WebSocket connection ID:", connection.id);
	}
}

this.onError

Agent rozšiřuje Server: onError aby ho bylo možné použít i pro zpracování chyb, které nejsou nutně chybami WebSocket. Volá se s Connection nebo unknown chyba.

class MyAgent extends Agent {
	onError(connectionOrError: Connection | unknown, error?: unknown) {
		if (error) {
			// WebSocket connection error
			console.error("Connection error:", error);
		} else {
			// Server error
			console.error("Server error:", connectionOrError);
		}

		// Optionally throw to propagate the error
		throw connectionOrError;
	}
}

this.destroy

this.destroy() odstraní všechny tabulky, smaže alarmy, vymaže úložiště a přeruší kontext. Aby bylo zajištěno úplné vyřazení Durable Object z paměti, this.ctx.abort() se volá asynchronně pomocí setTimeout() aby mohly aktuálně běžící handlery (například plánované úlohy) dokončit své operace čištění, než dojde k přerušení kontextu.

To znamená this.ctx.abort() vyvolá nezachytitelnou chybu, která se objeví ve vašich logách, ale stane se tak až po předání řízení event loopu (více se dočtete v abort()).

destroy() metodu lze bezpečně volat v rámci naplánovaných úloh. Pokud je zavolána uvnitř plánovacího callbacku, Agent nastaví interní příznak, aby přeskočil veškeré zbývající aktualizace databáze, a vrátí ctx.abort() do event loopu, aby obslužná rutina alarmu bezpečně doběhla dříve, než dojde k vystěhování Agenta.

class MyAgent extends Agent {
	async onStart() {
		console.log("Agent is starting up...");
		// Initialize your agent
	}

	async cleanup() {
		// This wipes everything!
		await this.destroy();
	}

	async selfDestruct() {
		// Safe to call from within a scheduled task
		await this.schedule(60, "destroyAfterDelay", {});
	}

	async destroyAfterDelay() {
		// This will safely destroy the Agent even when
		// called from within the alarm handler
		await this.destroy();
	}
}

static options

Nakonfigurujte chování agenta přepsáním static options na vaší třídě. Všechna pole jsou volitelná: výchozí hodnoty se použijí za běhu.

export class MyAgent extends Agent {
	static options = {
		hibernate: true,
		sendIdentityOnConnect: false,
		retry: { maxAttempts: 5, baseDelayMs: 200, maxDelayMs: 5000 },
	};
}
Možnost Typ Výchozí Popis
hibernate boolean true Zda agent při nečinnosti hibernuje. Připojení WebSocket zůstávají otevřená, i když DO spí
sendIdentityOnConnect boolean true Odešlete identitu (název agenta, název instance) klientům při připojení přes WebSocket. Nastavte na false ke skrytí citlivých názvů instancí
hungScheduleTimeoutSeconds number 30 Časový limit, po kterém je běžící intervalový plán považován za zaseknutý a vynuceně resetován. Pro dlouho běžící callbacky jej zvyšte
keepAliveIntervalMs number 30000 Interval v milisekundách pro keepAlive() alarmových heartbeatů. Nižší hodnoty znamenají rychlejší obnovení, ale častější alarmy
retry RetryOptions { maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 3000 } Výchozí možnosti opakování pro schedule(), queue(), a this.retry(). Možnosti nastavené pro jednotlivé úlohy tyto výchozí hodnoty přepisují

this.keepAlive() a this.keepAliveWhile()

Durable Objects se po období nečinnosti vyřadí z paměti (obvykle po 70 až 140 sekundách bez příchozích požadavků, zpráv WebSocketu nebo alarmů). Během dlouhotrvajících operací, jako je streamování odpovědí LLM, čekání na externí API nebo provádění vícekrokových výpočtů, může být agent vyřazen z paměti uprostřed práce.

keepAlive() vytvoří heartbeat alarmu, který zabraňuje vyřazení z paměti. keepAliveWhile() obaluje asynchronní funkci a zaručuje úklid.

class MyAgent extends Agent {
	async handleLongTask() {
		// Option 1: manual dispose
		const dispose = await this.keepAlive();
		try {
			await longRunningComputation();
		} finally {
			dispose();
		}

		// Option 2: automatic cleanup (recommended)
		const result = await this.keepAliveWhile(async () => {
			return await longRunningComputation();
		});
	}
}

AIChatAgent používá keepAliveWhile interně, aby agent zůstal aktivní během streamování odpovědí LLM. Podrobnosti najdete v Plánování úloh: Udržování agenta naživu.

Směrování

Agent třída znovu exportuje pomocné funkce pro adresování jako getAgentByName a routeAgentRequest.

const stub = await getAgentByName(env.MY_DO, "foo");
await stub.someMethod();

const res = await routeAgentRequest(request, env);
if (res) return res;

return new Response("Not found", { status: 404 });

Vrstva 3: AIChatAgent

AIChatAgent třída z @cloudflare/ai-chat rozšiřuje Agent o účelově navrženou vrstvu pro AI chat. Přidává automatickou perzistenci zpráv do SQLite, obnovitelné streamování, podporu nástrojů (na straně serveru, na straně klienta i s lidským zásahem) a React hook (useAgentChat) pro tvorbu chatovacích uživatelských rozhraní.

Celá hierarchie je: DurableObject > Server > Agent > AIChatAgent.

Pokud vytváříte chatovacího agenta, začněte s AIChatAgent. Pokud potřebujete nižší úroveň kontroly nebo nevytváříte chatové rozhraní, použijte Agent přímo.