
現在のエージェントフレームワークのほとんどはデスクトップを前提としています。ユーザー 1 人、マシン 1 台、プロセス 1 つ。ラップトップが開いている間だけエージェントが動作し、ローカルファイルシステムに書き込み、API キーを環境変数に保持し、ターミナルを閉じると終了します。何か問題が発生した場合はユーザーが再試行します。エージェントにパッケージが必要になると、pip install でユーザーの Python 環境にインストールされます。状態、シークレット、ライフサイクルはすべて、1 つの信頼された境界内に収まっています。
クラウドエージェントインフラには、そうした贅沢は一切ありません。
エージェントは、まっさらな状態から起動するサンドボックス上で動作し、見知らぬユーザーとハードウェアを共有し、ユーザーが直接会うことのない呼び出し元(スケジュール、HTTP リクエスト、別のエージェント)によってトリガーされます。実行が行われるとき、ユーザーは通常寝ています。サンドボックス内のコードは敵対的である可能性があります。ファイルシステムはデプロイメントを乗り越えなければなりません。認証情報はエージェントが存在する場所に置くことができません。デスクトップが無料で提供する永続性、同一性、ネットワーク信頼、再試行といった保証はすべて、明示的なシステムとして再構築する必要があります。
私たちは CREAO で、このレイヤーを強化するためにここ数ヶ月を費やしました。そこから得られた教訓は 2 つです。デスクトップエージェントをリリースしたことがあり、それがクラウドに移行すると何が変わるのか疑問に思ったことがあるなら、これがその変化です。
教訓 1: ゆっくり変化するものと、速く変化するものを分離する

