このページの目次
記憶と状態
API はステートレスであり、エージェントの「記憶」はすべてエンジニアリングによって構築されます。3層の記憶——セッション内での履歴再送信、compaction/context editing によるウィンドウ管理、memory tool/stores によるセッション横断的な永続化——は、それぞれ異なるメカニズムを用いて異なる問題を解決します。
概要
API はステートレスです(コンテキストエンジニアリング参照):モデルが1回のリクエストで「知っている」情報はすべてそのリクエストに含まれており、前回のやり取りを自動的に記憶することはありません。したがって、エージェントの「記憶」はすべてエンジニアリングによって構築されるものであり、呼び出し元がライフサイクルに応じて階層化して維持する一連のメカニズムです。「どのくらい長く存続するか」によって3層に分けるのは、この問題を整理するための最も明確なフレームワークです。
flowchart TD
A["① セッション内<br/>履歴全体を再送信 (プロトコル標準)"] --> B["② ウィンドウ上限接近時<br/>compaction 要約 / context editing による切り捨て"]
B --> C["③ セッション横断的な永続化<br/>memory tool / memory stores (セッション外へ保存)"]
第1層は「このラウンドで覚えている」、第2層は「対話が長すぎて溢れないようにする」、第3層は「終了して再開しても覚えている」ことを意味します。3層は異なるメカニズムを用いて異なる問題を解決しており、長期稼働するエージェントではこれら3つを併用することがよくあります。それぞれの境界を理解していなければ、間違ったツールを使ってしまうことになります(例えば、セッションを跨いで好みなどを記憶させたいのに、履歴の再送信だけを使ってしまうなど)。
第1層: セッション内 —— 履歴再送信のコスト
モデルが前回のやり取りを覚えているのは、履歴全体が再び送信されるからです。この層は「無料」(プロトコル標準機能)ですが、2つのコストがかかります。
- コストの線形増加: 各ラウンドで履歴全体を再送信するため、入力トークンはラウンド数に応じて累積します。
- いずれは溢れる: 履歴が無限に長くなると、いずれウィンドウ上限に近づきます(
stop_reason: "model_context_window_exceeded")。
prompt caching を使えば、再送信される安定したプレフィックス部分を約 0.1x のコストで処理できます(コンテキストエンジニアリング参照)。これによりコストが軽減されますが、溢れの問題は第2層で解決する必要があります。
第2層: ウィンドウ接近時の管理 —— 圧縮 vs 切り捨て
相反する2つの API プリミティブがあります。
| compaction(圧縮) | context editing(切り捨て) | |
|---|---|---|
| 行うこと | 過去の履歴を要約して compaction block に変換 | 古い tool_result / thinking を削除 |
| 情報の扱い | 要約して保持 | 直接削除 |
| Beta 版 | compact-2026-01-12 | context-management-2025-06-27 |
| 重要な注意点 | response.content(compaction block を含む)を塊で返す必要がある。text のみを返すと状態が失われる | ブロックのみを削除し、会話構造は変更しない |
覚え方:compaction は「要約」、context editing は「削除」です。これらはどちらも単一セッション内でウィンドウを空けるためのものであり、セッション横断的な記憶は解決しません。プロセスを閉じれば、それらは消えてしまいます。
第3層: セッション横断的な永続記憶
「終了して再開しても覚えている」ためには、重要な情報をセッション外に書き込む必要があります。2つのパスがあり、自己管理型か管理型かによって選択します。
パス A: memory tool(バックエンド自己管理)
ツール {"type": "memory_20250818", "name": "memory"} を宣言すると、モデルは /memories ディレクトリの読み書き能力、つまり6つのコマンドを獲得します。
| コマンド | 意味 |
|---|---|
view | ファイルの読み取り / ディレクトリのリスト表示 |
create | ファイルの新規作成 / 上書き |
str_replace | ファイル内の文字列を1箇所置換 |
insert | 指定行の後に挿入 |
delete | ファイルの削除 |
rename | ファイル名の変更 / 移動 |
ストレージバックエンドは実装者が自行実装します(ローカル FS、オブジェクトストレージ、DB など)。モデルはコマンドを送信するだけで、実装側が実行して tool_result を返します。Python/TypeScript SDK にはスケルトン(BetaAbstractMemoryTool / betaMemoryTool + ハンドラ)が提供されており、view/create/str_replace/... のストレージロジックを埋めるだけです。モデルは「ユーザーの好み」「プロジェクトの規約」「失敗した経験」などをファイルに書き込み、次のセッションで最初に view して読み戻します。
このアプローチは、このセッションでも現在使用しています。永続記憶は
~/.claude/projects/.../memory/下に配置され、1事実1ファイル +MEMORY.mdインデックスという形式です。MEMORY.mdは常駐し、各事実は必要に応じて読み込まれます。これが memory tool パターンの実装例です(今回のラウンドで「本サイトのビルド方法」という事実を更新しました)。
パス B: Managed Agents memory stores(管理型)
ワークスペースレベルの永続記憶庫。オブジェクトモデルは3層構造です。
メカニズムの要点:
- ファイルシステムとしてマウント: store は FUSE を介してコンテナ
/mnt/memory/<store>/にマウントされ、エージェントは通常のファイルツールで読み書きできます。システムプロンプトには、マウントが存在することを伝える説明文が自動的に注入されます。 - アクセス制御:
access: "read_only" | "read_write"。ファイルシステム層で強制されます。 - 楽観的排他制御:
memories.updateはprecondition: {type:"content_sha256", ...}をサポートしており、一致しない場合は 409 を返します。これにより、読み取り-変更-書き込みの処理で互いに上書きされません。 - 監査とロールバック: 変更のたびに
memory_versionが生成され、リスト表示、取得、redact(内容の抹消は行うが、actor とタイムスタンプは残し、情報漏洩や PII 削除用)が可能です。 - 1セッションあたり最大8つの store をマウントできます(「共有読み取り専用リファレンス + ユーザーごとの読み書き」など、階層化して使用可能)。
記憶 vs コンテキスト: どちらを使うべきか?
初心者は「長いコンテキスト」を「記憶」と混同しがちです。区別しましょう。
- コンテキスト (context): 1回のリクエストに詰め込まれるもので、1ラウンド終了時に消滅します(再送信しない限り)。「ウィンドウ」です。
- 記憶 (memory): リクエストやセッションを跨いで存続するもので、セッション外に書き込まれます。「ハードディスク」です。
「このラウンドで見たい」場合はコンテキストを使用(RAG でチャンクをウィンドウに配置)、「将来も覚えていたい」場合は記憶を使用(ファイルに書き込み)します。compaction/context editing はコンテキスト層の管理であり、記憶そのものではありません。
デザインとセキュリティ
優れた記憶設計(およびこのセッションでの記憶の書き方):
- 1つの経験につき1ファイル。ファイルの先頭に1行の要約を置き、関連性の判断を容易にします。
- 自明でない情報のみ保存。コードや履歴に既に存在する情報は保存しない(ノイズになるため)。「なぜそうなのか」「どこでつまずいたか」を保存します。
- 更新時は上書き、不要時は削除。重複を積み重ねず、既存のエントリを更新します。古くなったものは削除します。
セキュリティのレッドライン:
- パスは必ず検証する: モデルが渡す
pathは信頼できない出力です。正規化されたパス(realpath/Path.resolve())に解析し、記憶ルートディレクトリ内にあることを確認してください。..、シンボリックリンク、絶対パスによる逸脱、URL エンコードされたトラバーサル(%2e%2e%2f)を拒否してください。生の path で直接open()しないでください。 - シークレットを絶対に保存しない: API キー、パスワード、トークンは記憶に含めないでください。PII は慎重に扱ってください(GDPR/CCPA 対応)。memory stores の
redactは事後の対処手段です。 - マルチテナント分離: ユーザーごとに独立した記憶ディレクトリ + 認証を設けてください。参考実装には内蔵のアクセス制御はありません。
ベストプラクティス
- 「存続期間」に応じて3層を使い分ける。 セッション内では履歴の再送信、ウィンドウ接近時には compaction/context editing、セッション横断では memory tool/stores を使用——履歴の再送信で「好みなどを記憶させよう」としないでください。
- 1つの経験につき1ファイル、先頭に1行の要約。 関連性の判断を容易にし、
MEMORY.mdインデックスを常駐させて、各事実は必要に応じて読み込む(このセッションではそのようにしています)。 - 自明でない情報のみ保存。 コードや履歴に既に存在する情報は保存しない(ノイズになるため)。「なぜそうなのか」「どこでつまずいたか」「決定した規約」を保存します。
- 更新時は上書き、不要時は削除。 重複を積み重ねず、既存のエントリを更新します。古くなったものは削除します。
- パスは正規化してから境界を検証する。 モデルが渡す
pathは信頼できないため、resolve()して記憶ルート内にあることを確認し、../シンボリックリンク/URL エンコードされたトラバーサルを拒否します(セキュリティと防護参照)。 - シークレットは絶対に記憶に含めず、マルチテナントはユーザーごとに分離する。 参考実装には内蔵のアクセス制御はありません。PII は慎重に扱い、
redactは事後の対処手段です。
トレードオフと失敗パターン
- すべてを記憶に詰め込む: ノイズになり、検索の精度が低下する → 高価値で自明でない項目のみを保存する。
- 記憶をトランザクションデータベースのように扱う: 記憶はモデルが読むためのメモである → 同時書き込みには
content_sha256による楽観的ロック(memory stores)または自己管理型のバージョン管理を使用する。 - セッション横断でのユーザー混同: マルチテナントが分離されていない → ユーザーごとにディレクトリを分け、認証を行う。
- パスの未検証: ディレクトリトラバーサルにより任意のファイルが読み書きされる → 正規化 + 境界チェックを行う。
参考
- Anthropic 公式ドキュメント: Memory Tool、Managed Agents Memory Stores (platform.claude.com)
Keywords: stateless, 会话历史, prompt caching, compaction, context editing, model_context_window_exceeded, persistent memory, memory tool, memory_20250818, /memories, view/create/str_replace/insert/delete/rename, BetaAbstractMemoryTool, memory stores, memstore, FUSE mount, access, content_sha256, precondition, redact, memory version, path traversal, secret, PII, 多租户隔离