私たちは最近、camelAI エージェントを仮想マシンから移行し終えました。このエージェントは現在、Cloudflare Durable Object 内で動作し、ファイルシステムは SQLite と R2 に保存され、bash ではなく JavaScript を記述するようになりました。多くのチームがコーディングエージェントをフル Linux VM またはコンテナサンドボックスで実行しており、私たちも以前はそうしていました。
VM から離れたかった理由は、すべてのユーザーにディスクが接続された常時稼働マシンを提供するにはコストがかかりすぎて、スケールできなかったからです。難しいのは、コーディングエージェントが Linux を前提としていることです。彼らは bash を使うように訓練されており、私たちが立ち上げたハーネスはフル VM を必要としたため、ここにたどり着くまでに 3 回の再設計が必要でした。トレードオフとして、エージェントは現在、私たちが明示的なメソッドを構築したことだけしか実行できなくなりました。これは制限があるように聞こえますが、製品にとっては良いことでした。
私は camelAI の CTO、Miguel です。私たちのコードベースは最近オープンソースになったので、この投稿の内容はすべて github.com/qaml-ai/camelAI で読むことができます。関連するファイルへのリンクも随時貼っていきます。それでは、進化の過程を紹介します。
ステップ 0: VM 時代
私たちは Claude Code ハーネス上で立ち上げました。これは実行にフル仮想マシンを必要とします。いくつかの VM プロバイダーを試しましたが、永続性とパフォーマンスの要件に合うものはなく、最終的に独自のコンテナサービスを構築しました。その投稿はまだ公開されていますが、私たちはもうそのインフラを一切運用していません。
コンテナサービスは機能しましたが、重かったです。ユーザーごとに常時稼働の VM を用意するのは高価であり、各ユーザーのファイルを高速な接続ディスクに保持するのも同様に高価でした。スケールするということは、実際のマシンと実際のディスクをスケールすることを意味し、私たちが目指すユーザー数では法外なコストになる見込みでした。そこで、VM オーケストレーションを巧妙にする代わりに、VM をまったく必要としない方向で設計を始めました。
ステップ 1: エージェントを VM から取り出す
Claude Code ハーネスは VM と切り離せないため、最初の一手は独自のハーネスを構築することでした。Mario Zechner のオープンソースコーディングエージェント pi をベースに構築しました。pi はライブラリのスタックです。最上位のレイヤーは通常のオペレーティングシステムを前提としていますが、下位レイヤーはエージェントループや状態管理などのエージェントプリミティブを、実行場所を気にせずに提供します。私たちは pi のコードを一切変更しませんでした。それらの下位レイヤーをインポートし、その上に独自のハーネスを構築し、Linux 環境ではなく Cloudflare Durable Object 内で実行しています。
Durable Object は、Cloudflare のエッジ上で、作成したユーザーの近くで起動する小さなステートフルなコンピュートインスタンスです。各チャットスレッドは独自の Durable Object を持ち、すべてを集中型の VM ホスト経由でルーティングする場合と比較して、レイテンシーがそれだけで低下しました。
この段階では VM は維持しましたが、エージェントはもはや VM 内に存在しませんでした。コマンドを実行する必要があるときは、リモートで VM を呼び出しました。Anthropic は管理エージェントに対して同じ分割、つまり脳を手から分離することを説明しています。これにより、いくつかの良い特性が得られました。
- エージェントは VM が起動する前に応答を開始します。マシンの起動を待たないからです。
- エージェントが作業を続けている間、VM はスリープ状態に戻ることができ、ターンにコマンドが必要ない場合はまったく起動しないこともあります。
- 1 つの脳が複数の手を制御できます。単一のエージェントが複数の VM を同時に操作できます。
私たちはこれらの手をプロジェクトと呼んでいます。各プロジェクトには、コマンド実行用の VM と、Cloudflare Artifacts を介してプログラム的に作成された git リポジトリがありました。Artifacts は Worker からオンデマンドでプロビジョニングできる git 互換のストレージです。エージェントは自分が VM の外で実行されていることを実際には認識していませんでした。それでも bash を持ち、他のコーディングエージェントと同じように動作しました。
問題は、これでレイテンシーが改善されただけで、それ以外は何も変わらなかったことです。依然としてユーザーごとに VM があったため、元の設計と同じコストとスケーリングの問題が残っていました。
ステップ 2: VM を削除する
次のバージョンでは、同じプロジェクト構造を維持しつつ、背後にある VM を削除しました。各プロジェクトは現在、Durable Object 内に存在するファイルシステムによってバックアップされ、大きなファイルには R2 が使用されています。
これは私たちが発明したものではありません。Cloudflare のエージェントチームは Shell を構築しました。これは Workers 向けの実験的なファイルシステムと実行ランタイムであり、私たちはそのコードを大幅に再利用しました。仕組みはシンプルです。Durable Object のストレージは 10 GB の制限がある SQLite データベースで、各行には最大サイズがあります。小さなファイルは SQLite の行に直接保存されます。およそ 1.5 MB を超えるファイルは R2 に書き込まれ、SQLite の行にはポインタだけが保持されます。エージェントからは通常のファイルシステムのように見えますが、内部ではデータベースとオブジェクトストレージであり、永続性は維持しなければならないインフラではなく、保存されたデータです。
バージョン履歴は引き続き Artifacts を通じて管理されるため、git サーバーをホストすることなく、すべてのプロジェクトが git 履歴を保持します。
ステップ 3: bash を削除する
bash を削除するのは、思い切った決断に感じられました。コーディングエージェントは bash を使うように訓練されており、bash こそが誰もが VM でエージェントを実行する理由です。また、コスト以外にも問題がありました。bash とネットワークアクセスを持つエージェントは、何か有用なことをするために認証情報を必要とし、認証付きプロキシ URL の試みはハッキーで、強制が困難になりつつありました。
そこで、私たちは bash を削除しました。bash の代わりに、エージェントは JavaScript を記述し、Code Mode と Cloudflare の動的 Worker ローダー を通じて実行されます。各実行は、ミリ秒単位で起動し、数メガバイトのメモリを使用する新しい V8 アイソレートで行われます。サンドボックスには、ユーザーのデータ接続と、プラットフォームが実行できるすべてのメソッドがプリロードされています。認証情報はサンドボックスに入りません。エージェントは接続のメソッドを呼び出し、認証は私たちの側で行われます。
エージェントが実際に bash を何に使っているかを見ると、それを失うことによる損失は予想よりも小さいです。ほとんどの用途はファイル操作であり、エージェントにはネイティブツールが用意されています。読み取り、書き込み、編集に加えて、独自のgrep と glob の実装を提供しています。これで 80-20 をカバーできます。残りは特定のジョブのための特定のコマンドであり、それらは明示的なメソッドになりました。
- wrangler deploy をプロキシ経由で行う代わりに、私たちが完全に制御する deploy_project メソッドになりました。デプロイがいつ発生するかを正確に把握できるため、それをフックして自動的にライブプレビューを開くことができます。以前は、プロキシされた wrangler トラフィックをスニッフィングして、どのスレッドがデプロイしたかを推測する必要がありました。
- ユーザーのアプリケーションのビルドと Python ノートブックの実行は、それぞれ独自のメソッドになり、どちらも短命なコンテナによってバックアップされています。
これら 2 つのジョブにはコンテナを維持しました。なぜなら、それらは本当に Linux を必要とするからです。ユーザーアプリは Vite、Tailwind、React Router で構築されており、依存関係を追加するには bun install を実行する必要があります。ビルドを Worker 内で実行することを検討しました。ビルド対象自体が Worker であるためですが、そのパスは十分にサポートされておらず、Worker には 128 MB のメモリ制限とごく一部の CPU しかありません。ビルドは遅くなり、多くのプロジェクトがメモリ制限を超えるでしょう。そのため、代わりにビルドは Cloudflare Sandbox SDK を通じてコンテナを起動し、プロジェクトをコピーし、ジョブを実行し、結果を返し、コンテナをシャットダウンします。ノートブックの実行も同様です。フル Linux は引き続き使用しますが、実際に必要な数秒間だけです。
正直なデメリットは、エージェントが必要とするものを事前に予測しなければならないことです。bash があれば、エージェントは自分で解決できました。今では、機能が不足している場合、私たちがそれを追加する必要があります。実際には、このプレッシャーは製品にとって良いものでした。なぜなら、ユーザーが何をしているかを考え、エージェントに即興で対応させるのではなく、ファーストクラスのパスを構築することを強いられるからです。
予想外の利点もありました。bash は自由度が高く、安価なモデルは自由度の高い環境では苦戦します。明示的なメソッドのセットが小さいと、それらのモデルのパフォーマンスが顕著に向上します。これは、camelAI の運用コストを低く抑えることがこのアーキテクチャの目的であるため、重要です。
現状
スタックは現在、エージェントとそのファイルシステムに Durable Object、大規模ファイルに R2、git 履歴に Artifacts、ハーネスに pi、実行に Code Mode と動的 Worker となっています。これは他の Cloudflare アプリと同様にデプロイでき、管理する外部コンテナサービスはありません。
動的 Worker は、稼働時間ではなく実行ごとに課金されます。数千回の実行にかかるコストは、私たちが以前評価していたサービスのコンテナ時間数分のコストとほぼ同じです。すべてがユーザーの近くのエッジで実行されるためレイテンシーは低く、スケーリングは Cloudflare の問題であり、私たちの問題ではありません。
ユーザーは引き続きフルスタックアプリをビルドしてライブ URL にデプロイでき、エージェントは引き続き読み取り、書き込み、grep、デプロイを実行します。ユーザー側から見れば、何も変わっていません。
TL;DR
私たちは、独自に構築した VM サービス上の Claude Code ハーネスから始めましたが、それは高価でスケールが困難でした。まず、エージェント自体を Cloudflare Durable Object に移行し、リモートで VM を制御させることで、レイテンシーは改善されましたが、コストは改善されませんでした。次に、VM を完全に置き換え、Cloudflare の Shell プロジェクトをベースに、Durable Object の SQLite と R2 に保存されるファイルシステムを導入し、git 履歴は Cloudflare Artifacts を通じて管理しました。最後に、bash を削除し、エージェントに Code Mode と動的 Worker を介した JavaScript サンドボックスを提供し、デプロイ、ビルド、ノートブック用の明示的なメソッドを用意しました。結果として、コストは桁違いに低くなり、レイテンシーは低減し、運用はシンプルになり、小規模なモデルでも駆動しやすくなりました。すべては github.com/qaml-ai/camelAI でオープンソースとして公開されています。





