Skip to content

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

既存プロジェクトへ追加

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

既存の Cloudflare Workers プロジェクトへエージェントを追加する手順です。新規作成する場合は、代わりに チャットエージェントの構築 を参照してください。

前提条件

  • Wrangler 設定ファイルがある既存の Cloudflare Workers プロジェクト
  • Node.js 18 以降

1. パッケージをインストールする

npm i agents

React アプリでは追加パッケージは不要です。React バインディングは同梱されています。

Hono アプリの場合:

npm i agents hono-agents

2. Agent を作成する

エージェント用の新しいファイルを作成します(例: src/agents/counter.ts)。

import { Agent, callable } from "agents";

export class CounterAgent extends Agent {
	initialState = { count: 0 };

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

	@callable()
	decrement() {
		this.setState({ count: this.state.count - 1 });
		return this.state.count;
	}
}
import { Agent, callable } from "agents";

export type CounterState = {
	count: number;
};

export class CounterAgent extends Agent<Env, CounterState> {
	initialState: CounterState = { count: 0 };

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

	@callable()
	decrement() {
		this.setState({ count: this.state.count - 1 });
		return this.state.count;
	}
}

3. Wrangler 設定を更新する

Durable Object のバインディングとマイグレーションを追加します。

{
	"name": "my-existing-project",
	"main": "src/index.ts",
	// Set this to today's date
	"compatibility_date": "2026-09-20",
	"compatibility_flags": ["nodejs_compat"],

	"durable_objects": {
		"bindings": [
			{
				"name": "CounterAgent",
				"class_name": "CounterAgent",
			},
		],
	},

	"migrations": [
		{
			"tag": "v1",
			"new_sqlite_classes": ["CounterAgent"],
		},
	],
}
name = "my-existing-project"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-09-20"
compatibility_flags = [ "nodejs_compat" ]

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

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

ポイント:

  • bindings の nameenv 上のプロパティになります(例: env.CounterAgent
  • class_name はエクスポートしたクラス名と完全一致する必要があります
  • new_sqlite_classes は状態永続化用の SQLite ストレージを有効にします
  • agents パッケージには nodejs_compat フラグが必要です

4. TypeScript と Vite を設定する

上記の例のように @callable() デコレーターを使う場合、ビルド設定が 2 つ必要です。

tsconfig.jsonagents/tsconfig を継承します(または手動で "target": "ES2021" を設定します)。

{
	"extends": "agents/tsconfig"
}

既存の tsconfig.json に独自設定がある場合は、継承したうえで上書きできます。

{
	"extends": "agents/tsconfig",
	"compilerOptions": {
		"paths": { "~/*": ["./src/*"] }
	}
}

vite.config.tsagents() プラグインを追加します(Vite 8 向けの TC39 デコレーター変換を処理します)。

import agents from "agents/vite";

export default defineConfig({
	plugins: [
		agents(),
		// ... your existing plugins
	],
});
import agents from "agents/vite";

export default defineConfig({
	plugins: [
		agents(),
		// ... your existing plugins
	],
});

Vite を使わないプロジェクトでは、tsconfig.json の変更だけで十分です。バンドラーは TC39 デコレーター(ステージ 3、バージョン 2023-11)に対応している必要があります。

詳細は TypeScript 設定Vite 設定 のリファレンスを参照してください。

5. Agent クラスをエクスポートする

エージェントクラスは、メインのエントリポイントからエクスポートする必要があります。src/index.ts を更新します。

// Export the agent class (required for Durable Objects)
export { CounterAgent } from "./agents/counter";

// Your existing exports...
export default {
	// ...
};
// Export the agent class (required for Durable Objects)
export { CounterAgent } from "./agents/counter";

// Your existing exports...
export default {
	// ...
} satisfies ExportedHandler<Env>;

6. ルーティングを接続する

プロジェクト構成に合う方法を選びます。

素の Workers(fetch ハンドラー)

import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";

export default {
	async fetch(request, env, ctx) {
		// Try agent routing first
		const agentResponse = await routeAgentRequest(request, env);
		if (agentResponse) return agentResponse;

		// Your existing routing logic
		const url = new URL(request.url);
		if (url.pathname === "/api/hello") {
			return Response.json({ message: "Hello!" });
		}

		return new Response("Not found", { status: 404 });
	},
};
import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext) {
		// Try agent routing first
		const agentResponse = await routeAgentRequest(request, env);
		if (agentResponse) return agentResponse;

		// Your existing routing logic
		const url = new URL(request.url);
		if (url.pathname === "/api/hello") {
			return Response.json({ message: "Hello!" });
		}

		return new Response("Not found", { status: 404 });
	},
} satisfies ExportedHandler<Env>;