デスクトップでは、ユーザーの環境とエージェントのランタイムは同じものであり、同じ人によって同じ周期で更新されます。クラウドではそうではありません。
エージェントアプリはプラットフォーム側に状態を蓄積します。株式アナリストが matplotlib をインストールし、市場データをダウンロードし、チャート用のスクリプトを作成します。その環境がエージェントの筋肉の記憶です。ユーザーがその環境に満足した瞬間に、それをサンドボックススナップショットとして凍結し、ユーザーが環境を再度編集するまでそのスナップショットを凍結したまま保持します。すべての実行は同じイメージから起動します。同じパッケージ、同じファイル、同じバージョン。月曜日の実行は金曜日と同じように動作します。なぜなら、その下にあるものは何も動いていないからです。
これこそが、デスクトップフレームワークが無料で提供できない性質です。6 ヶ月前の pip install は、今日では異なるバージョンに解決されます。クラウドスナップショットは永遠に同じバイト列に解決されます。再現性はプラットフォームがユーザーに負う責任であり、凍結されたスナップショットはそれを提供する最も安価な方法です。
そして、結合問題が現れます。
ユーザーの環境を凍結する同じイメージには、ランナーコード(私たちが開発した、各実行でエージェントを管理する小さなハーネスライブラリ)も含まれています。ユーザーは自分の環境を静的に保ちたいと考えています。私たちはランナーを 1 日に何度もリリースしたいと考えています。1 つのアーティファクトに、2 つの相反する要件があります。
最初の修正は荒っぽいものでした。起動時に、スナップショット内のランナーが、デプロイしたばかりのバージョンと一致するかどうかを確認します。一致しない場合は、スナップショットを破棄し、クリーンなテンプレートから起動します。これで機能し、誰も文句を言いませんでした。被害はデプロイ後の最初の実行に限定されていました。
無人実行によってその防御は崩れました。月曜日の午前 9 時の cron ジョブは、午前 8 時 55 分にデプロイしたからといって、環境を失うべきではありません。私たちが暗黙のうちに破っていた契約、「環境は変更するまで凍結される」という契約です。
修正には思った以上に時間がかかりました。ユーザーの環境とランナーコードは、まったく異なる速度で変化します。ユーザーは好きな時にエージェントを編集します。私たちはプラットフォームを 1 日に何度もデプロイします。それらを 1 つのアーティファクトとして扱うと、デプロイのたびに選択を強いられます。古いランナーコードを維持するか、ユーザーが明示的に保存するよう求めた凍結環境を破壊するかの選択です。
私たちがたどり着いたモデルは、オペレーティングシステムが更新を処理する方法から借用しています。カーネルは変わります。ホームディレクトリは変わりません。セキュリティパッチを適用するためにディスクをワイプしたりはしません。
私たちも同じ境界を引きました。サンドボックスはユーザーの凍結スナップショットから起動し、手を加えません。その後、ランナーのみをホットスワップします。手順は次のとおりです。
- 新しいランナーをサンドボックス内の一時ディレクトリにステージングする。
- node --check でバリデーションし、実際のファイルに触れる前に構文エラーを確実にキャッチする。
- 原子的にスワップする。古いランナーの不変フラグを解除し、新しいものをコピーし、chattr +i で再ロックし、chattr バイナリ自体を隠してサンドボックスコードがロックを解除できないようにする。
- V8 のコンパイルキャッシュ(/home/user/.cache/v8-compile-cache/*)を消去し、新しいファイルが古いバイトコードではなく実際に読み込まれるようにする。
- いずれかのステップが失敗した場合、サンドボックスを強制終了し、新しいもので再試行する。中途半端にアップグレードされた状態がエージェントを実行することは決してない。
スワップ全体で約 300 ミリ秒かかります。ランナーコードがスワップされた場合のみ、成功した実行後に再スナップショットを作成し、更新されたコードをユーザーのイメージに焼き付けて、次回の実行でスワップを完全にスキップできるようにします。プラットフォームのデプロイメントはユーザーの状態を破棄するのではなく、新しいランナーをそこに折り込みます。ユーザーのパッケージ、ファイル、カスタマイズは変更されずに引き継がれます。
この教訓から学ぶことが 1 つあるとすれば、それは診断的な質問です。クラウドプラットフォームで永続化するものすべてについて、次のように自問してください。このアーティファクトの変更周期を誰が制御しているのか?ユーザーとプラットフォームの両方が所有している場合、遅かれ早かれ結合の代償を払うことになります。所有権の境界に沿ってアーティファクトを分割し、各側が自分の時計で更新できるようにしましょう。
教訓 2: シークレットを実行境界の外に置く

この教訓こそが、クラウドエージェントインフラを他のすべてから区別するものです。
デスクトップエージェントはユーザーとして実行されます。ユーザーのキーを、ユーザーのマシン上で、ユーザーのネットワークに対して使用します。クラウドエージェントは誰でもないものとして、共有ハードウェア上で、オープンインターネットに対して、敵対的である可能性のあるプロンプトから LLM が書いたコードを実行します。セキュリティモデルは、サンドボックス内のコードがすでに侵害されていると想定し、そうでないことを期待してはなりません。
私たちが守っているルールはシンプルです。長期有効な認証情報は決してサンドボックス内に置かれません。
エージェントが認証済みサービス(Slack、GitHub、ユーザー自身の API など)を呼び出す必要がある場合、トークンを保持しません。サンドボックス外で動作する API ブリッジにローカル HTTP リクエストを送信します。ブリッジはホスト側で OAuth トークンを添付し、呼び出しを転送します。トークンがサンドボックスのメモリや環境に入ることなく、レスポンスが返ってきます。
興味深いのは、ブリッジがサンドボックスにリクエストを許可する権限があることをどのように知るかという点です。2 つのチェックが、意図的にレイヤー化されています。
まず、IP 許可リスト。ブリッジは、サンドボックスホストが存在する内部ネットワーク範囲からの接続のみを受け入れます。それ以外の場所(開発者のラップトップ、漏洩した URL、パブリックインターネット)からの呼び出しは、アプリケーションコードが実行される前にネットワーク層でドロップされます。これにより、ブリッジは 1 つの物理インフラストラクチャに固定され、外部の誰にとっても役に立たなくなります。
次に、実行ごとに発行される短期間有効な JWT。サンドボックスが起動すると、プラットフォームはその特定の実行にスコープされたトークン(どのユーザー、どのアプリ、どのセッションか、実行期間をカバーする有効期限付き)に署名します。サンドボックスはすべてのブリッジ呼び出しでこれを提示します。ブリッジは署名を検証し、有効期限を確認し、その後に初めてユーザーの保存された認証情報を解決し、サーバー側で添付します。サンドボックスが乗っ取られた場合、攻撃者は実行とともに期限切れになり、その 1 つのセッションにスコープされた呼び出しのみを許可するトークンを取得します。盗むべきマスター認証情報はありません。
同じブリッジが課金控除、ログ、メトリクスを外部に運び出すため、サンドボックス境界を双方向に横断する唯一のインターフェースとなります。サンドボックス内の他のすべては、デフォルトで侵害されているものとして扱われます。
プロンプトインジェクションによってエージェントが process.env を Webhook にダンプするように仕向けられたとしても、攻撃者が得るのは、当社のネットワーク内からしか機能せず、実行とともに期限切れになる短期間有効な JWT だけです。この性質こそが、信頼できないユーザーコードを共有インフラ上で実行しても安心できる理由です。
根底にあるパターン
信頼性が高く安全なクラウドエージェントインフラは、新しいシステムではありません。それは、例外なく守られたいくつかの性質です。
- 状態はサンドボックス内に存在し、ユーザーが変更するまで凍結される。
- コードはホットスワップ可能で、状態から独立している。
- 認証情報はホスト側に存在し、エージェント内には決して置かない。
- 1 つの実行パイプラインが、人間、スケジューラ、他のソフトウェアのいずれのトリガーであっても、すべての呼び出し元にサービスを提供する。
最後の性質が、設計全体の要点です。1 つの executeAgent 関数が、UI クリック、スケジュール実行、API 呼び出しを処理します。課金システム、クレジット控除ログ、可観測性シグナルはすべて、人間が Run をクリックしたか、cron が起動したか、スクリプトが API を呼び出したかにかかわらず同一です。新しいトリガーサーフェスを追加することは、アーキテクチャの変更ではなく、ルーティングの変更です。エージェント自身は誰がトリガーを引いたかを知らず、気にもしません。
これこそが、デスクトップフレームワークが提供できず、クラウド版を構築する価値がある理由です。ラップトップ上のエージェントはラップトップに縛られています。クラウド上のエージェントは、スタックの他の部分が呼び出せる関数です。ユーザーは一度記述します。プラットフォームは、それをデプロイメントに耐えさせ、共有ハードウェア上で安全に実行させ、ユーザーが予期しなかった呼び出し元を受け入れられるようにします。
エージェントは、自然言語インターフェースを持つ関数です。その実装はユーザーに属します。そのトリガーサーフェス、ランタイム、セキュリティ境界はプラットフォームに属します。規律とは、各レイヤーが独自の時計で進化できるように構築し、他の人が発見する前にシステム間の隙間を見つけることに時間を費やすことです。
それが、次のサーフェスを安価に、そして安全にリリースするための方法です。





