powered by TechFeed
表示モード
Deep Dive

AIエージェントが途中で止まったとき「会話の記憶」と「作業環境の復元」は別物 — PulumiがKopiaでワークスペーススナップショットを実現した理由

9月26日、Pulumiが「AI agents need continuity, not just context」と題した記事を公開した。この記事では、AIエージェントが長時間タスクを実行する際に「コンテキストの復元」だけでなく「ワークスペースそのものの継続性」が必要であるという問題と、PulumiがオープンソースのバックアップツールKopiaを用いてどのように解決したかについて詳しく紹介されている。以下に、その内容を紹介する。

9月26日、Pulumiが「AI agents need continuity, not just context」と題した記事を公開した。この記事では、AIエージェントが長時間タスクを実行する際に「コンテキストの復元」だけでなく「ワークスペースそのものの継続性」が必要であるという問題と、PulumiがオープンソースのバックアップツールKopiaを用いてどのように解決したかについて詳しく紹介されている。以下に、その内容を紹介する。


「会話の再生」だけでは足りない

AIエージェントが長時間タスクを担う場面が増えるにつれ、見落とされがちな問題が浮上している。それはランタイムが再起動したとき、エージェントは何を「覚えている」べきか、という問いだ。

PulumiのAIエージェント「Neo」は、リポジトリのクローン、ファイル編集、依存関係のインストール、プレビューの実行、PRの作成といった一連のインフラ作業をこなす。こうしたタスクの途中でランタイムが再起動した場合、会話ログをリプレイすれば「エージェントが何を言ったか、どのツールを呼んだか」は復元できる。しかし、生成されたファイル、依存パッケージの状態、ローカルコミット、中間出力物は復元できない。

Pulumiはこの問題を次の一文に集約している。

Replay events to restore what Neo remembers. Restore a snapshot to recover what Neo changed.

(イベントリプレイはエージェントの記憶を復元する。スナップショットリストアはエージェントが変更した内容を復元する。)

会話メモリとワークスペース状態は、別々の継続性レイヤーとして扱わなければならない。


Gitベースの復元が「間違った抽象」だった理由

当初PulumiはGitを使ってワークスペースを復元していた。ホスト型ランタイムの作業ディレクトリにあるGitリポジトリをスキャンし、リモート・ブランチ・コミット・ローカルdiffを記録。コールドスタート時にそれらを使って再構築する、というアプローチだ。

しかしこのアプローチは実際にいくつかの問題を引き起こした。

  • タスク開始後にリポジトリが削除されると、クローンに失敗してタスクが再開できない
  • 大きなdiffがタスクステートのペイロード上限を超える
  • 依存キャッシュやビルド出力など、意図的にGitから除外したディレクトリに、エージェントが直前に生成した重要なコンテキストが含まれている場合がある

エージェントのワークスペースはGitリポジトリだけで構成されているわけではない。スクラッチファイル、パッケージマネージャの状態、ツール出力など、Gitの管理外にあるものが復元に不可欠な場合がある。


KopiaをコアPrimitiveに選んだ理由

Pulumiが検討した選択肢と却下した理由は以下のとおりだ。

  • EFS + AWS Backup: AWSに依存するため、セルフホスト環境への展開に使えない
  • Gitサーバー: Gitリポジトリでないファイルを扱えない。別のサービスを運用する負担も生じる
  • 直接オブジェクトストレージ同期: ポイントインタイムリストア、リネーム操作の効率、部分的な書き込み失敗後のリカバリモデルが欠如
  • ブロックボリュームスナップショット: リカバリのセマンティクスは強力だが、タスクスコープのワークスペースにはコストモデルが合わない

これらを除外した結果、「汎用オブジェクトストレージ上の暗号化タスクスコープスナップショット」という形に行き着いた。Kopiaは以下をすべて満たす。

  • インクリメンタルスナップショット
  • ポイントインタイムリストア
  • コンテンツアドレス型ストレージ(変更のないコンテンツは再アップロードしない)
  • 暗号化
  • S3等のオブジェクトストレージバックエンド対応
  • リテンションポリシーと保守的なクリーンアップ

ストレージレイアウトはタスクIDスコープで設計されている。

s3://<task-backup-bucket>/
  tasks/
    <task-id>/
      kopia/
        ...encrypted repository data...

タスクをまたいだ重複排除は捨てているが、そのぶんセキュリティとライフサイクルの境界がストレージ境界と一致する。あるタスクのランタイムが別のタスクのスナップショット履歴を読めないよう、タスクごとに異なるリポジトリパスワードを使い、ストレージ認証とリポジトリ復号は別個のチェックになっている。


コントロールプレーンとデータプレーンの分離

スナップショットの書き込みと読み出しはランタイムが直接オブジェクトストレージに対して行う。サービス(コントロールプレーン)がやることは、タスクの認証と、そのタスクのストレージプレフィックスにスコープされた短命な一時クレデンシャルの発行だけだ。

この分離により、サービスが大量のファイル転送のプロキシになることを防ぎつつ、マルチテナントのセキュリティ境界を維持している。ランタイムのクレデンシャルはスナップショットの作成はできるが、履歴の削除はできない。削除権限は信頼されたクリーンアップパスだけが持つ。


「エスケープハッチ」を先に展開する段階的ロールアウト

新しい永続化レイヤーを一気に本番投入するのではなく、3段階のモードを設けた。

モード 旧Git復元 スナップショット書き込み スナップショットリストア
disabled 権威的 なし なし
shadow 権威的 あり なし
enabled フォールバック あり あり

shadowモードがポイントで、スナップショットを書きながらレイテンシやストレージ挙動を観測し、タスク復元には影響を与えずに検証できた。enabledモードに移行後も旧Gitステートの書き込みを続けることで、実質的なロールバックパスを確保した。

リストアの失敗時もフォールバックが働く。Kopiaスナップショットの復元に失敗すれば旧リポジトリ再構築パスへ降りる。チェックポイント書き込みはベストエフォートで、失敗してもそのターンの応答は完了扱いになる。


スナップショットは「仕事」だけ取る

ワークスペース全体を保存するとコストが膨らむ。パッケージマネージャのキャッシュやビルドディレクトリのように再生成できるものは除外し、/workspace、/tmp-workspace、/var-workspaceといったルートを対象にする。ソース変更、生成済み設定ファイル、ローカルコミット、ツール出力が保存の対象だ。

コスト面では、Kopiaのスナップショットリポジトリが直接オブジェクトストレージ同期よりも大幅に安くなるとベンチマークで確認されている。理由はリクエスト数にある。リネーム・小ファイルが多いワークロードでは、同期ベースのアプローチは大量のwrite/list/delete操作を生む。スナップショットリポジトリはメタデータ管理コストを持つが、繰り返されるワークスペースの変動に対して挙動がずっと安定する。


AIエージェントが現実のエンジニアリングタスクを担うようになると、「会話の継続性」と「作業環境の継続性」を分けて設計する必要性は今後さらに高まるだろう。Pulumiの実装はその具体的な答えの一例を示している。

詳細はAI agents need continuity, not just contextを参照していただきたい。