Hono

import { Hono } from "hono";
import { agentsMiddleware } from "hono-agents";
export { CounterAgent } from "./agents/counter";

const app = new Hono();

// Add agents middleware - handles WebSocket upgrades and agent HTTP requests
app.use("*", agentsMiddleware());

// Your existing routes continue to work
app.get("/api/hello", (c) => c.json({ message: "Hello!" }));

export default app;
import { Hono } from "hono";
import { agentsMiddleware } from "hono-agents";
export { CounterAgent } from "./agents/counter";

const app = new Hono<{ Bindings: Env }>();

// Add agents middleware - handles WebSocket upgrades and agent HTTP requests
app.use("*", agentsMiddleware());

// Your existing routes continue to work
app.get("/api/hello", (c) => c.json({ message: "Hello!" }));

export default app;

静的アセットと併用する

エージェントと一緒に静的アセットを配信する場合、静的アセットがデフォルトで先に配信されます。静的アセットに一致しないパスだけが Worker コードで処理されます。

import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";

export default {
	async fetch(request, env, ctx) {
		// Static assets are served automatically before this runs
		// This only handles non-asset requests

		// Route to agents
		const agentResponse = await routeAgentRequest(request, env);
		if (agentResponse) return agentResponse;

		return new Response("Not found", { status: 404 });
	},
};
import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext) {
		// Static assets are served automatically before this runs
		// This only handles non-asset requests

		// Route to agents
		const agentResponse = await routeAgentRequest(request, env);
		if (agentResponse) return agentResponse;

		return new Response("Not found", { status: 404 });
	},
} satisfies ExportedHandler<Env>;

Wrangler 設定ファイルでアセットを設定します。

{
	"assets": {
		"directory": "./public",
	},
}
[assets]
directory = "./public"

7. TypeScript 型を生成する

Env インターフェースは手書きしないでください。wrangler types を実行し、Wrangler 設定と一致する型定義ファイルを生成します。設定とコードの不一致を、デプロイ時ではなくコンパイル時に検出できます。

バインディングを追加または名前変更したら、wrangler types を再実行します。

npx wrangler types

すべてのバインディングが型付きの型定義ファイルが作成されます。エージェントの Durable Object 名前空間も含みます。Agent クラスは生成された Env 型をデフォルトで使うため、型パラメーターとして渡す必要はありません。状態用の第 2 型パラメーターが必要な場合を除き、extends Agent で十分です(例: Agent<Env, CounterState>)。

型生成の詳細は 設定 を参照してください。

8. フロントエンドから接続する

React

import { useState } from "react";
import { useAgent } from "agents/react";

function CounterWidget() {
	const [count, setCount] = useState(0);

	const agent = useAgent({
		agent: "CounterAgent",
		onStateUpdate: (state) => setCount(state.count),
	});

	return (
		<>
			{count}
			<button onClick={() => agent.stub.increment()}>+</button>
			<button onClick={() => agent.stub.decrement()}>-</button>
		</>
	);
}
import { useState } from "react";
import { useAgent } from "agents/react";
import type { CounterAgent, CounterState } from "./agents/counter";

function CounterWidget() {
	const [count, setCount] = useState(0);

	const agent = useAgent<CounterAgent, CounterState>({
		agent: "CounterAgent",
		onStateUpdate: (state) => setCount(state.count),
	});

	return (
		<>
			{count}
			<button onClick={() => agent.stub.increment()}>+</button>
			<button onClick={() => agent.stub.decrement()}>-</button>
		</>
	);
}

ポイント:

  • useAgent は WebSocket 経由でエージェントに接続します
  • エージェントの状態が変わると onStateUpdate が発火します
  • agent.stub.methodName() は、エージェント上の @callable() 付きメソッドを呼び出します

バニラ JavaScript

import { AgentClient } from "agents/client";

