発表翌日のCloudflare OSを無料枠のWorkers AIで動かした

CloudflareがCloudflare OSをオープンソースで公開した。発表は2026年8月5日。翌日の夜に手元で動かして、内蔵のAIエージェントに○×ゲームを1本作らせるところまで通した。推論コストは合計$0.06。ただし素直には通らなかった。罠が2つ。
何が公開されたのか
社内で全社員が使っていたAI作業環境のオープンソース化。初版は2026年5月に社内展開されていて、今回公開されたのはその学びを反映して作り直したv2。READMEに「early access」「rough edges」と自分で書いてある。ライセンスはApache-2.0。リポジトリは公開2日でスター4,600超。
中身は3層。チャットでエージェントに仕事を頼むワークスペース、Gadget(ガジェット)と呼ぶ小さな自分専用アプリ、そしてGatekeeper(ゲートキーパー)というcapability型の安全層。面白いのはGadgetの思想で、スライドを1枚作るとSaaSを呼ぶのではなく「自分専用のスライドソフトのインスタンス」が生える。全ユーザが自分のコピーを持つので、エージェントに機能を足させても他人に影響しない。READMEはこれを「直近25年のクラウドアーキテクチャと『SaaS』からの大きな決別」と書く。
実装はCloudflareスタックで貫かれている。ワークスペース1つがDurable Object、GadgetはDynamic WorkerのFacetで動き、クライアントとサーバはCap’n Web RPCで喋る。LLM接続はサードパーティOSSのPi(Earendil Worksのpi-agent-core)に一本化。READMEには、Dynamic WorkersやFacetsを含むランタイム新機能がこの製品のために追加されたと明言されていて、Workersチームが考える「正しいWorkersの使い方」の教材でもある。
pnpm run-local で立つ
cloneして pnpm run-local。wranglerとworkerdでフルスタックがローカルに立ち、http://localhost:8787 でサインアップ画面が出る。Cloudflareアカウント不要、と言いたいところだが、AIモデルを繋ぐ段になるとそうはいかない。
モデルはAnthropic・OpenAI・Google・Workers AI・Ollamaの5系で、個人のAPIトークンを貼る方式。今回はせっかくなので全部Cloudflareで揃えたくてWorkers AIを選んだ。ここで1つ目の分かれ道。Workers AIには本来Workersバインディングという接続方法があるが、バックエンドのソースコメントに「Workers AI is fetch-only (no Workers-binding transport)」とあり、ユーザが追加するモデルはREST経由のみ。つまりAccount IDを控え、Workers AI権限のAPIトークンをダッシュボードで作って貼ることになる。
罠1: 推奨モデルは無料プランで動かない
モデル追加UIのsuggestedはKimi K2.7 CodeとGLM 5.2の2つ。Kimiを選んでトークンを貼り、チャットを送ると403が返る。ボディなし。UIからは原因が見えない。
トークンをREST APIに直接当てたら本当の理由が出た。
AiError: Model @cf/moonshotai/kimi-k2.7-code is not available on the Workers Free plan 〔…〕 Upgrade to access this model 〔…〕
有料プランのつもりでいたが調べたら自分のアカウントは無料プランだった…。そしてsuggestedの2つは両方Workers Paid($5/月)専用。モデルのdocsページには注記があるが、このときは知らず、どのモデルが無料で通るかはAPIのメタデータにも出ないので1リクエストずつ実弾で確認した。
| モデル | 無料プラン |
|---|---|
| @cf/openai/gpt-oss-120b | 通る |
| @cf/meta/llama-3.3-70b-instruct-fp8-fast | 通る |
| @cf/zai-org/glm-4.7-flash | 通る |
| @cf/qwen/qwen2.5-coder-32b-instruct | 通る |
| @cf/moonshotai/kimi-k2.7-code | Paid専用 |
| @cf/zai-org/glm-5.2 | Paid専用 |
無料プランで使うにはUIの「Other Cloudflare Workers AI…」からモデルIDを手入力する。コーディングエージェント用途の現実解はgpt-oss-120b。$0.35/$0.75 per Mトークンと安い。
罠2: Gadgetビルドが400で死ぬ
gpt-oss-120bに差し替えると雑談は普通に通る。が、○×ゲームを頼むとエージェントがGadgetを作った直後に400。またボディなし。
単発の再現では落ちない。ストリーミングもtools付きもcontent配列の単発も通る。仕方ないのでモデル呼び出し層にデバッグ用のfetchを挟んで、失敗した実リクエストとレスポンスを覗いた。出てきたのがこれ。
AiError: Bad input: Error: oneOf at ’/’ not met, 0 matches: 〔…〕 Type mismatch of ‘/messages/0/content’, ‘array’ not in ‘string’, 〔…〕 Type mismatch of ‘/messages/4/content’, ‘string’ not in ‘null’, 〔…〕
原因はマルチターン。エージェントのツール往復が始まると、リクエストにOpenAI標準のcontent-parts配列とtool_calls時の content: null が混ざる。Workers AIの互換エンドポイント(/ai/v1/chat/completions)のモデル入力スキーマがこの2つを飲めない。単発チャットが通ってエージェントで死ぬのはこのため。Pi側に「contentを文字列に畳む」互換フラグは無い。
なので正規化のシム(shim)を自前で挟んだ。要旨は15行。
fetch: async (url, init) => {
if (String(url).includes("/ai/v1/chat/completions") && typeof init?.body === "string") {
const body = JSON.parse(init.body);
for (const m of body.messages ?? []) {
if (Array.isArray(m.content)) {
m.content = m.content.map(p => typeof p === "string" ? p : p?.text ?? "").join("\n");
} else if (m.content == null) {
m.content = "";
}
}
init = { ...init, body: JSON.stringify(body) };
}
return fetch(url, init);
}
これで完走。エージェント実行52秒でGadgetが生成された。
動いた後は素直に面白い
エージェントの書いたコードはPending changesとしてドラフト扱いになり、まとめて承認できる。READMEはさらに先を行っていて、承認待ちのアクションを「シミュレートした結果」でエージェントに返して先へ進ませ、人間は後で一括承認する設計だと言う。同期承認で毎回止まるからみんな --dangerously-skip-permissions に流れる、という現実への構造的な回答。そこまでの深追いは今回していない。
初回のGadgetはサーバ側のTypeErrorで白画面だった。ここで「Send 2 captured errors to chat」というボタンが出る。実行時エラーをワンクリックでエージェントに突き返す導線が製品に組み込まれている。1往復で直って、○×ゲームが動いた。盤面をクリックするとDurable Objectの状態が進んで手番が回る。
コストの帰属も最初から画面にある。ワークスペース右上に累計$0.06、チャット下部に12,267 tokens / $0.0551。誰がどのワークスペースでいくら焼いたかが見える。あとgpt-ossは思考過程がそのまま画面に流れる。「User asks: … Provide answer.」が回答の上に出るのは、好みが分かれると思う。

