7月27日、Vinod Chuganiが「5 Architectural Patterns for Persistent Memory and State in AI Agents」と題した記事を公開した。AIエージェントにおける永続メモリと状態管理の5つのアーキテクチャパターンを解説した内容で、「会話履歴をコンテキストウィンドウに詰め込む」という初期のアプローチがなぜ破綻するのかを起点に、実務レベルの設計判断を掘り下げている。
LLMはステートレスな設計だ。すべての呼び出しはゼロから始まり、直前のやり取りを何も覚えていない。初期のエージェント開発者はこれを「会話履歴をまるごとコンテキストウィンドウに詰め込む」という方法で回避しようとしたが、これはすぐに破綻する。レイテンシが跳ね上がり、関連情報は埋もれ、同じ事実の古いバージョンと新しいバージョンが共存してもモデルは正しい方を拾えるとは限らない。
解決策はより大きなコンテキストウィンドウではない。メモリと状態を、後付けではなく設計上の意思決定として扱うことだ。
StateとMemoryは別物
記事はまず、混同されやすい2つの概念を整理する。
State(状態)はスナップショットだ。エージェントが今タスクについて知っていること——どのステップにいるか、直前のツール呼び出しは何を返したか——を表す。ホワイトボードのようなもので、セッションが終われば消える。
Memory(メモリ)は情報を境界を越えて運ぶ仕組みだ。次のターン、次のセッション、あるいは後から動く別のエージェントへと情報を引き継ぐ。
この2つは連動する。タスク開始時、エージェントはメモリから情報を読み込んでStateを構築する。タスク中はStateを更新し続け、完了時に重要な情報をメモリに書き戻す。メモリがStateに流れ込み、Stateがメモリにフィードバックされるサイクルだ。
壊れたStateはエージェントがタスク途中で迷子になることを意味し、壊れたMemoryはエージェントが学習も個別対応もできず毎回白紙から始まることを意味する。この2種類の障害は異なる対処が必要だ。
パターン1:インコンテキスト・ワーキングバッファ(最重要の基礎)
すべてのエージェントが必要とする基盤パターンである。
現在のセッションのメッセージ履歴やツール出力をスライディングウィンドウとして管理する。バッファがトークン上限に近づくと、古いターンをサマリーに圧縮して生のツール出力は捨てる。タスク完了時にはバッファをフラッシュし、保存すべき情報だけを長期ストアに書き出す。
ここで見落とされがちなトレードオフがある。会話途中でサマリーを作るとプロンプトのプレフィックスが書き換わり、KVキャッシュが無効化される。KVキャッシュとはTransformerがAttention計算の中間結果を再利用することで推論コストを削減する仕組みで、プレフィックスが変わると積み上げたキャッシュが使えなくなり、次の呼び出しでレイテンシスパイクが発生する。サマリーのタイミングは設計時に意識すべきポイントだ。
LangGraphのようなエージェントオーケストレーションフレームワークでは、このバッファ管理をノードとして組み込む実装パターンが一般的に使われている。
パターン2:実行チェックポイント(障害耐性)
長時間タスクは失敗する。タイムアウト、レート制限、人間の承認待ちなど様々な理由でセッションが中断される。チェックポイントはワークフロー状態をPostgreSQLやSQLiteのような永続ストアに保存し、中断した場所から再開できるようにする。
実務でよくハマる落とし穴がある。再開してもexactly-onceセマンティクスは保証されない。クラッシュ前にノードが途中まで実行していた場合(例:メール送信やDBへの書き込み)、再開時にその処理が再実行される可能性がある。副作用を持つノードは冪等性を持たせる必要がある。
人間が承認するステップが含まれるワークフローや、ネットワーク障害のリスクがある長時間タスクに必須のパターンだ。LangGraphはチェックポイント機構を組み込み機能として提供しており、PostgreSQLやSQLiteバックエンドを選択できる。
パターン3:セマンティックメモリ(セッション横断の知識)
エージェントが「知っていること」——事実、ユーザーの好み、ドメイン知識——をセッションをまたいで保持する。ベクトルストアとメタデータフィルタリングを組み合わせて、クエリ時に関連情報を検索しプロンプトに注入する。
設計上の重要な問題として古い事実の競合がある。3月に「Postgresを使っている」、7月に「Snowflakeに移行した」と両方が記録されていると、検索でどちらが引っかかるかわからない。再現性のある重み付け、更新ロジック、TTLによる解決が必要だ。
また記事はセキュリティ上の重大な注意を明記している。APIキーなどの認証情報をセマンティックメモリに保存してはいけない。プロンプトインジェクションや意図しない検索でモデルのレスポンスに露出するリスクがある。シークレットはシークレットマネージャーに置き、エージェントには値ではなくハンドルだけを渡す構成にすべきだ。
LangChainが提供するMemory APIはこのセマンティックメモリ層の抽象化として機能し、ベクトルストアの実装差異を吸収する設計になっている。
パターン4:エピソディックイベントログ(経験からの学習)
セマンティックメモリが「何を知っているか」を保存するのに対し、エピソディックメモリは「何をしたか」を保存する。Goal、Plan、Tool Calls、Outcomeという形式の時系列ログだ。
過去に同様のタスクで構文エラーによるDBクエリ失敗があれば、次回の実行前にその記録を参照して同じミスを繰り返さない。
注意点として、取得された失敗ログはモデルへのアドバイスであり制約ではない。モデルはそれを無視することもある。また一時的な環境障害を「戦略の失敗」として記録してしまうと、誤ったパターンを継続的に強化するリスクがある。ログ書き込み前に障害種別を分類する設計が望ましい。
パターン5:マルチスコープ分離(エンタープライズプライバシー)
複数ユーザーを扱うシステムでは、メモリを必ずサイロ化する必要がある。
すべてのメモリ書き込みにuser_id、session_id、org_idといったスコープタグを付与し、検索は認証トークンに基づいて厳密にフィルタリングする。アプリケーション層のクエリフィルタだけに頼るのは危険で、ストレージ層でのテナント名前空間分離やRow-Level Securityによる強制が望ましい。アプリ層のフィルタはWHERE句の書き忘れでオープンに失敗するが、ストレージ層の分離はクローズドに失敗する。
さらに困難な問題がある。ユーザーが削除権を行使した場合、生データだけでなく、そこから派生した埋め込みベクトル、サマリー、抽出済みファクトもすべて削除しなければならない。GDPRやCCPAへの対応を見据えるなら、削除パスの設計は初期アーキテクチャの段階から組み込む必要がある。
6ヶ月運用の現実
記事はこの5パターンがカバーしない問題にも言及している。6ヶ月の運用を経ると、セマンティックストアとエピソディックストアには重複エントリや古い情報が蓄積し、検索品質が低下する。TTL、統合バッチ処理、プルーニングポリシーはオプションの仕上げではなく、スケールでメモリを運用するための必須要件だ。
コンテキストウィンドウはデータベースではない。メモリを短期バッファ・エピソディックログ・セマンティックストアに分離して設計することで、はじめて「学習し、データ境界を守り、本番で持ちこたえるシステム」になる。
詳細は5 Architectural Patterns for Persistent Memory and State in AI Agentsを参照していただきたい。