const agent = new AgentClient({
	agent: "CounterAgent",
	name: "user-123", // Optional: unique instance name
	onStateUpdate: (state) => {
		document.getElementById("count").textContent = state.count;
	},
});

// Call methods
document.getElementById("increment").onclick = () => agent.call("increment");
import { AgentClient } from "agents/client";

const agent = new AgentClient({
	agent: "CounterAgent",
	name: "user-123", // Optional: unique instance name
	onStateUpdate: (state) => {
		document.getElementById("count").textContent = state.count;
	},
});

// Call methods
document.getElementById("increment").onclick = () => agent.call("increment");

仕組み

ボタンをクリックすると、次の流れで状態が更新されます。

  1. クライアント が WebSocket 経由で agent.stub.increment() を呼びました
  2. Agentincrement() を実行し、setState() で状態を更新しました
  3. 状態 は SQLite に自動で永続化されました
  4. ブロードキャスト が接続中のすべてのクライアントへ送られました
  5. ReactonStateUpdate 経由で更新されました
flowchart LR
    A["ブラウザー<br/>(React)"] <-->|WebSocket| B["Agent<br/>(Counter)"]
    B --> C["SQLite<br/>(状態)"]

主な概念

概念 意味
Agent インスタンス 一意の名前ごとにエージェントが作られます。CounterAgent:user-123CounterAgent:user-456 とは別です
永続状態 状態は再起動、デプロイ、ハイバネーションを生き延びます。SQLite に保存されます
リアルタイム同期 同じエージェントに接続しているすべてのクライアントが、状態の更新を即座に受け取ります
ハイバネーション 接続中のクライアントがないとき、エージェントはハイバネーションします(課金なし)。次のリクエストで起床します

Cloudflare へデプロイする

npm run deploy

エージェントは Cloudflare のグローバルネットワーク上で稼働し、ユーザーの近くで動きます。

よくある連携パターン

認証の後ろに置く Agents

エージェントへルーティングする前に認証を確認します。

export default {
	async fetch(request, env) {
		// Check auth for agent routes
		if (request.url.includes("/agents/")) {
			const authResult = await checkAuth(request, env);
			if (!authResult.valid) {
				return new Response("Unauthorized", { status: 401 });
			}
		}

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

		// ... rest of routing
	},
};
export default {
	async fetch(request: Request, env: Env) {
		// Check auth for agent routes
		if (request.url.includes("/agents/")) {
			const authResult = await checkAuth(request, env);
			if (!authResult.valid) {
				return new Response("Unauthorized", { status: 401 });
			}
		}

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

		// ... rest of routing
	},
} satisfies ExportedHandler<Env>;

カスタムのエージェントパスプレフィックス

デフォルトでは、エージェントは /agents/{agent-name}/{instance-name} にルーティングされます。次のようにカスタマイズできます。

import { routeAgentRequest } from "agents";

const agentResponse = await routeAgentRequest(request, env, {
	prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});
import { routeAgentRequest } from "agents";

const agentResponse = await routeAgentRequest(request, env, {
	prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});

CORS、カスタムのインスタンス名、ロケーションヒントなどのオプションは ルーティング を参照してください。

サーバーコードからエージェントにアクセスする

Worker のコードから、エージェントを直接操作できます。

import { getAgentByName } from "agents";

export default {
	async fetch(request, env) {
		if (request.url.endsWith("/api/increment")) {
			// Get a specific agent instance
			const counter = await getAgentByName(env.CounterAgent, "shared-counter");
			const newCount = await counter.increment();
			return Response.json({ count: newCount });
		}
		// ...
	},
};
import { getAgentByName } from "agents";

export default {
	async fetch(request: Request, env: Env) {
		if (request.url.endsWith("/api/increment")) {
			// Get a specific agent instance
			const counter = await getAgentByName(env.CounterAgent, "shared-counter");
			const newCount = await counter.increment();
			return Response.json({ count: newCount });
		}
		// ...
	},
} satisfies ExportedHandler<Env>;

複数のエージェントを追加する

設定を拡張して、エージェントを追加します。

// src/agents/chat.ts
export class Chat extends Agent {
	// ...
}

// src/agents/scheduler.ts
export class Scheduler extends Agent {
	// ...
}
// src/agents/chat.ts
export class Chat extends Agent {
	// ...
}

// src/agents/scheduler.ts
export class Scheduler extends Agent {
	// ...
}

