effidevFlutter・Cloudflareエッジ・クラウドコスト最適化
日本語

Cloudflare Agents SDK:ステートフルAIエージェント構築ガイド

Cloudflare Agents SDKによるステートフルAIエージェント構築

LLMをAPI経由で呼び出して回答を受け取るだけなら、誰にでもできます。問題はその先です。エージェントが過去の会話を記憶し、外部ツールを呼び出し、途中で人間の承認を待ってから実行を再開し、クローンスケジュールに従って自律的にタスクを開始する——いわゆる**「ステートフル(stateful)プロダクションエージェント」**を構築するには、状態管理、永続ストレージ、リアルタイム通信、長期間実行ワークフローをすべて自分で組み合わせる必要があります。Redis + PostgreSQL + WebSocketサーバー + キュー + クローンジョブを個別に運用すると、インフラ自体がエージェントよりも複雑になってしまいます。

Cloudflareが2026年のAgents Weekで公開したAgents SDKは、この問題をDurable Objectsという単一の基盤上で解決します。1つのエージェントインスタンスがそのまま1つのDurable Objectとなり、内蔵SQLiteに状態を永続化し、WebSocketでフロントエンドとリアルタイム通信を行い、アラームやクローンで自律実行まで処理します。追加のインフラなしでwrangler deployを1回実行するだけで、世界300以上のPoPにデプロイされます。

本記事では、Agents SDKのアーキテクチャからAgentクラスの実装、@callable()デコレータを使用した型安全RPC、useAgent/useAgentChat Reactフックとの連携、Human-in-the-Loop承認パターン、そして本番デプロイ時に注意すべき制限やコストまで、実際に本番運用できるレベルで解説します。

要点まとめ

  • Agents SDKのエージェントはDurable Objects上で動作し、インスタンスごとに最大1GBの内蔵SQLite30秒のCPU時間(リクエストごとにリセット)、壁時計時間基準で無制限の待機が可能です。
  • Agentクラスを継承し、@callable()デコレータでメソッドを公開すると、フロントエンドからuseAgentフックでWebSocketベースの型安全RPCを即座に呼び出せます。
  • Human-in-the-LoopはneedsApprovalフラグでツール呼び出しをゲーティングするか、Cloudflare WorkflowsのwaitForApproval()により数日〜数ヶ月の待機までサポートします。
  • エージェントはクローンスケジュールやアラームによって自律的にタスクを開始できるため、リクエスト-レスポンスのパターンに縛られません。
  • Workers Paidプラン基準のDurable Objects料金で課金され、1アカウントあたり数千万個の同時エージェントインスタンスを運用できます。

Agents SDKのアーキテクチャ:なぜDurable Objectsなのか

従来のサーバーレスAIエージェントの実装方式は、そのほとんどが**「ステートレスな関数 + 外部ストレージ」**の組み合わせです。LambdaやCloud FunctionsでLLMを呼び出し、会話履歴はRedisやDynamoDBに、長期間実行状態はStep Functionsに、リアルタイム通信は別途WebSocketサーバーに委ねます。1つのエージェントのために4〜5つのサービスを組み合わせる必要があり、各サービス間の整合性を保つグルーコードがエージェント自体のロジックよりも長くなってしまいます。

Agents SDKはこの組み合わせをDurable Objects 1つへと凝縮します。Cloudflare公式アーキテクチャドキュメントによると、エージェントインスタンスの構造は以下の通りです。

役割 Durable Objectsが提供するもの 従来の構造で対応するサービス
コンピューティング シングルスレッド isolate、リクエストごとにCPU 30秒 Lambda / Cloud Run
永続状態 内蔵SQLite(インスタンスごとに最大1GB) DynamoDB / Redis
リアルタイム通信 ネイティブWebSocket(Hibernation対応) Pusher / Socket.IO サーバー
スケジューリング アラームAPI + クローン表現式 CloudWatch Events / cron サーバー
グローバルルーティング グローバルユニークIDによりどこからでも同じインスタンスにアクセス Route 53 + ロードバランサー
障害耐性 自動マイグレーション、障害時は別のPoPで再起動 自作実装

