Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Agent クラスの内部

最終更新 Markdown で表示Agent セットアップ

agents ライブラリの中核は Agent クラスです。継承していくつかのメソッドをオーバーライドすれば、状態管理、WebSockets、スケジュール、RPC などをそのまま使えます。このページでは、Agent が層ごとにどう作られているかを説明し、内部で何が起きているかを把握できるようにします。

ここに示すスニペットは説明用であり、必ずしもベストプラクティスではありません。完全な API は API リファレンスソースコード を参照してください。

Agent とは

Agent クラスは DurableObject の拡張です。エージェントは Durable Objects そのものです。Durable Objects に不慣れな場合は、先に What are Durable Objects を読んでください。本質的には、グローバルにアドレス可能(各インスタンスに一意の ID がある)、シングルスレッドのコンピュートインスタンスで、長期ストレージ(キーバリューと SQLite)を持ちます。

AgentDurableObject を直接継承しません。partyserver パッケージの Server を継承し、ServerDurableObject を継承します。層で考えると DurableObject > Server > Agent です。

レイヤー 0: Durable Object

外側の層がどう使うかを理解するため、Durable Objects が公開するプリミティブを短く確認します。Durable Object クラスには次があります。

constructor

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

Workers ランタイムは内部処理のため、常にコンストラクタを呼びます。つまり次の 2 点です。

  1. Durable Object が初期化されるたびにコンストラクタは呼ばれますが、シグネチャは固定です。開発者はコンストラクタから引数を追加したり変更したりできません。
  2. クラスを手動でインスタンス化するのではなく、バインディング API を使い、DurableObjectNamespace 経由で行います。

RPC

組み込み型 DurableObject を継承した Durable Object クラスを書くと、公開メソッドが RPC メソッドとして公開されます。開発者は Worker からの DurableObjectStub で呼び出せます。

// 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 は Worker から Request を受け取り、Response を返せます。これは(開発者が実装する)fetch メソッド経由でのみ行えます。

WebSockets

Durable Objects は WebSockets を第一級でサポートします。Durable Object は fetch で受け取った Request から WebSocket を受け付け、あとは忘れられます。基底クラスは、コールバックとして呼ばれるメソッドを提供します。イベントリスナーの代わりになります。

基底クラスは webSocketMessage(ws, message)webSocketClose(ws, code, reason, wasClean)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()

HTTP と RPC リクエストだけが Durable Object の入口ではありません。アラームを使うと、後の時刻にイベントを発火するようスケジュールできます。次のアラームが到来すると、ランタイムは開発者が実装する alarm() メソッドを呼びます。

アラームをスケジュールするには this.ctx.storage.setAlarm() を使います。詳細は Alarms を参照してください。

this.ctx

基底の DurableObject クラスは DurableObjectStatethis.ctx に設定します。興味深いメソッドとプロパティは多いですが、ここでは this.ctx.storage に注目します。

this.ctx.storage

DurableObjectStorage は、Durable Object の永続化機構への主なインターフェースです。KV と SQLITE の同期 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

最後に、Durable Object は Worker の Envthis.env にも持ちます。詳細は Bindings を参照してください。

レイヤー 1: Server (partyserver)

Durable Objects が標準で提供するものを見たので、partyserverServer クラスの意味が分かりやすくなります。低レベルのプリミティブを、開発者向けのコールバックに置き換える、意見の入った DurableObject ラッパーです。

Server は独自のストレージ操作を追加しません。Durable Object のライフサイクルをラップするだけです。

アドレス指定

partyserver は、バインディングを手動で辿らず、名前で Durable Objects を指定するヘルパーを公開します。URL ルーティングスキーム(<your-worker>/servers/:durableClass/:durableName)も含み、Agent 層がこれを土台にします。

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

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

この URL スキームはリクエストルーターも有効にします。Agent 層では 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

アドレス指定層のおかげで、ServeronStart コールバックを公開できます。Durable Object が起動するたび(エビクション、休止、初回作成のあと)、かつ任意の fetch または 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(`...`);
	}
}

onRequestonConnect