Wrangler 設定ファイルを更新します。

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "durable_objects": {
    "bindings": [
      {
        "name": "CounterAgent",
        "class_name": "CounterAgent"
      },
      {
        "name": "Chat",
        "class_name": "Chat"
      },
      {
        "name": "Scheduler",
        "class_name": "Scheduler"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": [
        "CounterAgent",
        "Chat",
        "Scheduler"
      ]
    }
  ]
}
[[durable_objects.bindings]]
name = "CounterAgent"
class_name = "CounterAgent"

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

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

[[migrations]]
tag = "v1"
new_sqlite_classes = ["CounterAgent", "Chat", "Scheduler"]

エントリポイントからすべてのエージェントをエクスポートします。

export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";
export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";

トラブルシューティング

Agent not found、または 404 エラー

  1. エクスポートを確認する — Agent クラスは、メインのエントリポイントからエクスポートする必要があります。
  2. バインディングを確認する — Wrangler 設定ファイルの class_name は、エクスポートしたクラス名と完全に一致する必要があります。
  3. ルートを確認する — デフォルトのルートは /agents/{'{agent-name}'}/{'{instance-name}'} です。クライアント側の Agent 名は、クラス名と一致します(大文字小文字は区別しません)。

No such Durable Object class エラー

Wrangler 設定ファイルにマイグレーションを追加します。

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": [
        "YourAgentClass"
      ]
    }
  ]
}
[[migrations]]
tag = "v1"
new_sqlite_classes = ["YourAgentClass"]

WebSocket 接続が失敗する

ルーティングがレスポンスを変更せずに返すようにします。

// Correct - return the response directly
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;

// Wrong - this breaks WebSocket connections
if (agentResponse) return new Response(agentResponse.body);
// Correct - return the response directly
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;

// Wrong - this breaks WebSocket connections
if (agentResponse) return new Response(agentResponse.body);

状態が保持されない

次を確認します。

  1. this.state を直接変更せず、this.setState() を呼んでいること。
  2. マイグレーションの new_sqlite_classes に Agent クラスがあること。
  3. 同じ Agent インスタンス名に接続していること。
  4. クライアントで onStateUpdate コールバックを接続していること。
  5. WebSocket 接続が確立されていること(ブラウザーの開発者ツールで確認します)。

"Method X is not callable" エラー

メソッドに @callable() デコレーターを付けます。

import { Agent, callable } from "agents";

export class MyAgent extends Agent {
	@callable()
	increment() {
		// ...
	}
}
import { Agent, callable } from "agents";

export class MyAgent extends Agent {
	@callable()
	increment() {
		// ...
	}
}

agent.stub の型エラー

Agent と state の型パラメーターを追加します。

import { useAgent } from "agents/react";

// Pass the agent and state types to useAgent
const agent = useAgent({
	agent: "CounterAgent",
	onStateUpdate: (state) => setCount(state.count),
});

// Now agent.stub is fully typed
agent.stub.increment();
import { useAgent } from "agents/react";
import type { CounterAgent, CounterState } from "./server";

// Pass the agent and state types to useAgent
const agent = useAgent<CounterAgent, CounterState>({
	agent: "CounterAgent",
	onStateUpdate: (state) => setCount(state.count),
});

// Now agent.stub is fully typed
agent.stub.increment();

@callable() 使用時の SyntaxError: Invalid or unexpected token

開発サーバーが SyntaxError: Invalid or unexpected token で失敗する場合は、tsconfig.json"target": "ES2021" を設定します。これで、Vite の esbuild トランスパイラーが TC39 デコレーターをネイティブ構文のまま通さず、ダウンレベルします。

{
	"compilerOptions": {
		"target": "ES2021"
	}
}

次のステップ

エージェントが動くようになったら、次のトピックを確認してください。

よくある次のステップ

内容 参照先
AI / LLM 機能を追加する AI モデルを使う
MCP でツールを公開する MCP サーバー
バックグラウンドタスクを実行する タスクをスケジュールする
メールを処理する メールルーティング
Cloudflare Workflows を使う Workflows を実行する

さらに見る

状態管理

setState()、initialState、onStateChanged() を詳しく解説します。

Client SDK

useAgent と AgentClient の API リファレンスです。

Agents API

Agents SDK の完全な API リファレンスです。

役に立ちましたか?