核心となるのは**「エージェントインスタンス = Durable Objectインスタンス」という1:1のマッピングです。エージェントがthis.setState()を呼び出すと、内蔵SQLiteに即座に記録され、接続されているすべてのWebSocketクライアントへ状態変更が自動的にブロードキャストされます。エージェントがLLMの応答を待っている間も、Durable Objectは壁時計時間基準で無制限に待機できるため(CPU時間を消費しないため)、外部キューやStep Functionsを使わずに長期間実行ワークフローをエージェント内部で**処理できます。

この構造の最大の実質的メリットはデプロイ複雑性の消滅です。wrangler deployを1回実行するだけで、エージェントコード、状態ストレージ、WebSocketエンドポイント、スケジューラがすべて一括でCloudflareのグローバルネットワーク上に配置されます。別途データベースのプロビジョニングやWebSocketサーバーのスケーリング、クローンジョブの設定を行う必要はありません。

Agentクラスの実装:状態管理と@callable RPC

エージェントの基本骨格は、Agentクラスを継承することから始まります。GitHubのagents-starterテンプレートを基準にプロジェクトを作成し、核心的な構造を見ていきましょう。

# 프로젝트 생성 (2026-08 기준 최신 템플릿)
npm create cloudflare@latest -- --template cloudflare/agents-starter my-agent
cd my-agent

以下は、カスタマーサポートチケットを処理するエージェントのサーバーサイド実装例です。このコードが解決するのは、状態の永続化 + 型安全RPC + ツール呼び出しを1つのクラス内で処理するパターンです。

// src/server.ts
import { Agent, callable } from "agents";
import { AIChatAgent } from "@cloudflare/ai-chat";

// 에이전트 상태 타입 정의
export interface TicketState {
  ticketId: string | null;
  status: "idle" | "investigating" | "waiting-approval" | "resolved";
  history: Array<{ role: string; content: string; timestamp: number }>;
  metadata: Record<string, string>;
}

export class SupportAgent extends AIChatAgent<Env, TicketState> {
  // 초기 상태 — Durable Object 생성 시 SQLite에 자동 저장
  initialState: TicketState = {
    ticketId: null,
    status: "idle",
    history: [],
    metadata: {},
  };

  // @callable()로 노출한 메서드는 프론트엔드에서 agent.stub.assignTicket() 으로 호출 가능
  @callable()
  async assignTicket(ticketId: string, priority: string): Promise<string> {
    // setState()는 SQLite에 즉시 기록 + 연결된 모든 클라이언트에 브로드캐스트
    this.setState({
      ...this.state,
      ticketId,
      status: "investigating",
      metadata: { ...this.state.metadata, priority },
    });

    // Workers AI로 티켓 분석 (에지에서 추론)
    const analysis = await this.env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
      messages: [
        { role: "system", content: "고객 지원 티켓을 분석하고 해결 방안을 제시하라." },
        { role: "user", content: `티켓 ${ticketId}, 우선순위: ${priority}` },
      ],
    });

    return analysis.response ?? "분석 완료";
  }

  @callable()
  async getStatus(): Promise<TicketState> {
    return this.state;
  }

  // 크론 스케줄: 매 시간 미해결 티켓 체크
  async onCronTrigger(cron: string): Promise<void> {
    if (this.state.status === "investigating") {
      const elapsed = Date.now() - (this.state.history.at(-1)?.timestamp ?? 0);
      if (elapsed > 3600_000) {
        // 1시간 이상 미해결 → 에스컬레이션 알림
        await this.escalate();
      }
    }
  }

  private async escalate(): Promise<void> {
    this.setState({ ...this.state, metadata: { ...this.state.metadata, escalated: "true" } });
    // 슬랙 웹훅, 이메일 등 알림 전송 로직
  }
}

注意点:@callable()デコレータはWebSocketベースのRPC専用です。同じWorker内部や他のDurable Objectからエージェントメソッドを呼び出す場合は、標準のDurable Object RPC(this.env.AGENT.get(id))を使用します。@callable()を内部呼び出しにも使用すると、不要なWebSocketオーバーヘッドが追加されてしまいます。

