9月2日、Bala Priya Cが「AI Agent Memory Design: What Works and What Doesn't」と題した記事を公開した。この記事では、AIエージェントの信頼性の高いメモリシステム設計において何が機能し何が失敗するかについて詳しく紹介されている。以下に、その内容を紹介する。
AIエージェントが複数セッションをまたいで動作するようになると、「コンテキストウィンドウ内に全情報を収める」という単純な設計は成り立たなくなる。外部ストレージへの永続化が必要になるが、ここでの設計ミスは「その場で気づきにくく、後になって繰り返し表面化する」類の障害を生む。本記事はその具体的なパターンを整理した実践的な内容だ。
メモリとは何か:混同されがちな概念の整理
まず前提として、記事は「エージェントメモリ」を次のように定義する。
エージェントメモリとは、エージェントが実行時に外部ストレージへ書き込み、後続の呼び出しで取得する情報である。システムプロンプト、会話履歴、静的なナレッジベースはメモリではなく、設定・コンテキスト・固定検索ソースだ。
メモリは機能別に4層に分類される。
| メモリ種別 | 保持する内容 | 主な取得方法 |
|---|---|---|
| エピソード記憶 | 過去のやり取り・決定事項 | セマンティック類似検索 |
| セマンティック記憶 | 事実・嗜好・ドメイン知識 | セマンティック検索またはキー直引き |
| 手続き記憶 | 成功したアクションパターン | パターンマッチ・直接取得 |
| ワーキングメモリ | タスクの中間状態・スクラッチパッド値 | キー直接アクセス |
この4層を単一ストアに押し込むと、後から問題が起きやすい。各層は取得方法も、失敗のパターンも異なるからだ。
機能するアプローチ:重要度スコアリングとスコープ分離
重要度スコアリングで書き込みを絞る
「全部保存」はコストを上げてノイズを増やし、「何も保存しない」ではセッションをまたぐたびにゼロリセットになる。記事が推奨するのは、書き込み前に重要度と信頼度を評価し、閾値を満たしたものだけを永続化するアプローチだ。
from pydantic import BaseModel
from datetime import datetime
class MemoryEntry(BaseModel):
content: str
memory_type: str # episodic | semantic | procedural
importance: float # 0.0 to 1.0
created_at: datetime
confidence: float # 揮発性の高い事実は時間とともに低下
source: str # このメモリを生成したもの
tags: list[str]
def should_persist(entry: MemoryEntry) -> bool:
"""重要度の閾値を満たす場合のみ長期ストアへ書き込む"""
return entry.importance >= 0.6 and entry.confidence >= 0.7
取得時にも同じフィールドでフィルタリングしてからセマンティック検索にかけることで、候補プールを小さく保ち、ストア全体を埋め込み距離でランキングする非効率を避けられる。
マルチエージェント構成ではスコープを分離する
複数エージェントが同一の共有メモリストアにアクセスする設計は、よくある落とし穴だ。調査エージェントが自身の次ステップ用に書いたメモを、コードエージェントが誤読して無関係なコンテキストで行動する、という事態が起きる。
解決策はエージェントロール別のスコープ分離だ。
class MemoryScope:
GLOBAL = "global" # オーケストレーターが読み書き
RESEARCH = "research" # 調査エージェントのみ
EXECUTION = "execution" # 実行エージェントのみ
SHARED_FACTS = "shared" # 全エージェントが読み取り可、オーケストレーターが書き込み
def write_memory(content: str, scope: str, agent_id: str):
allowed_scopes = AGENT_WRITE_PERMISSIONS.get(agent_id, [])
if scope not in allowed_scopes:
raise PermissionError(f"Agent {agent_id} cannot write to scope {scope}")
# 書き込み処理へ
スコープ外への書き込みはサイレントに汚染するのではなく、即座に失敗させる。これにより問題の特定が格段に容易になる。
ステップごとに書き込む
タスク完了時にのみメモリへ書き込む設計は、途中で失敗した場合に中間学習がすべて失われる。推奨されるのは各ステップ完了直後にワーキングメモリへ書き込み、成功したステップのみをエピソード記憶へ昇格させるパターンだ。ワーキングメモリにはTTL(有効期限)を設定し、タスクが完了しなければ自動で消える。
また、メモリの取得タイミングも重要だ。タスク開始時に一度だけ取得する設計は長いタスクで破綻する。各決定ポイントで取得し、まずワーキングメモリを確認してから、該当がなければエピソード記憶にフォールバックする設計が推奨されている。
プロバナンス(出所情報)を全書き込みに付与する
provenance: dict = {
"agent_id": str,
"tool_name": str,
"input_hash": str, # このメモリを生成した入力のハッシュ
"session_id": str,
"trust_level": float # 1.0=信頼済みシステム, 0.5=ユーザー入力, 0.0=外部Web
}
エージェントが誤動作したとき、原因が現在のコンテキストにあるのか、過去セッションで書き込まれたものにあるのかを判別するために必須の情報だ。後から付け足すのではなく、最初の書き込みから組み込むべきだと記事は述べている。
機能しないアプローチ:ベクターDB単一依存と要約圧縮
ベクターDBへの全情報集約
ベクターDBはメモリに有用だが、単一ストアとして使う場合に複数の問題が生じる。
- セマンティック類似度が必ずしも現在の決定に関連するとは限らない
- チャンキングが不適切だと関連情報が分断される
- 多段階クエリにはベクター検索が対応できない
- 基礎となる事実が変わっても、保存済み埋め込みは更新されない
要約をメモリ圧縮として使う
コンテキストが長くなったときに要約して保存する手法には、デバッグが難しい2つの失敗モードがある。
1. 重要な詳細の消失:要約は圧縮のために情報を捨てる。捨てられるのが制約条件や特定の数値である場合、その後のセッションは制約を含まない要約に基づいて動作し、問題は見えにくい形で進行する。
2. ハルシネーションの複利化:前セッションでエージェントがハルシネーションした事実が要約に含まれると、高信頼のメモリとして永続化される。後続セッションがそれをグラウンドトゥルースとして扱い、複数セッションにわたって誤りが一貫して表面化し続ける。
記事が示す対策は、自由形式の要約の代わりに型付きスキーマと信頼度閾値を使った構造化ファクト抽出だ。
# NG
summary = llm.summarize(conversation_history)
memory.write(summary)
# 推奨
facts = llm.extract(
text=conversation_history,
schema=ExtractedFacts, # Pydanticモデルで型定義
prompt="Extract only specific, verifiable facts. Exclude opinions and inferences."
)
for fact in facts:
if fact.confidence >= 0.8:
memory.write(fact)
会話全体を散文ブロックに縮約する最初のアプローチでは、詳細の消失とハルシネーションの混入が検知しにくい。2番目のアプローチでは、モデルの出力を型スキーマと信頼度閾値で制約し、書き込まれるのが検証可能な個別事実の集合になる。
詳細はAI Agent Memory Design: What Works and What Doesn'tを参照していただきたい。