メモリーシステム v2: コンテキスト肥大化問題の解決
AI エージェントを悩ませていた2つの問題がありました。ツール出力によってコンテキストウィンドウが爆発的に増加し(コンテキストの82.5%がツール結果)、/new コマンドで短期メモリの引き継ぎなしにすべての作業状態が削除されることです。我々は積極的なコンテキストプルーニング、MEMORY.md を97%圧縮、および各セッションリセット前にメモリファイルを自動的に更新するセッションハンドオフフックを実装しました。
永続メモリシステムの構築に関する前回のブログ記事では、MEMORY.md の肥大化問題について説明しました。6週間後にファイルが700行を超えて増加し、インラインコンテンツからポインタベースのエントリへの切り替えで修正しました。修正は機能しました。MEMORY.md はコンパクトになり、セッション起動が改善され、すべてが正常に機能していました。
その後、再び肥大化しました。
4週間後、MEMORY.md は92,000文字790行に戻っていました。オーガナイザーパイプラインは新しいファクトをインラインで書き続け、トピック別ファイルへの委譲を延ばしていました。バイトサイズの制限が一貫して適用されていませんでした。元々の修正は症状を改善していましたが、根本原因を解決していませんでした。
さらに問題だったのは、MEMORY.md が制御下にあるにもかかわらず、セッションがタスクの途中でコンテキスト制限に達するようになったことです。エージェントはいくつかのファイルを読み、検索を実行し、その後停止しました。メモリが足りなくなったのではなく、コンテキストウィンドウがそのセッションの前の部分からのツール出力でいっぱいになったのです。
そして、我々が許容していた3番目の問題がありました。/new を実行して新しいセッションを開始するたびに、エージェントはそれがちょうど何をしていたのかについてのすべての認識を失いました。長期メモリシステム(v1)はファクト、好み、プロジェクト知識をうまく処理していました。しかし、短期の作業状態(どのタスクが進行中か、どの決定が最近下されたか、次のステップは何か)は完全に消えてしまいました。ユーザーは、リセット前にメモリファイルを更新するようエージェントに手動で指示するか、コンテキストを失うことを受け入れる必要がありました。
3つの問題、1つのテーマ:あらゆるタイムスケールでコンテキストのための体系的なライフサイクルがありませんでした。
修正前の測定
何かを変更する前に、session-stats.py を記述して、過去15セッションを分析し、コンテキストが実際どこに行っているのかを理解しました。出力は明確でした。
Session context breakdown (15 sessions, chars):
┌──────────────────┬───────────┬───────────┬────────────┐
│ Category │ Total │ % of ctx │ Avg/session│
├──────────────────┼───────────┼───────────┼────────────┤
│ Tool results │ 1,842,300 │ 82.5% │ 122,820 │
│ System prompt │ 268,100 │ 12.0% │ 17,873 │
│ Assistant text │ 64,700 │ 2.9% │ 4,313 │
│ User input │ 55,900 │ 2.5% │ 3,727 │
└──────────────────┴───────────┴───────────┴────────────┘
最も極端なセッション:159,000文字のツール結果、ユーザー入力とアシスタントテキストの合計1,500文字。実際の会話は独自のコンテキストウィンドウではほぼ見えませんでした。
システムプロンプトはセッションあたり平均17K文字でした。起動時に MEMORY.md が読み込まれることはわかっていましたが、メモリに関連した何も起こらなかったセッションを含むすべてのセッション全体で、総コンテキストの12%を占めるのを見て、この数字は具体的になりました。エージェントは、何をしているかに関わらず、すべてのセッションで17K文字のコンテキスト税を支払っていました。
2つの問題が測定可能になりました:セッション内でのツール結果の肥大化と、セッション間でのMEMORY.md の肥大化。どちらも解決可能で、ソリューションを評価するための数字がありました。
ソリューション1:コンテキストプルーニング
セッション内の問題は、ツール出力が蓄積することです。エージェントがファイルを読む—それは8K文字のコンテキストです。検索を実行する—別の4K。ファイルを編集し、ディフを見る—2K。テスト出力を読む—6K。中程度の複雑なタスクの後、コンテキストはほぼ、エージェントがもはや参照する必要がない前のステップからのツール出力です。
OpenClaw の contextPruning 機能は TTL ベースのアプローチで処理します。設定可能な時間ウィンドウの後、最も最近のターンを超えるツール出力はプレースホルダーで置き換えられます。コンテンツはアクティブなコンテキストから消えますが、エージェントは何かが起こったのを見ることができます。
我々の設定:
contextPruning:
mode: cache-ttl
ttl: 30
minPrunableToolChars: 100
hardClearRatio: 0
ttl: 30 で、30秒より古いツール結果は次のターンでプルーニングの対象となります。minPrunableToolChars: 100 はほぼ何も費用がかからない小さなツール出力の置き換えを防ぎます。hardClearRatio: 0 は完全なワイプを決してしないことを意味します—最も最近のターンはそのままです。
効果として、エージェントは蓄積された完全な履歴ではなく、最近のツール コンテキストのスライディングウィンドウで動作します。ファイルの反復的な読み込みや検索−反復ループに関連するタスクでは、これはステップ8でコンテキスト制限に達することとタスクを完了することの違いです。
我々が懸念していたことの1つ:プルーニングはエージェントが以前の作業を参照する能力を破壊するでしょうか?実際には、そうではありません。ほとんどのタスクでは、エージェントは最も最近のツール呼び出しの出力を必要とするか、メモリにあるべき一般的なファクトを必要とします。エージェントがすでに処理したファイルを再度読む必要がある場合、それは通常、そのファクトがメモリに書き込まれるべき兆候です。コンテキストにキャッシュされるのではなく。
ソリューション2:MEMORY.md の構造圧縮
92K→コンパクト移行は、最初は避けていた設計上の質問に直面する必要がありました:MEMORY.md はいったい何を含むべきでしょうか?
v1 の答えは、「最近のアクティビティ、アクティブなプロジェクト、重要な連絡先、インフラストラクチャノート」であり、管理可能に保つためのバイトサイズの上限がありました。これは間違っていました。バイトサイズの上限はコンテンツを圧縮するインセンティブですが、蓄積を防ぎません—各エントリは短くなる前に、容量が足りなくなると、ルールを曲げ始めるだけです。
正しい答えは、MEMORY.md はポインタを含むべき、コンテンツではないということです。「このファイルは何のためですか?」という質問に「X を含むために」と答えることができるなら、MEMORY.md は X を含むべきではありません—「X については memory/X.md を参照」を含むべきです。MEMORY.md はエージェントに何を見るべきかを伝えるインデックスであり、エージェントが何を知っているかを含む文書ではありません。
その定義を使うと、ターゲット構造は明らかになりました:
## Users
| handle | role | notes |
| --- | --- | --- |
| @orange | owner | ... |
## Projects
| name | status | detail file |
| --- | --- | --- |
| claw-stack | active | memory/entities/project-claw-stack.md |
| info-pipeline | active | memory/entities/project-info-pipeline.md |
## Infrastructure
| service | notes | detail file |
| --- | --- | --- |
| CF Workers | edge compute | memory/infra/cloudflare.md |
## Behavior rules
See AGENTS.md for current rules.
## Recent (last 5)
- 2026-03-09: ...
構造化されたファクト用のテーブル(ユーザー、プロジェクト、インフラ)。他のすべて用のポインタ。最近のアクティビティは5エントリに制限され、ローリング。合計目標:5,000文字未満。
移行後、MEMORY.md は92,000文字から2,900文字になりました—97%削減です。セッション起動は、MEMORY.md コンテキストの〜23K トークンから〜700トークンに移動しました。以前 MEMORY.md にあったすべてはまだ QMD ベクトル検索で検索可能です。インラインではなく、トピック別ファイルに存在するだけです。
移行スクリプト自体は約150行の Python でした:現在の MEMORY.md を読む、Claude Haiku を使用してカテゴリ別にファクトを抽出し、適切なトピック別ファイルにファクトを書き込む、新しいポインタベースの MEMORY.md を生成します。実行には20秒かかりました。
ソリューション3:セッションハンドオフフック
コンテキストプルーニングと MEMORY.md の圧縮は、技術的な肥大化問題を対処しました。我々が許容していた3番目の問題がありました。新しいセッションを開始するために /new を実行するとき、現在のセッションからすべての作業コンテキストを失います。どのファイルを編集していたか?次のステップは?デバッグしていたバグについてちょうど何を理解したか?
従来の反応は「より良いノートを書いてください」です。我々はそれを自動化したかった。
OpenClaw は特定のコマンドで発火するフックをサポートします。新しいセッションが開始する前にセッション要約パイプラインを実行する command:new フックを書きました:
# Triggered on /new
def session_handoff(transcript):
summary = claude_haiku(
system=open("MANIFEST.md").read(), # file map for the memory system
prompt=f"Summarize this session. Extract: current work state, "
f"decisions made, lessons learned, entities updated. "
f"Format as structured updates for memory files.\n\n{transcript}"
)
apply_memory_updates(summary) # updates MEMORY.md, TODO.md, entities, etc.
フックは20秒のタイムアウト付きで同期的に実行され、トランスクリプトが迅速に処理するには大きすぎる場合は非同期にフォールバックします。実際には、ほとんどのセッションは8~12秒で処理されます。
重要な部分は MANIFEST.md で、メモリシステムの構造を説明するファイルです:どのファイルが存在するか、それぞれが何を含むか、どの種類の更新がどこに行くか。それがなければ、Haiku はプロジェクト更新が MEMORY.md ではなく memory/entities/project-X.md に行くべきことを知りません。MANIFEST はメモリを維持するエージェントのスキーマドキュメントです。
ハンドオフフック後、/new はまだ新しいコンテキストを開始しますが、MEMORY.md は現在のセッションの成果を反映しています。次のセッションは、どこで離れたかを知ります。
衰退防止ルール
システムを2回再構築した後、同じ問題の再発を防ぐために AGENTS.md に明示的なルールを書きました:
ハード制限:
- MEMORY.md は5,000文字以下に保つ必要があります。更新が超えるとき、トピック別ファイルに書き込み、ポインタを追加する代わりに。
- コミットハッシュ、コードスニペット、または生のエラーメッセージを MEMORY.md に書き込まないでください。これらは一時的か(コミットハッシュ、エラー)、トピック別ファイルに属しています(コード)。
禁止コンテンツ:
- 5個以上のアイテムのリスト(トピック別ファイルを使用)
- 別のメモリファイルにすでに存在するファクト(重複なし)
- 「一時的な」ノート(MEMORY.md ではなく TODO ファイルに書き込む)
定期メンテナンス:
- 3個以上のファイルに触れたセッションの後、トピック別ファイルを更新する必要があるかどうかを確認します
- プロジェクトステータスが変更されるとき、MEMORY.md テーブルではなくエンティティファイルを更新します
AGENTS.md に書き込まれたルールはシステムプロンプトの一部になるため、オーガナイザーパイプラインとハンドオフフックの両方がそれらを見ます。これらはコードで強制されません、コンテキストの明示的なルールは非公式な規約より有意に優れています。
測定された結果
v2 変更をデプロイした直後の即時結果:
| メトリック | 前 | 後 |
|---|---|---|
| MEMORY.md サイズ | 〜92K 文字 (〜23K トークン) | 〜2.9K 文字 (〜700 トークン) |
| セッション起動コンテキスト税 | 〜23K トークン | 〜700 トークン |
| ツール結果のコンテキストシェア | 82.5% | 30秒後にプルーニング |
| /new 全体での作業状態の保存 | いいえ | はい(自動化) |
MEMORY.md の削減は97%の削減です。すべての新しいセッションは22K少ないオーバーヘッドトークンで開始され、実際のタスクにより多くの余地があることを意味します。コンテキストプルーニング設定とは、30秒より古いツール結果はプレースホルダーで置き換えられることを意味し、複数ステップタスクでステップ8でコンテキスト制限に達するセッション内蓄積を防ぎます。
ハンドオフフックが一貫してコンテキストメモリ更新を生成するかどうかは、数週間の使用後に分かるでしょう。アーキテクチャは正しい—質問は Haiku の判断が規模でどれだけ保持するかということです。我々はバックレポートします。
メモリについて学んだもの
v1 ブログ記事は肥大化問題を技術的な問題として、技術的な修正を使用としてフレーミングしました:バイトサイズ制限を強制し、インラインコンテンツではなくポインタを使用します。そのフレーミングは正しかったが不完全でした。
実際の問題は、メモリ管理はストレージ問題ではなく、情報アーキテクチャ問題だということです。「このファクトは後で関連があるかもしれないので、MEMORY.md に入れてください」と言う度に、悪いインデックス決定をしていました。MEMORY.md は特定の層ではなく、キャッチオール容器として使用されていました。
v2 システムが機能するのは、より良い強化メカニズム(TTL プルーニングとサイズ制限は役立つ)があるからではなく、各層は何のためにあるかについてがより明確だからです:
- アクティブなコンテキスト:現在のセッションの作業状態。一時的。積極的にプルーニング。
- MEMORY.md:セッション向け。セッションを開始するために必要な最小コンテキスト。ポインタのみ。
- トピック別ファイル:特定のトピックについての深さ。オンデマンドで読み込まれます。コンテンツが存在する場所。
- ベクトル検索:すべてのメモリ全体でのフォールバック取得。どこを見るか分からないクエリ用。
新しいファクトが到着するとき、質問は「これを覚えるべきか?」ではありません。「この層はどこに属していますか?」のです。ほとんどのファクトは MEMORY.md に属しません。そのアーキテクチャを正しく取得することが肥大化を防ぐものです。
エージェント開発者のための実践的なポイント
同じようなものを構築している場合、我々が2回犯した間違いは知る価値があります:
インデックス/コンテンツの分離を書き込み時に強制し、遡及的ではなく強制します。 MEMORY.md のバイトサイズ制限は肥大化を防ぎません—それはそれを超える前により小さくするだけです。本当の制約は:インデックスにコンテンツなし、ポインタのみ。すべての書き込みをこれを確認します。
最適化する前にコンテキスト分布を測定します。 MEMORY.md が主な問題だと仮定しました。それは問題でした。ツール結果はより大きな問題でした。session-stats の実行に1日かかり、より大きな問題をすぐに明らかにしました。測定優先。
TTL ベースのコンテキストプルーニングは低リスク高リターンです。 それはエージェント動作を破壊するのではないかと心配しました。それはしませんでした。ほとんどのタスクでは、古いツール結果はノイズで、シグナルではありません。プルーニングします。
ハンドオフフックは完璧なノート取得より価値があります。 信頼性でセッション終了ノートを取ることをヒューマン(またはエージェント)に求めるのは失敗の戦略です。自動化します。書き込まれない手動ノートより10秒かかる粗い抽出さえ優れています。
メモリシステムのスキーマをそれを使用するエージェントについて文書化します。 MANIFEST.md パターン—物事がどこに行くかを説明するファイル—は自動化されたメモリ更新が実際に正しい場所に物事を置く理由です。それがなければ、すべての更新がファイル配置についての臨機応変の決定になります。
AI エージェント用のメモリシステムはまだ十分に若いため、確立された慣行がありません。これらはわれわれの規模で機能したパターンです。あなたの規模、アクセスパターン、エージェントのタスク分布は異なった制約を生産するでしょう。しかし、基礎となる原則は保持します:エージェントメモリは情報アーキテクチャです。インフラストラクチャを構築する前にアーキテクチャを正しく取得します。