wrangler.jsoncの設定で、エージェントをDurable Objectとしてバインディングする部分も忘れずに設定する必要があります。

// wrangler.jsonc
{
  "name": "support-agent",
  "main": "src/index.ts",
  "compatibility_date": "2026-07-28",
  "durable_objects": {
    "bindings": [
      {
        "name": "SUPPORT_AGENT",
        "class_name": "SupportAgent"
      }
    ]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["SupportAgent"] }
  ],
  "ai": { "binding": "AI" }
}

Reactフロントエンド連携:useAgentとuseAgentChatフック

Agents SDKはReact専用のフックを提供し、フロントエンドとエージェント間の接続を宣言的に処理します。公式React連携ガイドで提供されている3つのフックの役割は明確に異なります。

フック パッケージ 用途
useAgent agents/react 汎用エージェント接続。状態同期 + RPC呼び出し
useAgentChat @cloudflare/ai-chat/react チャット特化。メッセージ永続化、ストリーミング、ツール実行を自動処理
useVoiceAgent @cloudflare/voice 音声STT/TTS。同じWebSocket上でリアルタイム音声処理

以下は、useAgentを使用して先ほど作成したSupportAgentと接続するReactコンポーネントの例です。このコードが解決するのは、WebSocket接続管理 + 状態自動同期 + 型安全RPC呼び出しを宣言的に処理するパターンです。

// src/client.tsx
import { useAgent } from "agents/react";
import { useState, useCallback } from "react";
import type { SupportAgent, TicketState } from "./server";

function SupportDashboard() {
  const [ticket, setTicket] = useState<TicketState | null>(null);
  const [ticketId, setTicketId] = useState("");

  // useAgent는 WebSocket 연결을 자동으로 관리하고,
  // 에이전트의 setState() 호출 시 onStateUpdate가 자동으로 트리거된다
  const agent = useAgent<SupportAgent, TicketState>({
    agent: "SupportAgent",
    onStateUpdate: (state) => setTicket(state),
    onError: (error) => console.error("Agent connection error:", error),
  });

  const handleAssign = useCallback(async () => {
    if (!agent.stub || !ticketId) return;
    // 타입 안전한 RPC 호출 — @callable() 메서드만 stub에 노출됨
    const result = await agent.stub.assignTicket(ticketId, "high");
    console.log("분석 결과:", result);
  }, [agent.stub, ticketId]);

  return (
    <div>
      <input
        value={ticketId}
        onChange={(e) => setTicketId(e.target.value)}
        placeholder="티켓 ID 입력"
      />
      <button onClick={handleAssign} disabled={!agent.connected}>
        티켓 할당
      </button>
      {ticket && (
        <div>
          <p>상태: {ticket.status}</p>
          <p>우선순위: {ticket.metadata.priority ?? "미지정"}</p>
        </div>
      )}
    </div>
  );
}

useAgentChatはより特化したフックであり、会話履歴のSQLite永続化、LLMストリーミング応答処理、ツール呼び出し結果の自動インライン化まで内部的に処理します。チャットUIを作成する際は、useAgentの代わりにuseAgentChatを使用することで、コード量を半分以下に削減できます。

Human-in-the-Loop:ツール承認ゲーティングと長期待機パターン

本番環境のAIエージェントにおいて最も難度の高い部分の1つが、**「エージェントが危険な操作を実行する前に人間の確認を得ること」**です。Agents SDKはこれを3つのレベルでサポートしています。

パターン1:needsApprovalによるツールゲーティング。 ツール定義時に needsApproval: true を設定すると、エージェントが該当ツールを呼び出そうとした際に実行を一時停止し、approval-requested 状態へ遷移します。フロントエンドから承認または拒否を送信すると、エージェントが実行を継続または中断します。

