連載の現在地:Phase 5「自動化」(1 / 3本目) — 人が呼ばなくても裏で働く仕組みにする
1. 公開 → 2. 費用と安定 → 3. 安全 → 4. 品質 → 5. 自動化
こんな悩みの人へ
手元のパソコンで動かしているAIエージェントを、いつでも動く状態にしたい。定期的に情報を集めて、変化があれば知らせてくれるエージェントを作りたい。ユーザーごとに、会話の続きや作業の途中経過を覚えさせたい。そんな方に向けた記事です。
結論
CloudflareのAgents SDKがおすすめです。 エージェント1体ごとに、状態(覚えていること)を保存する仕組みと、決まった時刻に自分を起こす仕組みが用意されています。サーバーを常に起動しておく必要はなく、リクエストや予定の時刻が来たときだけ動きます。
一方、ずっと処理を回し続ける重い計算や、Python前提のエージェントフレームワークをそのまま使いたい場合は、コンテナやVPSのほうが向いています。
なぜ「常時稼働」が難しいのか
「常に動いている」といっても、その中身はほとんどが待ち時間です。実際のエージェントは、次の指示や予定の時刻、外部APIの応答を待っている時間が大半を占めます。そのためにサーバーを起動し続けると、費用も保守も無駄が多くなります。
状態をどこに置くかでも、設計が変わります。会話の履歴、作業の途中経過、ユーザーの設定。メモリに置けば再起動で消え、データベースに置けば読み書きの設計が必要になります。
複数のユーザーの状態を扱うアプリでは、利用者ごとの分離も必要です。認証した利用者と保存先の対応付けや、同時更新の設計が欠かせません。とはいえ、1つのプロセスを使うだけで必ず混ざる、という意味ではありません。
Agents SDKでは、エージェントのインスタンスをDurable Object上で動かします。インスタンスごとに保存領域があり、アプリがインスタンス名を割り当てて、ユーザーごと・用途ごとに分けられます。認証した利用者と割り当て先の照合は、アプリ側で行います。
この記事で作る構成は、次のとおりです。
[クライアント] --WebSocket / HTTP--> [Worker: routeAgentRequest()]
| /agents/watch-agent/{インスタンス名}
v
[WatchAgent インスタンス(名前ごとに1つ)]
state: keywords / lastCheckedAt (自動で保存)
schedule(): 予定の時刻に、自分を起こす解決策の比較
方法 | 費用 | 手間 | 向いている状況 |
|---|---|---|---|
Cloudflare Agents SDK | 無料枠あり。本格運用はWorkers有料プラン | 中くらい | ユーザーごとに状態を持つ。待ち時間が多い |
VPSで常駐プロセス | 月数百円〜 | 多い | Python製のフレームワークをそのまま使う。重い処理を回し続ける |
Cloud Runなど + スケジューラー | 使った分だけ | 中くらい | 定期実行が中心で、状態は外部のデータベースに置ける |
GitHub Actionsの定期実行 | 無料枠あり | 少ない | 1日数回の情報収集など、軽い定期作業だけ |
どれを選ぶか
定期的に1つの作業をするだけなら、GitHub Actionsや、Workersの定期実行(Cron Triggers)で足ります。ユーザーごとに会話や作業の状態を持たせたいならAgents SDKを、PythonのエージェントライブラリやGPUが必要ならVPSやコンテナを選びます。
手順
前提:Cloudflareのアカウントと、Node.js 22.19以降が入ったパソコン。設定例は現在のWranglerを前提にしています。
- TypeScriptのWorkerプロジェクトを作り、Agents SDKを追加します。以下は画面を含まない、サーバー側の最小構成です。
npm create cloudflare@latest -- watch-agent --type=hello-world --lang=ts
cd watch-agent
npm install agents@latest
npm install --save-dev wrangler@latestsrc/index.tsにエージェントの中身を書きます。状態はthis.stateで読み、this.setState()で更新します。更新した状態は自動で保存されます。接続する画面を作れば、状態を同期することもできます。
import { Agent, routeAgentRequest, callable } from "agents";
interface Env {
WatchAgent: DurableObjectNamespace<WatchAgent>;
}
type WatchState = { keywords: string[]; lastCheckedAt: string | null };
export class WatchAgent extends Agent<Env, WatchState> {
initialState: WatchState = { keywords: [], lastCheckedAt: null };
@callable()
addKeyword(keyword: string) {
this.setState({ ...this.state, keywords: [...this.state.keywords, keyword] });
return this.state.keywords;
}
}
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;this.state を直接書き換えると保存されません。必ず setState() を使います。
- 設定ファイル(
wrangler.jsonc)に、エージェントのクラスを登録します。exportsにクラスとstorage: "sqlite"を指定すると、SQLiteを使うDurable Objectとして宣言できます。
{
"name": "watch-agent",
"main": "src/index.ts",
"compatibility_date": "2026-11-03",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [{ "name": "WatchAgent", "class_name": "WatchAgent" }]
},
"exports": {
"WatchAgent": { "type": "durable-object", "storage": "sqlite" }
}
}WatchAgentクラスの中に、次のメソッドを追加します。クライアントからscheduleCheck()を呼ぶと、60秒後にcheckKeywords()が実行されます。
@callable()
async scheduleCheck() {
return this.schedule(60, "checkKeywords");
}
async checkKeywords() {
console.log("確認するキーワード:", this.state.keywords);
this.setState({ ...this.state, lastCheckedAt: new Date().toISOString() });
}この例は予約の実行と状態の保存を示すものです。情報の収集や通知は checkKeywords() に組み込みます。予約時刻は秒数・日時・cron形式で指定できます。予約APIの仕様も参照してください。
npx wrangler typesでWorkersの型を生成し、npx wrangler devで起動します。クライアントの接続先は/agents/watch-agent/demoのような形式です。demoは1つの共有インスタンス名なので、利用者ごとに分ける場合は認証したユーザーIDと対応付け、他人のインスタンスへ接続できないようにします。公開時は認証・認可を設定します。
起動したら、別のターミナルからエージェントを呼んで試せます。次は、Node.jsのスクリプトから @callable() のメソッドを呼ぶ動作確認用の例です。
// try-agent.ts(動作確認用)
import { AgentClient } from "agents/client";
async function main() {
const client = new AgentClient({ agent: "watch-agent", name: "demo", host: "localhost:8787" });
console.log(await client.call("addKeyword", ["Cloudflare"])); // 追加後のキーワード一覧
await client.call("scheduleCheck"); // 60秒後に checkKeywords() を予約
client.close();
}
main();npx tsx try-agent.ts で実行すると、追加後のキーワード一覧が表示されます。もう一度実行して前回のキーワードが残っていれば、状態が保存されています。60秒ほど待つと、wrangler dev のターミナルに checkKeywords() のログが出ます。画面から使う場合は、Reactの useAgent() フックでも同じように呼び出せます。書き方はSDKのバージョンで変わることがあるため、Agentクラスの仕様もあわせて確認してください。
動作を確認できたら、公開します。
npx wrangler deploy更新日:2026-11-03
つまずきやすい点
設定ファイルの class_name は、コードのクラス名と完全に一致させます。違っていると、デプロイの時点か実行時にエラーになります。
routeAgentRequest が返したレスポンスは、加工せずにそのまま返してください。作り直すと、WebSocketの接続が切れてしまいます。
TypeScriptの設定で、experimentalDecorators を有効にしないことも大切です。これを有効にすると、@callable() が実行時に動かなくなります。
なお、LLMの利用料は別にかかります。エージェントが自動で動くほど、APIの呼び出しも増えます。費用の上限は、「LLM APIの請求額が読めない」の方法で設定してください。