Server は下層 Durable Object の fetch をすでに実装しており、開発者が使える 2 つのコールバックを公開します。HTTP リクエスト向けが onRequest、受信 WS 接続向けが onConnect です(WebSocket 接続はデフォルトで受け付けます)。

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

onConnect が新規接続ごとのコールバックであるのと同様に、ServerDurableObject クラスのデフォルトコールバックの上にラッパーも提供します。onMessageonCloseonError です。

接続中の全クライアントへ WS メッセージを送る this.broadcast もあります(魔法ではなく、this.getConnections() を回すだけです)。

this.name

Durable Object の内部から name を取るのは難しいです。partyserverthis.name で使えるようにしようとしますが、完全な解決ではありません。詳細は この GitHub issue を参照してください。

レイヤー 2: Agent

いよいよ Agent クラスです。AgentServer を継承し、ステートフルでスケジュール可能、観測可能なエージェント向けの意見の入ったプリミティブを提供します。RPC、WebSockets、(さらに)メールで通信できます。

this.statethis.setState()

Agent の中核機能の 1 つが自動の状態永続化です。開発者はジェネリック引数と initialState(ストレージに状態がないときだけ使われます)で状態の形を定義し、Agent が読み込み、保存、状態変更のブロードキャストを扱います(上記の Serverthis.broadcast() を確認してください)。

this.state は、ストレージ(SQL)から状態を遅延読み込みするゲッターです。this.setState() で更新すると、Durable Object のエビクションを越えて状態が残り、自動でシリアライズしてストレージへ書き戻します。

状態変更に反応するには this.onStateChanged をオーバーライドできます。

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);
	}
}

状態は cf_agents_state SQL テーブルに保存されます。状態メッセージは(クライアントからもサーバーからも)type: "cf_agent_state" で送られます。agentsJS と React のクライアント を提供するので、リアルタイムの状態更新はそのまま使えます。

this.sql

Agent は、Durable Object の SQL ストレージに対するクエリ実行向けに、便利な sql テンプレートタグを提供します。パラメータ化クエリを組み立てて実行します。これは this.ctx.storage.sql同期 SQL API を使います。

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 と呼び出し可能なメソッド

agents は Durable Objects の RPC を一歩進め、WebSockets 経由の RPC を実装します。クライアントは Agent のメソッドを直接呼べます。WebSocket 経由で呼べるようにするには @callable() デコレータを使います。メソッドはシリアライズ可能な値、または(@callable({ streaming: true }) を使う場合)ストリームを返せます。

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

クライアントはこのメソッドを、WebSocket メッセージを送って呼び出せます。

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

提供されている React クライアントなら、次のように簡単です。

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

this.queue とその周辺

エージェントには、遅延実行向けの組み込みタスクキューがあります。作業のオフロードや操作の再試行に便利です。使えるメソッドは this.queuethis.dequeuethis.dequeueAllthis.dequeueAllByCallbackthis.getQueuethis.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);
	}
}

タスクは cf_agents_queues SQL テーブルに保存され、順に自動でフラッシュされます。タスクが成功すると自動でデキューされます。

this.schedule とその周辺

エージェントは、Durable Object の alarm() をラップしてメソッドのスケジュール実行をサポートします。使えるメソッドは this.schedulethis.getSchedulethis.getSchedulesthis.cancelSchedule です。スケジュールは一回限り、遅延、または(cron 式を使った)繰り返しにできます。

Durable Objects は同時に 1 つのアラームしか許せないため、Agent クラスは複数スケジュールを SQL で管理し、単一アラームを使うことで回避します。

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);
	}
}

スケジュールは cf_agents_schedules SQL テーブルに保存されます。cron スケジュールは実行後に自動で再スケジュールされ、一回限りのスケジュールは削除されます。

this.mcp とその周辺

Agent には複数サーバー対応の MCP クライアントが含まれます。MCP インターフェースを公開する外部サービスとエージェントが連携できます。MCP クライアントの詳細は MCP クライアント API にあります。

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);
	}
}

メールの扱い

エージェントは Cloudflare の 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",
		});
	}
}