// 도구 정의 예시 — 결제 처리 도구에 승인 게이트 적용
const tools = [
  {
    name: "process_refund",
    description: "고객에게 환불을 처리한다",
    parameters: {
      type: "object",
      properties: {
        amount: { type: "number", description: "환불 금액 (USD)" },
        reason: { type: "string", description: "환불 사유" },
      },
      required: ["amount", "reason"],
    },
    // 이 플래그가 핵심 — 에이전트가 이 도구를 호출하려 하면 일시 정지
    needsApproval: true,
  },
];

注意点:needsApprovalセッション内のリアルタイム承認に適しています。ユーザーがブラウザを閉じるとWebSocketが切断されるため、承認待機状態が数時間以上続くようなシナリオには適していません。

パターン2:Cloudflare WorkflowsのwaitForApproval()。 長期待機が必要な場合(例:管理者による承認が翌日まで届かない可能性があるワークフロー)、Cloudflare WorkflowswaitForApproval() メソッドを使用します。このメソッドはDurable Objectの上に永続的なゲートを構築します。エージェントインスタンスがメモリから解放されても(Hibernation)、承認イベントが届くと自動的に復帰してワークフローを再開します。数日から数ヶ月におよぶ待機が可能です。

パターン3:MCP Elicitation。 エージェントがModel Context Protocol(MCP)サーバーと通信する際、MCPサーバー側で追加情報が必要な場合に configureElicitationHandlers() を通じてユーザーへ直接質問を送信できます。エージェントがMCPクライアントとして複数の外部ツールをオーケストレーションする際に有用です。

スケジューリングと自律実行:クローン、アラーム、ウェブフック

従来のAIチャットボットは、ユーザーがメッセージを送信して初めて動作します。本番環境のエージェントは自らタスクを開始できる必要があります。Agents SDKのエージェントはDurable ObjectsのアラームAPIを継承し、3つの自律実行パターンをサポートしています。

トリガー 設定方法 適したシナリオ
クローンスケジュール wrangler.jsonctriggers.crons 毎時/日次の定期タスク(レポート、モニタリング)
アラーム this.ctx.storage.setAlarm(timestamp) 特定のタイミングで1回実行(予約送信、リマインダー)
ウェブフック受信 Workerの fetch ハンドラからエージェントへルーティング 外部イベントへの反応(Stripe決済完了、GitHub PRマージ)
メール受信 email() 핸들러 メールベースの自動応答・分類

この構造の最大の強みは、エージェントがアイドル状態のときはコストが0になる点です。Durable ObjectsのHibernation APIのおかげで、WebSocket接続を維持したままエージェントをメモリから解放できます。アラームやクローンがトリガーされると自動的に復帰してタスクを実行し、再びスリープ状態に戻ります。常に稼働し続けるVMインスタンスが不要であるため、数千個のエージェントを同時にデプロイしても実際にアクティブになったエージェントに対してのみ課金されます。

// 크론 트리거 설정 (wrangler.jsonc)
{
  "triggers": {
    "crons": ["0 */6 * * *"]  // 매 6시간마다 실행
  }
}

本番デプロイ:制限、コスト、運用チェックリスト

Agents SDKを本番環境へ導入する際に必ず確認すべき制限とコスト構造を整理します。2026年8月時点のCloudflare Workers料金ページおよびDurable Objects料金ページの数値です。

項目 備考
アカウントあたりの同時エージェントインスタンス 数千万個以上 ハードリミットは申請により引き上げ可能
インスタンスあたりの最大状態サイズ 1GB (SQLite) Key-Valueストレージも別途使用可能
リクエストごとのCPU時間 30秒 HTTPリクエストまたはWebSocketメッセージごとにリセット
壁時計待機時間 無制限 LLM応答待機などはCPUを消費しない
エージェント定義数 ~250,000 アカウント全体
Durable Objects料金 (Paid) リクエスト100万件あたり$0.15 + GB-秒あたり$12.50/月 Hibernation時は壁時計時間に課金されない
Workers AI推論 モデルにより異なる Llama 3.3 70B: 入力1Mトークン $0.27