次に出やすい悩み
状態を持ったエージェントが動いたら、次は、重い処理をどう分けるか、何を覚えさせるかを設計します。1回では終わらない大量の処理や、決まった時刻の処理を組み込みたいなら「定期実行や時間のかかる処理が必要になった」を、会話の続きや利用者の好みを覚えさせたいなら、Phase 4の「AIに会話の続きを覚えさせたい」を読んでみてください。checkKeywords() で実際にWebの情報を集めたい場合は、「Webの情報収集や画面操作を自動化したい」で方法を紹介しています。
「手元のPCで動いているエージェントを、そのまま常時動かせるか」の見極めからご相談いただけます。エージェントの開発と、動かす基盤の構築はCloudflareインフラ構築をご覧ください。
よくある質問
Q. 常時使えるエージェントには、起動し続けるサーバーが必要ですか?
この記事のように、指示や予約の到着を待ち、そのときだけ処理する構成なら、常駐サーバーを自分で管理する必要はありません。会話や作業の状態を保存し、イベントを受けて処理を再開します。CPUを使い続ける計算や、GPUが必要な処理は分けて設計します。
Q. 複数の利用者の会話や作業が混ざるのを防ぐには?
認証した利用者とエージェントのインスタンスをサーバー側で対応付けます。利用者が送ってきたインスタンス名をそのまま信用せず、その利用者に接続権限があるかも確認してください。状態の保存領域を分けることと、他人の保存領域へのアクセスを防ぐことの両方が必要です。
Q. Pythonで作ったエージェントを、そのまま移せますか?
使っているライブラリや実行環境の確認が必要です。本文はTypeScriptのAgents SDKを使う構成なので、Python前提のフレームワークやネイティブ拡張をそのまま使えるとは限りません。既存のPython環境を維持したい場合は、コンテナやVPSで動かす方法も比較します。
Q. 自動実行でLLMの利用料が増えないようにするには?
実行頻度、1回あたりの呼び出し数、再試行の回数に上限を設け、利用者や処理の種類ごとに利用量を記録します。エージェントを動かす基盤の費用と、LLM APIの料金は分けて管理してください。予約方法はAgentsの予約APIを、予算管理はLLM APIの費用対策を参照できます。
執筆者情報
IT顧問、AI・業務アプリ開発、SESを通じて、企業のIT活用を支援しています。代表の藤澤 昌隆は、2007年からシステム開発・運用に携わり、アクセンチュアでの業務ITコンサルティングを経て、2019年にパブロフ株式会社を設立しました。
AI活用の支援事例として、営業工程を支える7つの業務別AIアプリの構築を公開しています。アプリの開発に加え、入力・出力の形式、評価基準、テストケースの整備にも取り組んでいます。
記事の内容に関するご質問や誤りのご指摘は、お問い合わせ窓口へお寄せください。