メールをエージェントへルーティングするには、Worker の email ハンドラで routeAgentEmail を使います。

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

コンテキスト管理

agents は、リクエストライフサイクル全体でコンテキストを維持するため、すべてのメソッドを AsyncLocalStorage でラップします。これにより、コードのどこからでも、現在のエージェント、接続、リクエスト、またはメール(扱っているイベントによる)にアクセスできます。

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

AgentServeronError を拡張し、WebSocket エラーに限らないエラーも扱えます。Connection または unknown エラーで呼ばれます。

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() はすべてのテーブルを削除し、アラームを消し、ストレージをクリアし、コンテキストを中止します。Durable Object を完全にエビクションするため、this.ctx.abort()setTimeout() で非同期に呼ばれます。現在実行中のハンドラ(スケジュールタスクなど)が、コンテキスト中止前にクリーンアップを終えられるようにします。

つまり this.ctx.abort() は捕捉できないエラーを投げ、ログに出ます。ただしイベントループへ一度 yield したあとです(詳細は abort() を参照してください)。

destroy() はスケジュールタスク内から安全に呼べます。スケジュールコールバック内から呼ぶと、Agent は残りのデータベース更新をスキップする内部フラグを立て、アラームハンドラがきれいに完了してからエージェントがエビクションされるよう、ctx.abort() をイベントループへ yield します。

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

クラスの static options をオーバーライドして、エージェントの挙動を設定します。すべてのフィールドは任意です。デフォルトは実行時に適用されます。

export class MyAgent extends Agent {
	static options = {
		hibernate: true,
		sendIdentityOnConnect: false,
		retry: { maxAttempts: 5, baseDelayMs: 200, maxDelayMs: 5000 },
	};
}
オプション デフォルト 説明
hibernate boolean true 非アクティブ時にエージェントが休止するかどうか。DO がスリープ中も WebSocket 接続は開いたままです
sendIdentityOnConnect boolean true WebSocket 接続時にクライアントへ ID(エージェント名、インスタンス名)を送ります。機密性の高いインスタンス名を隠すには false にします
hungScheduleTimeoutSeconds number 30 実行中の interval スケジュールをハングとみなして強制リセットするまでのタイムアウトです。長時間かかるコールバックでは増やします
keepAliveIntervalMs number 30000 keepAlive() アラームハートビートの間隔(ミリ秒)です。小さいほど復旧は速いですが、アラームは頻繁になります
retry RetryOptions { maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 3000 } schedule()queue()this.retry() のデフォルト再試行オプションです。タスクごとのオプションがこれらを上書きします

this.keepAlive()this.keepAliveWhile()

Durable Objects は、一定期間非アクティブ(通常は受信リクエスト、WebSocket メッセージ、アラームがない状態が 70〜140 秒)になるとエビクションされます。長時間かかる操作(LLM 応答のストリーミング、外部 API 待ち、複数ステップの計算)の途中で、エージェントがエビクションされることがあります。

keepAlive() はエビクションを防ぐアラームハートビートを作ります。keepAliveWhile() は非同期関数をラップし、クリーンアップを保証します。

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 は、LLM 応答のストリーミング中にエージェントを生存させるため、内部で keepAliveWhile を使います。詳細は スケジュールタスク — エージェントを生存させる を参照してください。

ルーティング

Agent クラスは アドレス指定ヘルパーgetAgentByNamerouteAgentRequest として再エクスポートします。

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 });

レイヤー 3: AIChatAgent

@cloudflare/ai-chatAIChatAgent クラスは、AI チャット向けの意見の入った層で Agent を拡張します。SQLite への自動メッセージ永続化、再開可能なストリーミング、ツールサポート(サーバー側、クライアント側、人が介在する承認)、チャット UI 構築向けの React フック(useAgentChat)を追加します。

全体の階層は DurableObject > Server > Agent > AIChatAgent です。

チャットエージェントを作る場合は AIChatAgent から始めます。より低レベルの制御が必要、またはチャット UI を作らない場合は、Agent を直接使います。

役に立ちましたか?