運用における3つの注意点:

  1. SQLiteの1GB制限を超えないよう状態を整理する。 会話履歴が無制限に蓄積されると制限に達します。古い履歴はR2にアーカイブし、SQLiteには直近N件のみを維持する戦略が必要です。

  2. CPU 30秒の制限を意識する。 LLMの呼び出し自体は await 待機であるためCPUを消費しませんが、応答のパースや後処理のロジックが複雑な場合、CPU 30秒を超える可能性があります。重い後処理は別のWorkerへ分離するか、Cloudflare Queuesで非同期処理します。

  3. Hibernationを積極活用する。 WebSocket接続が存在する場合でも hibernateWebSocket() を使用すれば、メッセージがない間エージェントをメモリから解放できます。これにより、GB-秒課金がアクティブな処理時間のみに適用され、コストを大幅に削減できます。WebSocket Hibernationガイドにて、このパターンのコスト削減効果を詳細に解説しています。

既存のエージェントフレームワークとの違い:LangGraph、Mastraとの比較

Agents SDKが唯一の選択肢というわけではありません。同時期に活発に使用されているエージェントフレームワークと比較することで、Agents SDKのポジショニングが明確になります。

比較項目 Cloudflare Agents SDK LangGraph Mastra
実行環境 Cloudflareエッジ(専用) どこでも(クラウド、ローカル) どこでも(Node.js)
状態管理 Durable Objects内蔵SQLite 外部チェックポインタ(Redis, Postgres) 外部DBが必要
リアルタイム通信 ネイティブWebSocket + Reactフック 自作実装またはLangServe 自作実装
長期間実行 Hibernation + Workflows AsyncIO + 外部キュー 外部キューが必要
デプロイ wrangler deploy 1コマンド コンテナ/サーバーレスの直接設定 コンテナ/サーバーレスの直接設定
ベンダーロックイン 高い(Cloudflare専用) 低い 低い
強み インフラゼロ、グローバルエッジ、コスト効率 グラフベースの複雑なワークフロー、柔軟性 簡潔なAPI、迅速なプロトタイ핑

Agents SDKの核心的なトレードオフは、**「インフラの複雑性を0にする代わりにCloudflareへ完全に依存する」**という点です。すでにCloudflare Workersを主軸として活用しているチームであれば、Agents SDKが最も素早く本番環境へ到達できる経路となります。一方でマルチクラウド戦略が必須であったり、エージェントの状態グラフが複雑なDAG/循環構造を要求する場合は、LangGraphの方がより適しています。

よくある質問

Agents SDKとWorkers AIの違いは何ですか?

Workers AIはモデル推論APIです。LlamaやWhisperといったモデルをエッジで実行するエンジンです。Agents SDKはエージェントフレームワークであり、状態管理・ツール呼び出し・ユーザー相互作用・スケジューリングなど、エージェントの「脳」を構成するインフラストラクチャです。通常はAgents SDK内部からWorkers AIを推론バックエンドとして呼び出す形で組み合わせて使用します。

1つのエージェントに複数のユーザーを接続できますか?

可能です。Durable Objectは複数のWebSocket接続を同時に受け入れます。エージェントIDをチャットルームIDのように使用すれば、同じエージェントに複数のユーザーが接続してリアルタイムに状態を共有できます。ただし、Durable Objectのシングルスレッドという特性上、極端に書き込みが集中するシナリオ(秒間数千件)ではパフォーマンス上の限界があります。

既存のWorkersプロジェクトにAgents SDKを追加できますか?

可能です。agents パッケージをインストールし、wrangler.jsonc にDurable Objectsバインディングを追加したうえで、Workerの fetch ハンドラから routeAgentRequest(request, env) を呼び出せば、既存のルーティングとエージェントルーティングが共存できます。既存のAPIエンドポイントはそのまま維持しながら、エージェント機能だけを段階的に追加できます。

ローカル開発はどのように行いますか?

wrangler dev でローカル開発サーバーを実行すると、Durable Objects、SQLite、WebSocketがすべてローカルでエミュレートされます。本番環境と同一のAPIサーフェスをローカルでテストできるため、外部サービスへの依存なしで開発-テストサイクルをスピーディーに回すことができます。