cloudflare-agents について
Cloudflare Agents SDKでWorkersとDurable Objects上にステートフルなリアルタイムエージェントを構築する開発者向けスキルです。状態管理、スケジューリング、複数エージェント連携、MCPに対応し、AI推論機能は提供しません。
主な機能
- 01ステートフルなエージェントの構築
- 02リアルタイムWebSocket通信
- 03SQLiteによる状態保持
- 04タスクのスケジュールとエージェント連携
- 05MCPサーバーの構築
活用シーン
- 01永続状態を必要とするエージェントの実装
- 02双方向リアルタイムチャットの構築
- 03定期タスクや遅延タスクの設定
- 04MCPツール実行に対応したエージェントの作成
MCP 対応
Agents SDKは、状態を保持したツール実行を含むMCPサーバーの実装に対応します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill cloudflare-agentsインストール後、Claude Code で/cloudflare-agentsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.md.claude-plugin/plugin.jsonrules/cloudflare-agents.mdtemplates/basic-agent.tsbrowser-agent.tscalling-agents-worker.tschat-agent-streaming.tshitl-agent.tsmcp-server-basic.tsrag-agent.tsreact-useagent-client.tsxscheduled-agent.tssimple-chat-no-agents-sdk.tsstate-sync-agent.tswebsocket-agent.tsworkflow-agent.ts詳細
Cloudflare Agents SDK
ステータス:本番運用可能 ✅ 最終更新日:2026-01-09 依存関係:cloudflare-worker-base(推奨) 最新バージョン:[email protected]、@modelcontextprotocol/sdk@latest 本番環境でのテスト済み:Cloudflare 独自の MCP サーバー(https://github.com/cloudflare/mcp-server-cloudflare)
最近の更新(2025~2026年):
- 2026年1月:Agents SDK v0.3.6。呼び出し可能メソッドの修正とプロトコルバージョンのサポートを更新
- 2025年11月:Agents SDK v0.2.24 以降で再開可能なストリーミングに対応(切断後、ページ更新後もストリームが維持され、タブやデバイス間で同期)、MCP クライアントの改善、スケジュール関連の修正
- 2025年9月:AI SDK v5 との互換性、自動メッセージ移行
- 2025年8月:MCP Elicitation に対応、http-streamable トランスポート、タスクキュー、メール連携
- 2025年4月:MCP に対応(MCPAgent クラス)、agents から
import { context } - 2025年3月:パッケージ名を変更(agents-sdk → agents)
再開可能なストリーミング([email protected] 以降)
AIChatAgent は再開可能なストリーミングに対応しました。これにより、クライアントは再接続して、データを失うことなくストリーミング中の応答を引き続き受信できます。次のような、実際の運用で重要なシナリオに対応します。
- 接続タイムアウトを超える長時間の AI 応答
- ネットワークが不安定なユーザー(モバイル、機内 Wi-Fi)
- 会話の途中でデバイスを切り替えるユーザー
- ユーザーがいったん画面を離れ、後で戻ってくるバックグラウンドタスク
- 複数のクライアント間で同期を保つ必要があるリアルタイムコラボレーション
主な機能:ページの更新後や接続が切れた後もストリームが維持され、開いているタブやデバイス間で同期されます。
実装(AIChatAgent で自動的に行われます):
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
return streamText({
model: openai('gpt-4o-mini'),
messages: this.messages,
onFinish
}).toTextStreamResponse();
// ✅ Stream automatically resumable
// - Client disconnects? Stream preserved
// - Page refresh? Stream continues
// - Multiple tabs? All stay in sync
}
}
コードを変更する必要はありません。[email protected] 以降で AIChatAgent を使用するだけです。
Cloudflare Agents とは?
Cloudflare Agents SDK を使用すると、Cloudflare Workers と Durable Objects 上で動作する、AI を活用した自律型エージェントを構築できます。エージェントでは次のことができます。
- WebSocket と Server-Sent Events を介してリアルタイムに通信する
- 組み込みの SQLite データベースで状態を永続化する(エージェントあたり最大 1GB)
- 遅延、特定の日付、cron 式を使用してタスクをスケジュールする
- 非同期の Cloudflare Workflows を起動してワークフローを実行する
- Browser Rendering API と Puppeteer を使用してWeb を閲覧する
- Vectorize ベクトルデータベースと Workers AI の埋め込みを使ってRAG を実装する
- Model Context Protocol を実装した MCP サーバーを構築する
- レビューと承認のためのhuman-in-the-loop パターンに対応する
- 世界中で独立したエージェントインスタンスを数百万規模にスケールする
各エージェントインスタンスは、数秒、数分、または数時間稼働できる、グローバルで一意のステートフルなマイクロサーバーです。
Agents SDK は必要ですか?
注意:Agents SDK を使用する前に、本当に必要かどうかを確認してください。
次の場合は Vercel AI SDK のみを使用してください(よりシンプルです)。
- ✅ 基本的なチャットインターフェースを構築する
- ✅ Server-Sent Events(SSE)ストリーミングで十分(一方向:サーバー → クライアント)
- ✅ エージェントの状態を永続化する必要がない(または、D1/KV を別途使用して管理する)
- ✅ 単一ユーザー、単一会話のシナリオ
- ✅ AI の応答だけが必要で、複雑なワークフローやスケジュール管理は不要
チャットアプリケーションの 80%はこれで対応できます。 このような場合は、Workers 上で Vercel AI SDK を直接使用してください。よりシンプルで、必要なインフラストラクチャが少なく、ストリーミングも自動で処理されます。
例(Agents SDK は不要):
// worker.ts - Simple chat with AI SDK only
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
export default {
async fetch(request: Request, env: Env) {
const { messages } = await request.json();
const result = streamText({
model: openai('gpt-4o-mini'),
messages
});
return result.toTextStreamResponse(); // Automatic SSE streaming
}
}
// client.tsx - React with built-in hooks
import { useChat } from 'ai/react';
function ChatPage() {
const { messages, input, handleSubmit } = useChat({ api: '/api/chat' });
// Done. No Agents SDK needed.
}
結果:コードは 500 行ではなく 100 行です。Durable Objects のセットアップも、WebSocket の複雑さも、マイグレーションもありません。
Agents SDK を使うべき場合:
- ✅ WebSocket 接続(真の双方向リアルタイム通信)
- ✅ Durable Objects(グローバルに一意で、状態を保持するエージェントインスタンス)
- ✅ 組み込みの状態永続化(エージェントごとに最大 1GB の SQLite ストレージ)
- ✅ マルチエージェント連携(エージェント同士が互いを呼び出し、通信する)
- ✅ スケジュールされたタスク(遅延、cron 式、定期ジョブ)
- ✅ 人間が介在するワークフロー(承認ゲート、レビュー手順)
- ✅ 長時間実行エージェント(バックグラウンド処理、自律型ワークフロー)
- ✅ 状態を保持したツール実行が可能な MCP サーバー
これはアプリケーションの約 20%です。Agents SDK が提供するインフラストラクチャが必要な場合に使用します。
重要な理解:Agents SDK が担うこと/担わないこと
Agents SDK が担うこと:
- 🏗️ WebSocket 接続、Durable Objects、状態管理のインフラストラクチャ層
- 🔧 状態を保持する自律型エージェントを構築するためのフレームワーク
- 📦 ライフサイクルメソッドを備えた Durable Objects のラッパー
Agents SDK が担わないこと:
- ❌ AI 推論プロバイダー(AI SDK、Workers AI、OpenAI などはご自身で用意してください)
- ❌ ストリーミング応答ハンドラー(自動解析には AI SDK を使用してください)
- ❌ LLM 統合(これは別の役割です)
次のように考えてください:
- Agents SDK = 建物(WebSocket、状態、ルーム)
- AI SDK/Workers AI = AI の頭脳(推論、推論処理、応答)
これらを組み合わせて使用することも(ほとんどの場合に推奨)、Workers AI を直接使用することもできます(SSE の手動解析を行う場合)。
選択フローチャート
Building an AI application?
│
├─ Need WebSocket bidirectional communication? ───────┐
│ (Client sends while server streams, agent-initiated messages)
│
├─ Need Durable Objects stateful instances? ──────────┤
│ (Globally unique agents with persistent memory)
│
├─ Need multi-agent coordination? ────────────────────┤
│ (Agents calling/messaging other agents)
│
├─ Need scheduled tasks or cron jobs? ────────────────┤
│ (Delayed execution, recurring tasks)
│
├─ Need human-in-the-loop workflows? ─────────────────┤
│ (Approval gates, review processes)
│
└─ If ALL above are NO ─────────────────────────────→ Use AI SDK directly
(Much simpler approach)
If ANY above are YES ────────────────────────────→ Use Agents SDK + AI SDK
(More infrastructure, more power)
アーキテクチャの比較
| 機能 | AI SDK のみ | Agents SDK + AI SDK |
|---|---|---|
| セットアップの複雑さ | 🟢 低い(npm install で完了) | 🔴 高い(Durable Objects、マイグレーション、バインディング) |
| コード量 | 🟢 約 100 行 | 🟡 500 行以上 |
| ストリーミング | ✅ 自動(SSE) | ✅ 自動(AI SDK)または手動(Workers AI) |
| 状態管理 | ⚠️ 手動(D1/KV) | ✅ 組み込み(SQLite) |
| WebSocket | ❌ 手動セットアップ | ✅ 組み込み |
| React フック | ✅ useChat、useCompletion | ⚠️ カスタムフックが必要 |
| マルチエージェント | ❌ 非対応 | ✅ 組み込み(routeAgentRequest) |
| スケジュール設定 | ❌ 外部(Queue/Workflow) | ✅ 組み込み(this.schedule) |
| ユースケース | シンプルなチャット、補完 | 複雑な状態保持型ワークフロー |
まだ判断できない場合
AI SDK から始めてください。 WebSocket や Durable Objects が必要だと分かった場合は、いつでも後から Agents SDK に移行できます。インフラストラクチャは、後から削除するより追加するほうが簡単です。
ほとんどの開発者の場合:チャットインターフェースを構築していて、WebSocket、マルチエージェント連携、スケジュールされたタスクについて特別な要件がない場合は、AI SDK を直接使用してください。より速く、複雑さを抑えてリリースできます。
Agents SDK を使用するのは、そのインフラストラクチャ機能が必要な具体的な理由を特定できた場合に限ってください。
クイックスタート(10 分)
1. テンプレートでプロジェクトをひな形から作成する
npm create cloudflare@latest my-agent -- \
--template=cloudflare/agents-starter \
--ts \
--git \
--deploy false
作成されるもの:
- Agent プロジェクトの完全な構成
- TypeScript の設定
- Durable Objects のバインディングを含む wrangler.jsonc
- チャットエージェントの実装例
- useAgent フックを備えた React クライアント
2. または既存の Worker に追加する
cd my-existing-worker
npm install agents
続いて、Agent クラスを作成します:
// src/index.ts
import { Agent, AgentNamespace } from "agents";
export class MyAgent extends Agent {
async onRequest(request: Request): Promise<Response> {
return new Response("Hello from Agent!");
}
}
export default MyAgent;
3. Durable Objects のバインディングを設定する
wrangler.jsonc を作成または更新します:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-agent",
"main": "src/index.ts",
"compatibility_date": "2025-10-21",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{
"name": "MyAgent", // MUST match class name
"class_name": "MyAgent" // MUST match exported class
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyAgent"] // CRITICAL: Enables SQLite storage
}
]
}
重要な設定ルール:
- ✅
nameとclass_nameは必ず同じ値にしてください - ✅
new_sqlite_classesは最初のマイグレーションに必ず含めてください(後から追加できません) - ✅ Agent クラスは必ずエクスポートしてください(エクスポートしないとバインディングが失敗します)
- ✅ マイグレーションタグは再利用できません(各マイグレーションに一意のタグが必要です)
4. デプロイする
npx wrangler@latest deploy
エージェントは次の URL で実行されています:https://my-agent.<subdomain>.workers.dev
アーキテクチャの概要:各要素の連携
各ツールの役割を理解すると、混乱を防ぎ、適切な組み合わせを選びやすくなります。
スタック
┌─────────────────────────────────────────────────────────┐
│ Your Application │
│ │
│ ┌────────────────┐ ┌──────────────────────┐ │
│ │ Agents SDK │ │ AI Inference │ │
│ │ (Infra Layer) │ + │ (Brain Layer) │ │
│ │ │ │ │ │
│ │ • WebSockets │ │ Choose ONE: │ │
│ │ • Durable Objs │ │ • Vercel AI SDK ✅ │ │
│ │ • State (SQL) │ │ • Workers AI ⚠️ │ │
│ │ • Scheduling │ │ • OpenAI Direct │ │
│ │ • Multi-agent │ │ • Anthropic Direct │ │
│ └────────────────┘ └──────────────────────┘ │
│ ↓ ↓ │
│ Manages connections Generates responses │
│ and state and handles streaming │
└─────────────────────────────────────────────────────────┘
↓
Cloudflare Workers + Durable Objects
各ツールが提供するもの
1. Agents SDK(このスキル)
目的:ステートフルなリアルタイムエージェントのインフラストラクチャ
提供する機能:
- ✅ WebSocket 接続管理(双方向リアルタイム通信)
- ✅ Durable Objects のラッパー(グローバルに一意なエージェントインスタンス)
- ✅ 組み込みの状態永続化(最大 1GB の SQLite)
- ✅ ライフサイクルメソッド(
onStart、onConnect、onMessage、onClose) - ✅ タスクのスケジュール設定(cron/遅延に対応した
this.schedule()) - ✅ 複数エージェントの連携(
routeAgentRequest()) - ✅ クライアントライブラリ(
useAgent、AgentClient、agentFetch)
提供しない機能:
- ❌ AI 推論(LLM 呼び出しなし)
- ❌ ストリーミングレスポンスの解析(各自で用意してください)
- ❌ プロバイダー連携(OpenAI、Anthropic など)
次のように考えてください:建物とインフラ(部屋、ドア、配管)であり、入居者(AI)ではありません。
2. Vercel AI SDK(AI に推奨)
目的:自動ストリーミングに対応した AI 推論
提供する機能:
- ✅ ストリーミングレスポンスの自動処理(SSE の解析は SDK が行います)
- ✅ マルチプロバイダー対応(OpenAI、Anthropic、Google など)
- ✅ React フック(
useChat、useCompletion、useAssistant) - ✅ プロバイダー間で統一された API
- ✅ ツール呼び出し/関数呼び出し
- ✅ Cloudflare Workers で動作 ✅
例:
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
const result = streamText({
model: openai('gpt-4o-mini'),
messages: [...]
});
// Returns SSE stream - no manual parsing needed
return result.toTextStreamResponse();
Agents SDK と併用する場合:
- ✅ ほとんどのチャットアプリケーション
- ✅ React フックを使いたい場合
- ✅ 複数の AI プロバイダーを使う場合
- ✅ クリーンで抽象化された AI 呼び出しを使いたい場合
Agents SDK との組み合わせ:
import { AIChatAgent } from "agents/ai-chat-agent";
import { streamText } from "ai";
export class MyAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
// Agents SDK provides: WebSocket, state, this.messages
// AI SDK provides: Automatic streaming, provider abstraction
return streamText({
model: openai('gpt-4o-mini'),
messages: this.messages // Managed by Agents SDK
}).toTextStreamResponse();
}
}
3. Workers AI(AI の代替手段)
目的:Cloudflare のプラットフォーム上での AI 推論
提供する機能:
- ✅ 費用対効果の高い推論(Workers のサブスクリプションに含まれます)
- ✅ 外部 API キー不要
- ✅ モデル:LLaMA 3、Qwen、Mistral、埋め込みなど
- ✅ Cloudflare のネットワーク上で動作(低レイテンシ)
提供しない機能:
- ❌ ストリーミングの自動解析(生の SSE 形式を返します)
- ❌ React フック
- ❌ マルチプロバイダーの抽象化
手動での解析が必要です:
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [...],
stream: true
});
// Returns raw SSE format - YOU must parse
for await (const chunk of response) {
const text = new TextDecoder().decode(chunk); // Uint8Array → string
if (text.startsWith('data: ')) { // Check SSE format
const data = JSON.parse(text.slice(6)); // Parse JSON
if (data.response) { // Extract .response field
fullResponse += data.response;
}
}
}
使用する場合:
- ✅ コストが重要な場合(埋め込み、大量のリクエスト)
- ✅ Cloudflare 固有のモデルが必要な場合
- ✅ SSE を手動で解析できる場合
- ✅ 外部依存関係が許可されない場合
トレードオフ:費用を節約する代わりに、手動解析に時間をかけます。
推奨される組み合わせ
オプション A:Agents SDK + Vercel AI SDK(推奨 ⭐)
使用する場面:WebSocket/状態管理が必要で、AI との連携も簡潔に実現したい場合
import { AIChatAgent } from "agents/ai-chat-agent";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
return streamText({
model: openai('gpt-4o-mini'),
messages: this.messages, // Agents SDK manages history
onFinish
}).toTextStreamResponse();
}
}
メリット:
- ✅ 優れた開発者体験
- ✅ ストリーミングを自動処理
- ✅ Agents SDK による WebSocket と状態管理
- ✅ クリーンで保守しやすいコード
デメリット:
- ⚠️ 外部 API キーが必要
- ⚠️ AI プロバイダーの追加費用が発生
オプション B:Agents SDK + Workers AI
使用する場面:WebSocket/状態管理が必要で、コストが重要な場合
import { Agent } from "agents";
export class BudgetAgent extends Agent<Env> {
async onMessage(connection, message) {
const response = await this.env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [...],
stream: true
});
// Manual SSE parsing required (see Workers AI section above)
for await (const chunk of response) {
// ... manual parsing ...
}
}
}
メリット:
- ✅ 費用対効果が高い
- ✅ 外部依存関係なし
- ✅ Agents SDK による WebSocket と状態管理
デメリット:
- ❌ SSE の手動解析が複雑
- ❌ 選択できるモデルが限られる
- ❌ 保守するコードが増える
オプション C:Vercel AI SDK のみ(Agents なし)
使用する場面:WebSocket や Durable Objects が不要な場合
// worker.ts - Simple Workers route
export default {
async fetch(request: Request, env: Env) {
const { messages } = await request.json();
const result = streamText({
model: openai('gpt-4o-mini'),
messages
});
return result.toTextStreamResponse();
}
}
// client.tsx - Built-in React hooks
import { useChat } from 'ai/react';
function Chat() {
const { messages, input, handleSubmit } = useChat({ api: '/api/chat' });
return <form onSubmit={handleSubmit}>...</form>;
}
メリット:
- ✅ 最もシンプルな方法
- ✅ コードが最小限
- ✅ すばやく実装できる
- ✅ React フックを標準搭載
デメリット:
- ❌ WebSocket なし(SSE のみ)
- ❌ Durable Objects による状態管理なし
- ❌ 複数エージェントの連携なし
最適な用途:チャットアプリケーションの 80%
選択マトリックス
| ニーズ | 推奨スタック | 複雑さ | コスト |
|---|---|---|---|
| シンプルなチャット、状態管理なし | AI SDK のみ | 🟢 低 | $$(AI プロバイダー) |
| チャット + WebSocket + 状態管理 | Agents SDK + AI SDK | 🟡 中 | $$$(インフラ + AI) |
| チャット + WebSocket + 予算重視 | Agents SDK + Workers AI | 🔴 高 | $(インフラのみ) |
| 複数エージェントのワークフロー | Agents SDK + AI SDK | 🔴 高 | $$$(インフラ + AI) |
| ツールを備えた MCP サーバー | Agents SDK(McpAgent) | 🟡 中 | $(インフラのみ) |
主なポイント
Agents SDK は AI ではなく、インフラストラクチャです。 AI 推論ツールと組み合わせて使用します。
- 最高の DX を求める場合:Agents SDK + Vercel AI SDK ⭐
- コストを抑える場合:Agents SDK + Workers AI(手動での解析が必要です)
- シンプルさを重視する場合:AI SDK のみ(WebSocket/状態が不要な場合)
このスキルの以降の部分では、Agents SDK(インフラストラクチャ層)に焦点を当てます。AI 推論のパターンについては、ai-sdk-core または cloudflare-workers-ai スキルを参照してください。
設定(wrangler.jsonc)
必須の重要な設定:
{
"durable_objects": {
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] } // MUST be in first migration
]
}
一般的なオプションのバインディング:ai、vectorize、browser、workflows、d1_databases、r2_buckets
重要な移行ルール:
- ✅
new_sqlite_classesはタグ「v1」に指定する必要があります(既存のデプロイ済みクラスに SQLite を追加することはできません) - ✅
nameとclass_nameは完全に一致する必要があります - ✅ 移行はアトミックに行われます(すべてのインスタンスが同時に更新されます)
- ✅ 各タグは一意である必要があり、以前のタグを編集または削除することはできません
参照:https://developers.cloudflare.com/agents/api-reference/configuration/
エージェントの基本パターン
Agent クラスの基本 - ライフサイクルメソッドを使用して Agent<Env, State> を拡張します。
onStart()- エージェントの初期化onRequest()- HTTP リクエストの処理onConnect/onMessage/onClose()- WebSocket の処理onStateUpdate()- 状態の変更への対応
主なプロパティ:
this.env- 環境バインディング(AI、DB など)this.state- 現在のエージェントの状態(読み取り専用)this.setState()- 永続化された状態の更新this.sql- 組み込みの SQLite データベースthis.name- エージェントインスタンスの識別子this.schedule()- 将来のタスクのスケジュール
参照:https://developers.cloudflare.com/agents/api-reference/agents-api/ の Agent API 公式ドキュメント
WebSocket とリアルタイム通信
Agents は双方向のリアルタイム通信に WebSocket をサポートしています。次のような場合に使用します。
- サーバーがストリーミングしている間に、クライアントからメッセージを送信する
- エージェントからメッセージを開始する(通知、更新など)
- 状態を保持した長時間接続
基本パターン:
export class ChatAgent extends Agent<Env, State> {
async onConnect(connection: Connection, ctx: ConnectionContext) {
// Auth check, add to participants, send welcome
}
async onMessage(connection: Connection, message: WSMessage) {
// Process message, update state, broadcast response
}
}
SSE の代替手段:一方向のサーバー → クライアントのストリーミング(よりシンプルな HTTP ベース)には、WebSocket ではなく Server-Sent Events を使用します。
参照:https://developers.cloudflare.com/agents/api-reference/websockets/
状態管理
2 つの状態管理方法:
-
this.setState(newState)- JSON シリアライズ可能な状態(最大 1GB)- 自動的に永続化され、WebSocket クライアントと同期されます
- 用途:ユーザー設定、セッションデータ、小規模なデータセット
-
this.sql- 組み込みの SQLite データベース(最大 1GB)- タグ付きテンプレートリテラルにより SQL インジェクションを防止します
- 用途:リレーショナルデータ、大規模なデータセット、複雑なクエリ
状態に関するルール:
- ✅ JSON シリアライズ可能な値のみ(オブジェクト、配列、プリミティブ、null)
- ✅ 再起動後も保持され、即時に整合性が保たれます
- ❌ 関数や循環参照は使用できません
- ❌ 合計上限は 1GB(状態と SQL の合計)
SQL パターン:
await this.sql`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)`
await this.sql`INSERT INTO users (email) VALUES (${userEmail})` // ← Prepared statement
const users = await this.sql`SELECT * FROM users WHERE email = ${email}` // ← Returns array
状態の型安全性に関する注意点
重要:状態メソッドに型パラメーターを指定しても、結果が型定義と一致することは検証されません。TypeScript では、指定した型に存在しない、または適合しないプロパティ(フィールド)は、エラーなく破棄されます。
interface MyState {
count: number;
name: string;
}
export class MyAgent extends Agent<Env, MyState> {
initialState = { count: 0, name: "default" };
async increment() {
// TypeScript allows this, but runtime may differ
const currentState = this.state; // Type is MyState
// If state was corrupted/modified externally:
// { count: "invalid", otherField: 123 }
// TypeScript still shows it as MyState
// count field doesn't match (string vs number)
// otherField is dropped silently
}
}
予防策:重要な状態操作には、実行時検証を追加します。
// Validate state shape at runtime
function validateState(state: unknown): state is MyState {
return (
typeof state === 'object' &&
state !== null &&
'count' in state &&
typeof (state as MyState).count === 'number' &&
'name' in state &&
typeof (state as MyState).name === 'string'
);
}
async increment() {
if (!validateState(this.state)) {
console.error('State validation failed', this.state);
// Reset to valid state
await this.setState(this.initialState);
return;
}
// Safe to use
const newCount = this.state.count + 1;
await this.setState({ ...this.state, count: newCount });
}
参照:https://developers.cloudflare.com/agents/api-reference/store-and-sync-state/
タスクのスケジュール
Agents は this.schedule() を使用して、将来実行するタスクをスケジュールできます。
遅延(秒)
export class MyAgent extends Agent {
async onRequest(request: Request): Promise<Response> {
// Schedule task to run in 60 seconds
const { id } = await this.schedule(60, "checkStatus", { requestId: "123" });
return Response.json({ scheduledTaskId: id });
}
// This method will be called in 60 seconds
async checkStatus(data: { requestId: string }) {
console.log('Checking status for request:', data.requestId);
// Perform check, update state, send notification, etc.
}
}
特定の日付
export class MyAgent extends Agent {
async scheduleReminder(reminderDate: string) {
const date = new Date(reminderDate);
const { id } = await this.schedule(date, "sendReminder", {
message: "Time for your appointment!"
});
return id;
}
async sendReminder(data: { message: string }) {
console.log('Sending reminder:', data.message);
// Send email, push notification, etc.
}
}
Cron 式
export class MyAgent extends Agent {
async setupRecurringTasks() {
// Every 10 minutes
await this.schedule("*/10 * * * *", "checkUpdates", {});
// Every day at 8 AM
await this.schedule("0 8 * * *", "dailyReport", {});
// Every Monday at 9 AM
await this.schedule("0 9 * * 1", "weeklyReport", {});
// Every hour on the hour
await this.schedule("0 * * * *", "hourlyCheck", {});
}
async checkUpdates(data: any) {
console.log('Checking for updates...');
}
async dailyReport(data: any) {
console.log('Generating daily report...');
}
async weeklyReport(data: any) {
console.log('Generating weekly report...');
}
async hourlyCheck(data: any) {
console.log('Running hourly check...');
}
}
スケジュール済みタスクの管理
export class MyAgent extends Agent {
async manageSchedules() {
// Get all scheduled tasks
const allTasks = this.getSchedules();
console.log('Total tasks:', allTasks.length);
// Get specific task by ID
const taskId = "some-task-id";
const task = await this.getSchedule(taskId);
if (task) {
console.log('Task:', task.callback, 'at', new Date(task.time));
console.log('Payload:', task.payload);
console.log('Type:', task.type); // "scheduled" | "delayed" | "cron"
// Cancel the task
const cancelled = await this.cancelSchedule(taskId);
console.log('Cancelled:', cancelled);
}
// Get tasks in time range
const upcomingTasks = this.getSchedules({
timeRange: {
start: new Date(),
end: new Date(Date.now() + 24 * 60 * 60 * 1000) // Next 24 hours
}
});
console.log('Upcoming tasks:', upcomingTasks.length);
// Filter by type
const cronTasks = this.getSchedules({ type: "cron" });
const delayedTasks = this.getSchedules({ type: "delayed" });
}
}
スケジュールの制約:
- 各タスクは SQL データベースの 1 行に対応します(タスクごとの最大サイズは 2 MB)
- タスクの合計数は次の条件で制限されます:
(task_size * count) + other_state < 1GB - Cron タスクは明示的にキャンセルされるまで実行され続けます
- コールバックメソッドは Agent クラスに存在する必要があります(存在しない場合はエラーがスローされます)
重大なエラー:コールバックメソッドが存在しない場合:
// ❌ BAD: Method doesn't exist
await this.schedule(60, "nonExistentMethod", {});
// ✅ GOOD: Method exists
await this.schedule(60, "existingMethod", {});
async existingMethod(data: any) {
// Implementation
}
ワークフローの実行
エージェントは非同期の Cloudflare Workflows をトリガーできます。
ワークフローバインディングの設定
wrangler.jsonc:
{
"workflows": [
{
"name": "MY_WORKFLOW",
"class_name": "MyWorkflow"
}
]
}
Workflow が別のスクリプトにある場合:
{
"workflows": [
{
"name": "EMAIL_WORKFLOW",
"class_name": "EmailWorkflow",
"script_name": "email-workflows" // Different project
}
]
}
ワークフローのトリガー
import { Agent } from "agents";
import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workers";
interface Env {
MY_WORKFLOW: Workflow;
MyAgent: AgentNamespace<MyAgent>;
}
export class MyAgent extends Agent<Env> {
async onRequest(request: Request): Promise<Response> {
const userId = new URL(request.url).searchParams.get('userId');
// Trigger a workflow immediately
const instance = await this.env.MY_WORKFLOW.create({
id: `user-${userId}`,
params: { userId, action: "process" }
});
// Or schedule a delayed workflow trigger
await this.schedule(300, "runWorkflow", { userId });
return Response.json({ workflowId: instance.id });
}
async runWorkflow(data: { userId: string }) {
const instance = await this.env.MY_WORKFLOW.create({
id: `delayed-${data.userId}`,
params: data
});
// Monitor workflow status periodically
await this.schedule("*/5 * * * *", "checkWorkflowStatus", { id: instance.id });
}
async checkWorkflowStatus(data: { id: string }) {
// Check workflow status (see Workflows docs for details)
console.log('Checking workflow:', data.id);
}
}
// Workflow definition (can be in same or different file/project)
export class MyWorkflow extends WorkflowEntrypoint<Env> {
async run(event: WorkflowEvent<{ userId: string }>, step: WorkflowStep) {
// Workflow implementation
const result = await step.do('process-data', async () => {
return { processed: true };
});
return result;
}
}
エージェントとワークフローの比較
| 機能 | エージェント | ワークフロー |
|---|---|---|
| 目的 | インタラクティブなユーザー向け | バックグラウンド処理 |
| 期間 | 数秒から数時間 | 数分から数時間 |
| 状態 | SQLite データベース | ステップベースのチェックポイント |
| インタラクション | WebSockets、HTTP | 直接のインタラクションなし |
| 再試行 | 手動 | ステップごとに自動 |
| ユースケース | チャット、リアルタイム UI | ETL、バッチ処理 |
ベストプラクティス:複数のワークフローを調整するには、エージェントを使用します。エージェントは、ユーザーとのインタラクションを維持しながら、ワークフローのトリガー、監視、結果への応答を行えます。
Web の閲覧
エージェントは、ウェブスクレイピングや自動化に Browser Rendering を使用できます。
バインディング:wrangler.jsonc に "browser": { "binding": "BROWSER" } を追加します
パッケージ:@cloudflare/puppeteer
ユースケース:エージェントのワークフロー内でのウェブスクレイピング、スクリーンショット、自動ブラウジング
参照:Puppeteer と Workers の完全な統合ガイドについては、cloudflare-browser-rendering スキルをご覧ください。
検索拡張生成(RAG)
エージェントは Vectorize(ベクトルデータベース)と Workers AI(埋め込み)を使用して RAG を実装できます。
パターン:ドキュメントを取り込む → 埋め込みを生成する → Vectorize に保存する → クエリする → コンテキストを取得する → AI に渡す
バインディング:
"ai": { "binding": "AI" }- 埋め込み用の Workers AI"vectorize": { "bindings": [{ "binding": "VECTORIZE", "index_name": "my-vectors" }] }- ベクトル検索
一般的なワークフロー:
- Workers AI で埋め込みを生成します(
@cf/baai/bge-base-en-v1.5) - Vectorize にベクトルをアップサートします(
this.env.VECTORIZE.upsert(vectors)) - 類似するベクトルをクエリします(
this.env.VECTORIZE.query(queryVector, { topK: 5 })) - 取得したコンテキストを AI プロンプトで使用します
参照:RAG の完全な実装ガイドについては、cloudflare-vectorize スキルをご覧ください。
AI モデルの使用
エージェントは次の方法で AI モデルを呼び出せます。
- Vercel AI SDK(推奨):マルチプロバイダー、自動ストリーミング、ツール呼び出し
- Workers AI:Cloudflare のプラットフォーム上の推論(費用対効果が高く、手動での解析が必要)
アーキテクチャに関する注意:Agents SDK は、インフラストラクチャ(WebSockets、状態、スケジューリング)を提供します。AI 推論は別のレイヤーです。「頭脳」には AI SDK を使用してください。
参照:
- AI SDK 統合パターンの詳細については、
ai-sdk-coreスキルをご覧ください - Workers AI のストリーミング解析については、
cloudflare-workers-aiスキルをご覧ください
エージェントの呼び出し
主な 2 つのパターン:
-
routeAgentRequest(request, env)- URL パターン/agents/:agent/:nameによる自動ルーティング- 例:
/agents/my-agent/user-123は MyAgent インスタンス「user-123」にルーティングされます
- 例:
-
getAgentByName<Env, T>(env.AgentBinding, instanceName)- カスタムルーティング- メソッドの呼び出しやリクエストの受け渡しに使用するエージェントスタブを返します
- 例:
const agent = getAgentByName(env.MyAgent, 'user-${userId}')
マルチエージェント通信:
export class AgentA extends Agent<Env> {
async processData(data: any) {
const agentB = getAgentByName<Env, AgentB>(this.env.AgentB, 'processor-1');
return await (await agentB).analyze(data);
}
}
重要なセキュリティ上の注意:エージェントを作成またはアクセスする前に、必ず Worker で認証してください。エージェントは呼び出し元が認証済みであることを前提としてください。
参照:https://developers.cloudflare.com/agents/api-reference/calling-agents/
クライアント API
ブラウザー/React との統合:
AgentClient(agents/clientから)- ブラウザー用 WebSocket クライアントagentFetch(agents/clientから)- エージェントへの HTTP リクエストuseAgent(agents/reactから)- WebSocket 接続と状態同期のための React フックuseAgentChat(agents/ai-reactから)- すぐに使えるチャット UI フック
すべてのクライアントライブラリが自動的に処理します:WebSocket 接続、状態の同期、再接続ロジック。
参照:https://developers.cloudflare.com/agents/api-reference/client-apis/
Model Context Protocol(MCP)
Agents SDK を使用して MCP サーバーを構築します。
MCP サーバーのセットアップ
npm install @modelcontextprotocol/sdk agents
基本的な MCP サーバー
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new McpServer({ name: "Demo", version: "1.0.0" });
async init() {
// Define a tool
this.server.tool(
"add",
"Add two numbers together",
{
a: z.number().describe("First number"),
b: z.number().describe("Second number")
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
}
}
ステートフル MCP サーバー
type State = { counter: number };
export class StatefulMCP extends McpAgent<Env, State> {
server = new McpServer({ name: "Counter", version: "1.0.0" });
initialState: State = { counter: 0 };
async init() {
// Resource
this.server.resource(
"counter",
"mcp://resource/counter",
(uri) => ({
contents: [{ uri: uri.href, text: String(this.state.counter) }]
})
);
// Tool
this.server.tool(
"increment",
"Increment the counter",
{ amount: z.number() },
async ({ amount }) => {
this.setState({
...this.state,
counter: this.state.counter + amount
});
return {
content: [{
type: "text",
text: `Counter is now ${this.state.counter}`
}]
};
}
);
}
}
MCP トランスポートの設定
import { Hono } from 'hono';
const app = new Hono();
// Modern streamable HTTP transport (recommended)
app.mount('/mcp', MyMCP.serve('/mcp').fetch, { replaceRequest: false });
// Legacy SSE transport (deprecated)
app.mount('/sse', MyMCP.serveSSE('/sse').fetch, { replaceRequest: false });
export default app;
トランスポートの比較:
- /mcp:ストリーミング可能な HTTP(最新の方式、推奨)
- /sse:サーバー送信イベント(レガシー、非推奨)
MCP プロトコルのバージョンサポート
Agents SDK は複数の MCP プロトコルバージョンをサポートしています。[email protected] 以降では、新しいプロトコルバージョンを受け入れるため、バージョン検証が寛容になっています。
- サポート対象のバージョン:
2024-11-05、2025-11-25、および今後のバージョン - エラー(0.3.x より前):
Error: Unsupported MCP protocol version: 2025-11-25 - 修正内容:バージョン検証で、古すぎない任意のプロトコルバージョンが受け入れられるようになりました
これは、ステートレスなトランスポートへの MCP コミュニティの移行に沿ったものです。プロトコルバージョンのエラーが発生した場合は、[email protected] 以降に更新してください。
OAuth を使用した MCP
import { OAuthProvider } from '@cloudflare/workers-oauth-provider';
export default new OAuthProvider({
apiHandlers: {
'/sse': MyMCP.serveSSE('/sse'),
'/mcp': MyMCP.serve('/mcp')
},
// OAuth configuration
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
// ... other OAuth settings
});
MCP サーバーのテスト
# Run MCP inspector
npx @modelcontextprotocol/inspector@latest
# Connect to: http://localhost:8788/mcp
Cloudflare の MCP サーバー:本番環境での例については、リファレンスをご覧ください。
重要なルール
必ず行うこと ✅
- Agent クラスをエクスポートする - バインディングを機能させるには、エクスポートが必要です
- v1 のマイグレーションに new_sqlite_classes を含める - 後から SQLite を追加することはできません
- バインディング名をクラス名と一致させる - 「binding not found」エラーを防ぎます
- Agent ではなく Worker で認証する - セキュリティのベストプラクティスです
- SQL にはタグ付きテンプレートリテラルを使用する - SQL インジェクションを防ぎます
- WebSocket の切断に対処する - 状態は保持されますが、接続は保持されません
- スケジュール済みタスクのコールバックが存在することを確認する - メソッドが存在しない場合、エラーが発生します
- ユーザー固有のエージェントには idFromName() を使用する - 永続状態には絶対に newUniqueId() を使用しないでください(Issue #23 を参照)
- 状態のサイズ制限を確認する - エージェントごとの合計上限は 1GB です
- タスクのペイロードサイズを監視する - スケジュール済みタスクごとの上限は 2MB です
- ワークフローのバインディングを正しく使用する - wrangler.jsonc で設定する必要があります
- 挿入前に Vectorize インデックスを作成する - メタデータのフィルタリングに必要です
- ブラウザーのインスタンスを閉じる - リソースリークを防ぎます
- 永続化には setState() を使用する - this.state を変更するだけでは不十分です
- まずローカルでマイグレーションをテストする - マイグレーションはアトミックであり、ロールバックできません
- WebSocket メッセージ履歴を間引く - 累積ペイロードを 1MB 未満に保ってください(Issue #17 を参照)
- 実行時に状態の形状を検証する - TypeScript の型では実行時の検証は強制されません(「状態の型安全性」を参照)
絶対に行わないこと ❌
- デプロイ済みの既存クラスに SQLite を追加しない - 最初のマイグレーションに含める必要があります
- マイグレーションを段階的にデプロイしない - アトミックに行う必要があります
- Worker での認証を省略しない - エージェントへのアクセス前に必ず認証してください
- SQL 文字列を手動で構築しない - タグ付きテンプレートを使用してください
- エージェントごとの状態を 1GB 超にしない - 厳格な上限です
- 存在しないコールバックを指定してタスクをスケジュールしない - 実行時エラーが発生します
- ユーザー固有のエージェントに newUniqueId() を使用しない - 状態は保持されません(Issue #23 を参照)
- MCP に SSE を使用しない - 非推奨のため、/mcp トランスポートを使用してください
- ブラウザーバインディングを忘れない - Web ブラウジングに必要です
- this.state を直接変更しない - 代わりに setState() を使用してください
- WebSocket のペイロードを 1MB 超にしない - 接続がクラッシュします(Issue #17 を参照)
- 状態の検証を TypeScript の型に任せない - 実行時チェックを追加してください(「状態の型安全性」を参照)
既知の問題の予防
このスキルは、文書化された 23 件の問題を予防します。
Issue 1:マイグレーションがアトミックでない
エラー:「Cannot gradually deploy migration」
ソース:https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/
理由:マイグレーションはすべてのインスタンスに同時に適用されます
予防策:コード変更とは別にマイグレーションをデプロイし、npx wrangler versions deploy を使用してください
Issue 2: new_sqlite_classes の不足
エラー:「既存のクラスで SQLite を有効にできません」
参照元: https://developers.cloudflare.com/agents/api-reference/configuration/
理由: SQLite は最初のマイグレーションで有効にする必要があります
防止策: タグ「v1」のマイグレーションに new_sqlite_classes を含めます
Issue 3: Agent クラスがエクスポートされていない
エラー:「バインディングが見つかりません」または「undefined にアクセスできません」
参照元: https://developers.cloudflare.com/agents/api-reference/agents-api/
理由: Durable Objects ではクラスをエクスポートする必要があります
防止策: export class MyAgent extends Agent(export キーワードを付けます)
Issue 4: バインディング名の不一致
エラー:「バインディング 'X' が見つかりません」
参照元: https://developers.cloudflare.com/agents/api-reference/configuration/
理由: バインディング名はクラス名と完全に一致している必要があります
防止策: wrangler.jsonc で name と class_name が同一であることを確認します
Issue 5: グローバルな一意性が理解されていない
エラー: エージェントインスタンスで予期しない動作が発生する 参照元: https://developers.cloudflare.com/agents/api-reference/agents-api/ 理由: 同じ名前を指定すると、常にグローバルで同じエージェントインスタンスが返されます 防止策: インスタンス名には一意の識別子(userId、sessionId)を使用します
Issue 6: WebSocket の状態が永続化されない
エラー: 切断後に接続状態が失われる 参照元: https://developers.cloudflare.com/agents/api-reference/websockets/ 理由: WebSocket 接続は永続化されませんが、エージェントの状態は永続化されます 防止策: 接続状態ではなく、setState() を使って重要なデータをエージェントの状態に保存します
Issue 7: スケジュールされたタスクのコールバックが存在しない/長時間の AI リクエストがタイムアウトする
エラー: 「Agent にメソッド X が存在しません」または「非アクティブ状態のため IoContext がタイムアウトしました」または「blockConcurrencyWhile() の呼び出しが長時間待機しました」
参照元: https://developers.cloudflare.com/agents/api-reference/schedule-tasks/ および GitHub Issue #600
理由: this.schedule() が未定義のメソッドを呼び出しているか、AI リクエストが blockConcurrencyWhile ラッパーのために 30 秒を超えた際、スケジュールされたコールバックが失敗していました(PR #653 により [email protected] で修正済み)
防止策: スケジュールする前に、コールバックメソッドが存在することを確認します。長時間実行される AI リクエストでは、[email protected] 以降に更新してください。
過去の問題(0.2.x より前):スケジュールされたコールバックは blockConcurrencyWhile でラップされ、30 秒の制限が適用されていました。この制限を超える AI リクエストは、有効な非同期処理であっても失敗していました。
// ✅ Fixed in 0.2.x - schedule callbacks can now run for their full duration
export class MyAgent extends Agent<Env> {
async onRequest(request: Request) {
await this.schedule(60, "processAIRequest", { query: "..." });
}
async processAIRequest(data: { query: string }) {
// This can now take > 30s without timeout
const result = await streamText({
model: openai('gpt-4o'),
messages: [{ role: 'user', content: data.query }]
});
}
}
Issue 8: 状態サイズの上限超過
エラー:「データベースの最大サイズを超えました」 参照元: https://developers.cloudflare.com/agents/api-reference/store-and-sync-state/ 理由: エージェントの状態とスケジュールされたタスクの合計が 1GB を超えています 防止策: 状態のサイズを監視し、大きなデータには外部ストレージ(D1、R2)を使用します
Issue 9: スケジュールされたタスクが大きすぎる
エラー:「タスクのペイロードが 2MB を超えています」 参照元: https://developers.cloudflare.com/agents/api-reference/schedule-tasks/ 理由: 各タスクはデータベースの行に対応し、サイズ上限は 2MB です 防止策: タスクのペイロードを最小限に抑え、大きなデータはエージェントの状態/SQL に保存します
Issue 10: ワークフローバインディングがない
エラー:「undefined のプロパティ 'create' を読み取れません」 参照元: https://developers.cloudflare.com/agents/api-reference/run-workflows/ 理由: wrangler.jsonc でワークフローバインディングが設定されていません 防止策: this.env.WORKFLOW を使用する前に、ワークフローバインディングを追加します
Issue 11: Browser バインディングが必要
エラー:「BROWSER バインディングが undefined です」
参照元: https://developers.cloudflare.com/agents/api-reference/browse-the-web/
理由: Browser Rendering では明示的なバインディングが必要です
防止策: wrangler.jsonc に "browser": { "binding": "BROWSER" } を追加します
Issue 12: Vectorize インデックスが見つからない
エラー:「インデックスが存在しません」
参照元: https://developers.cloudflare.com/agents/api-reference/rag/
理由: 使用する前に Vectorize インデックスを作成する必要があります
防止策: エージェントをデプロイする前に wrangler vectorize create を実行します
Issue 13:MCP のトランスポートに関する混乱
エラー:「SSE トランスポートは非推奨です」
出典:https://developers.cloudflare.com/agents/model-context-protocol/transport/
理由:SSE トランスポートはレガシーであり、ストリーミング可能な HTTP が推奨されています
予防策:/sse ではなく、MyMCP.serve('/mcp') を使用して /mcp エンドポイントを利用します
Issue 14:認証のバイパス
エラー:セキュリティの脆弱性
出典:https://developers.cloudflare.com/agents/api-reference/calling-agents/
理由:認証が Worker ではなく Agent で行われています
予防策:getAgentByName() を呼び出す前に、必ず Worker で認証します
Issue 15:インスタンス名の誤り
エラー:ユーザー間のデータ漏えい
出典:https://developers.cloudflare.com/agents/api-reference/calling-agents/
理由:インスタンス名が適切でないと、誤ったエージェントにアクセスできる可能性があります
予防策:user-${userId} のような名前空間付きの名前を使用し、所有権を検証します
Issue 16:Workers AI のストリーミングには手動解析が必要
エラー:「Cannot read property 'response' of undefined」または AI の応答が空
出典:https://developers.cloudflare.com/workers-ai/platform/streaming/
理由:Workers AI はストリーミング応答を通常のオブジェクトではなく、Server-Sent Events(SSE)形式の Uint8Array として返します
予防策:TextDecoder と SSE 解析パターンを使用します(上記の「AI 向け Workers AI(代替手段)」セクションを参照)
問題 - ストリームのチャンクに直接アクセスしようとしても失敗します:
const response = await env.AI.run(model, { stream: true });
for await (const chunk of response) {
console.log(chunk.response); // ❌ undefined - chunk is Uint8Array, not object
}
解決策 - SSE 形式を手動で解析します:
const response = await env.AI.run(model, { stream: true });
for await (const chunk of response) {
const text = new TextDecoder().decode(chunk); // Step 1: Uint8Array → string
if (text.startsWith('data: ')) { // Step 2: Check SSE format
const jsonStr = text.slice(6).trim(); // Step 3: Extract JSON from "data: {...}"
if (jsonStr === '[DONE]') break; // Step 4: Handle termination
const data = JSON.parse(jsonStr); // Step 5: Parse JSON
if (data.response) { // Step 6: Extract .response field
fullResponse += data.response;
}
}
}
より良い代替手段:これを自動的に処理する Vercel AI SDK を使用します:
import { streamText } from 'ai';
import { createCloudflare } from '@ai-sdk/cloudflare';
const cloudflare = createCloudflare();
const result = streamText({
model: cloudflare('@cf/meta/llama-3-8b-instruct', { binding: env.AI }),
messages
});
// No manual parsing needed ✅
手動解析を許容する場合:
- コストが重要である(Workers AI のほうが安価です)
- 外部依存関係を追加できない
- SSE 解析コードの保守を許容できる
代わりに AI SDK を使用する場合:
- 計算コストより開発者の時間を重視する
- ストリーミングを自動化したい
- 複数プロバイダーのサポートが必要である
Issue 17:WebSocket のペイロードサイズ制限(1MB)
エラー:Error: internal error; reference = [reference ID]
出典:GitHub Issue #119
理由:累積メッセージペイロードが約 1 MB を超えると、WebSocket 接続が失敗します。大きなデータ(例:それぞれ 200KB 以上)を返すツール呼び出しを 5~6 回行うと、ペイロードが上限を超え、「internal error」が発生して接続がクラッシュします。
予防策:クライアント側でメッセージ履歴を削減し、950KB 未満に保ちます
問題:大きなツールの結果を含むすべてのメッセージがクライアントと LLM にストリーミングで返され、会話を継続するため、ペイロードが累積して増加します。
// Workaround: Prune old messages client-side
function pruneMessages(messages: Message[]): Message[] {
let totalSize = 0;
const pruned = [];
// Keep recent messages until we hit size limit
for (let i = messages.length - 1; i >= 0; i--) {
const msgSize = JSON.stringify(messages[i]).length;
if (totalSize + msgSize > 950_000) break; // 950KB limit
pruned.unshift(messages[i]);
totalSize += msgSize;
}
return pruned;
}
// Use before sending to agent
const prunedMessages = pruneMessages(allMessages);
より良い解決策(提案):完全なツールの結果ではなく、メッセージの要約のみをクライアントに送信するサーバー側のコンテキスト管理を行います。
Issue 18:needsApproval ツールでのアシスタントメッセージの重複
エラー:同一の toolCallId を持つメッセージが重複し、元のメッセージが input-available 状態のままになります
出典:GitHub Issue #790
理由:ツールで needsApproval: true を使用すると、元のメッセージが更新されず、システムによってアシスタントメッセージが重複して作成されます。クライアントが承認しても、サーバーが生成したメッセージ(状態は input-available)は approval-responded に遷移しません。
予防策:これは既知の制限事項だと理解しておきます。修正されるまでは、UI で両方のメッセージ ID を追跡します。
// Current behavior ([email protected])
export class MyAgent extends AIChatAgent<Env> {
tools = {
sensitiveAction: tool({
needsApproval: true, // ⚠️ Causes duplicate messages
execute: async (args) => {
return { result: "action completed" };
}
})
};
}
// Result: Two messages persist
// 1. Server message: ID "assistant_1768917665170_4mub00d32", state "input-available"
// 2. Client message: ID "oFwQwEpvLd8f1Gwd", state "approval-responded"
回避策:UI で両方のメッセージを処理するか、この問題が解決するまで needsApproval の使用を避けます。
問題 19:クライアント側でのツール実行時にメッセージが重複する(0.2.31+ で修正済み)
エラー:Duplicate item found with id rs_xxx(OpenAI API による拒否)
出典:GitHub Issue #728
修正バージョン:[email protected]+
原因:サーバー側の実行関数を持たないクライアント側ツールで useAgentChat を使用すると、agents ライブラリが同じ reasoning ID を持つアシスタントメッセージを重複して作成し、OpenAI API に拒否されていました。
予防策:[email protected] 以降に更新してください
過去の問題(0.2.31 より前):クライアント側でのツール実行時に、既存のメッセージを更新する代わりに新しいメッセージが作成されていました。その結果、元のメッセージが未完了の状態で残り、providerMetadata.openai.itemId の値が重複していました。
// ✅ Fixed in 0.2.31+ - no changes needed
// Just ensure you're on [email protected] or later
問題 20:非同期クエリでキャッシュ TTL が適用されない(修正済み)
エラー:cacheTtl を設定しているにもかかわらず、トークンの有効期限切れ後に 401 Unauthorized エラーが発生する
出典:GitHub Issue #725
修正バージョン:agents のリリースノートを確認してください
原因:useAgent フックにキャッシュの問題があり、queryPromise が各 cacheKey に対して一度だけ計算され、その後 TTL が切れても保持されていました。useMemo の実装で依存配列に時間が含まれていなかったため、TTL が適用されていませんでした。
予防策:agents を最新バージョンに更新してください
過去の回避策(修正前):
// Force cache invalidation by including token version in queryDeps
const [tokenVersion, setTokenVersion] = useState(0);
const { state } = useAgent({
query: async () => ({ token: await getJWT() }),
queryDeps: [tokenVersion], // ✅ Force new cache key
cacheTtl: 60_000,
});
// Manually refresh token before expiry
useEffect(() => {
const interval = setInterval(() => {
setTokenVersion(v => v + 1);
}, 50_000); // Refresh every 50s
return () => clearInterval(interval);
}, []);
問題 21:DO のハイバネーション後に jsonSchemaValidator が機能しなくなる(修正済み)
エラー:TypeError: validator.getValidator is not a function
出典:GitHub Issue #663
修正バージョン:agents のリリースノートを確認してください
原因:Durable Object がハイバネーション後に復元される際、Agents SDK は JSON.stringify() を使用して MCP 接続オプションをシリアライズしていました。その結果、CfWorkerJsonSchemaValidator などのクラスインスタンスがメソッドを持たないプレーンオブジェクトに変換されていました。JSON.parse() による復元後、バリデーターはメソッドを失っていました。
予防策:agents を最新バージョンに更新してください(バリデーターは現在組み込みです)
過去の問題(修正前):
// ❌ Before fix - manual validator caused errors
import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/sdk/cloudflare-worker';
const mcpAgent = new McpAgent({
client: {
jsonSchemaValidator: new CfWorkerJsonSchemaValidator(), // Got serialized to {}
}
});
現在の方法(修正済み):
// ✅ Now automatic - no manual validator needed
const mcpAgent = new McpAgent({
// SDK handles validator internally
});
問題 22:ハイバネーション後に WorkerTransport の ClientCapabilities が失われる(0.3.5+ で修正済み)
エラー:Error: Client does not support form elicitation
出典:GitHub Issue #777
修正バージョン:[email protected]+
原因:サーバーレス環境で MCP サーバーと WorkerTransport を使用すると、TransportState インターフェースに sessionId と initialized の状態だけが保存され、clientCapabilities が保存されないため、Durable Object のハイバネーションサイクルをまたいでクライアントの機能が保持されませんでした。
予防策:[email protected] 以降に更新してください
過去の問題(0.3.5 より前):
// Client advertised elicitation capability during handshake,
// but after hibernation, capability info was lost
await server.elicitInput({ /* form */ }); // ❌ Error: capabilities lost
解決策(0.3.5 で修正):
// TransportState now includes clientCapabilities
interface TransportState {
sessionId: string;
initialized: boolean;
clientCapabilities?: ClientCapabilities; // ✅ Now persisted
}
問題 23:idFromName() と newUniqueId() の重要な使い分け
エラー:状態が永続化されず、リクエストごとに新しいエージェントインスタンスが作成される
出典:Cloudflare ブログ - OpenAI を使ったエージェントの構築
原因:idFromName() の代わりに newUniqueId() を使用すると、呼び出すたびに新しいエージェントインスタンスが作成され、メモリや状態が永続化されません。これは状態の永続性をひそかに損なう、初期段階でよくあるバグです。
予防策:ユーザーごとのエージェントには必ず idFromName() を使用し、newUniqueId() は使用しないでください
// ❌ WRONG: Creates new agent every time (state never persists)
export default {
async fetch(request: Request, env: Env) {
const id = env.MyAgent.newUniqueId(); // New ID = new instance
const agent = env.MyAgent.get(id);
// State never persists - different instance each time
return agent.fetch(request);
}
}
// ✅ CORRECT: Same user = same agent = persistent state
export default {
async fetch(request: Request, env: Env) {
const userId = getUserId(request);
const id = env.MyAgent.idFromName(userId); // Same ID for same user
const agent = env.MyAgent.get(id);
// State persists across requests for this user
return agent.fetch(request);
}
}
重要な理由:
newUniqueId():呼び出すたびにランダムな一意の ID を生成 → 新しいエージェントインスタンスidFromName(string):文字列から決定的な ID を生成 → 同じ入力には同じエージェント
目安:99% のケースで idFromName() を使用してください。一度限りの一時的なエージェントインスタンスが本当に必要な場合に限り、newUniqueId() を使用します。
依存関係
必須
- cloudflare-worker-base - 基盤(Hono、Vite、Workers のセットアップ)
オプション(機能に応じて)
- cloudflare-workers-ai - Workers AI のモデル呼び出し用
- cloudflare-vectorize - Vectorize を使った RAG 用
- cloudflare-d1 - エージェントの状態以外の永続ストレージを追加する場合
- cloudflare-r2 - ファイルストレージ用
- cloudflare-queues - メッセージキュー用
NPM パッケージ
agents- Agents SDK(必須)@modelcontextprotocol/sdk- MCP サーバーの構築用@cloudflare/puppeteer- ウェブ閲覧用ai- モデル呼び出し用の AI SDK@ai-sdk/openai- OpenAI モデル@ai-sdk/anthropic- Anthropic モデル
公式ドキュメント
- Agents SDK: https://developers.cloudflare.com/agents/
- API リファレンス: https://developers.cloudflare.com/agents/api-reference/
- Durable Objects: https://developers.cloudflare.com/durable-objects/
- Workflows: https://developers.cloudflare.com/workflows/
- Vectorize: https://developers.cloudflare.com/vectorize/
- Browser Rendering: https://developers.cloudflare.com/browser-rendering/
- Model Context Protocol: https://modelcontextprotocol.io/
- Cloudflare MCP サーバー: https://github.com/cloudflare/mcp-server-cloudflare
同梱リソース
テンプレート(templates/)
wrangler-agents-config.jsonc- 設定の完全な例basic-agent.ts- 最小構成の HTTP エージェントwebsocket-agent.ts- WebSocket ハンドラーstate-sync-agent.ts- 状態管理のパターンscheduled-agent.ts- タスクのスケジューリングworkflow-agent.ts- ワークフローとの統合browser-agent.ts- ウェブ閲覧rag-agent.ts- RAG の実装chat-agent-streaming.ts- ストリーミングチャットcalling-agents-worker.ts- エージェントのルーティングreact-useagent-client.tsx- React クライアントmcp-server-basic.ts- MCP サーバーhitl-agent.ts- Human-in-the-loop
リファレンス(references/)
agent-class-api.md- Agent クラスの完全なリファレンスclient-api-reference.md- ブラウザークライアント APIstate-management-guide.md- 状態と SQL の詳細解説websockets-sse.md- WebSocket と SSE の比較scheduling-api.md- タスクスケジューリングの詳細workflows-integration.md- Workflows ガイドbrowser-rendering.md- ウェブ閲覧のパターンrag-patterns.md- RAG のベストプラクティスmcp-server-guide.md- MCP サーバー開発mcp-tools-reference.md- MCP ツール APIhitl-patterns.md- Human-in-the-loop ワークフローbest-practices.md- 本番環境向けのパターン
例(examples/)
chat-bot-complete.md- 完全なチャットエージェントmulti-agent-workflow.md- エージェントのオーケストレーションscheduled-reports.md- 定期タスクbrowser-scraper-agent.md- ウェブスクレイピングrag-knowledge-base.md- RAG システムmcp-remote-server.md- 本番環境向け MCP サーバー
最終確認日: 2026-01-21 パッケージバージョン: [email protected] 準拠状況: Cloudflare Agents SDK の公式ドキュメント 変更内容: コミュニティ調査に基づく新しい課題を 7 件追加(WebSocket ペイロードの制限、状態の型安全性、idFromName の注意点)。[email protected] に更新