罠は2つともWorkers AI側
振り返ると、詰まった箇所はどちらもCloudflare OS本体ではなくWorkers AI側の都合。推奨モデルのプラン制限はUIに出ないし、互換エンドポイントはOpenAI標準のマルチターンを飲めない。自社のエージェント製品と自社の推論基盤が噛み合っていないのは、発表翌日らしいと言えばらしい。シムは15行なので、Pi側に互換フラグを足すかWorkers AI側がスキーマを広げるか、どちらかに落ちるはず。issueは書くつもり。
$5払ってKimi K2.7 Code(従量は$0.95/$4.00 per Mトークン)で同じコースを走らせるかどうかは、また今度。
追記(2026-08-07)
公開後に追試した。エージェントが送る満形式(content配列・content: null・tool role)を無料通過4モデルに直接投げると、gpt-oss-120bとllama-3.3-70bとqwen2.5-coderは400で、glm-4.7-flashは通る。スキーマはモデル別で、無料プランでもglm-4.7-flashを選んでいればシムなしで動いた。逆に言えば踏むかどうかはモデル選択の籤引き。suggestedの2つがPaid専用なのは、マルチターン形式を飲めるモデルだけをキュレートした結果と考えると辻褄が合う(GLM 5.2と同族のglm-4.7-flashが通ることは実測、本体は$5の向こう側なので未確認)。
予告したissueは出した: cloudflare/cloudflare-os#54