エージェントのメモリ設計
Claude Code メモリの索引 MEMORY.md と、毎回読ませる引き継ぎ文書が247KBまで膨らんだ。索引の書き方と、腐るメモの見分け方をまとめる。
エージェントにメモを残させると、最初は賢くなったように見えます。数か月たつと、メモは膨らみ、古くなり、ときには嘘をつきます。この記事では、編集部が Claude Code のメモリと引き継ぎ文書で踏んだ失敗と、いまの設計をまとめます。
試した環境は、編集部の検証環境(Linux、Claude Code 2.1.301、Python 3.13.5、2026-10-16)です。
Claude Code メモリと MEMORY.md の上限
Claude Code の auto memory の索引 MEMORY.md は、セッションの開始時に先頭200行か25KBまでしか読まれません(出典:https://code.claude.com/docs/en/memory 、2026-10-16確認)。手元で日本語の索引の行を200行並べると 13,199 bytes だったので、行数で管理すれば足ります。CLAUDE.md 側の行数の考え方は「CLAUDE.mdは削るほど効く」に書きました。
索引の各行に「いつ開くか」を書く
メモは、存在するだけでは効きません。読まれて初めて効きます。「着手前に読む」と書いたメモがあったのに、読まれないまま同じ穴に落ちたことがあります。
そこで、索引の1行ごとに、開くきっかけを書くことにしました。「commit が拒否された時」「本番の再開を議論する時は必読」のような書き方です。同じ場面で開くメモは1本にまとめ、まとめ元を merged_from に残します。
メモを書くときは、Why:(なぜ)と How to apply:(どう使うか)の2項目を必須にしています。書き込みそのものは hook で確認(ask)にして、人が通すかどうかを決めます。
メモには方針だけを書き、状態は書かない
メモリに置くのは、長く変わらない方針だけです。「いま何をしているか」という状態は、引き継ぎ文書と git の側を正とします。
状態を書いたメモは腐ります。引き継ぎ文書にあった環境変数の値を信じてデバッグしたら、その値は3日前に変わっていました。判断の直前に、実際の値を取り直します。
前のセッションの診断も、コードとデータで確かめ直します。「commit したのに行が無い」という診断は誤りでした。タイムゾーンのせいで、処理が3時間前に止まったように見えていただけでした。
結論が変わったメモには版数を付ける
訂正したメモには版数を付け、「第3版を先に読め」と書きます。欠陥が見つかった分析スクリプトは archive に移したうえで、次の3点をそろえます。
- スクリプトの先頭に、退役したことを書く。
- README の表に1行足す。
- 元の結果の文書に「この数値を引用しない」と書く。
古い数字が引用されるのを、機械的に防ぐためです。
毎回読む文書は膨らむ
セッションの開始時に読ませる文書は、膨らむと最大のコンテキストの負債になります。編集部では、タスク表が90〜120KB、引き継ぎ文書が247KBになりました。タスク表の「最終更新」の行に、40セッション分のログがつながって65KBになったこともあります。
いまは、終わったタスクを1行に縮め、作業のログは引き継ぎ文書とコミットの本文に置いています。
メモが判断を縛る
エージェントが書いたメモは、次のエージェントの枠になります。 「新しい方針を探して」と頼んでも、メモに残っていた過去の成果の数字が基準点になり、既存の方針の改良に戻っていきました。新しい案を探させるときは、そのメモを読ませない構成も考えます。
注入された文脈を疑う
頼んでいない別の LLM のサービスを、エージェントが使おうとしたことがあります。API の設定は正常でした。原因は、外部のツールが CLAUDE.md に書き込んでいた過去の活動のログでした。
そのツールを外したあとも、ほかの AI ツールの設定ディレクトリや、ホーム直下の ~/CLAUDE.md に、計40件の書き込みが残っていました。Claude Code は、作業ディレクトリから上の階層の CLAUDE.md を、すべて起動時に読み込みます(出典:https://code.claude.com/docs/en/memory 、2026-10-16確認)。ホーム直下のファイルは、その下のどのプロジェクトでも読まれます。
上の階層に何があるかは、次のように確かめられます。
#!/usr/bin/env bash
# find-claude-md.sh:cwd から / まで遡って CLAUDE.md を列挙する
d=$(cd "${1:-.}" && pwd)
while :; do
for f in "$d/CLAUDE.md" "$d/.claude/CLAUDE.md" "$d/CLAUDE.local.md"; do
[ -f "$f" ] && echo "$f ($(wc -c <"$f") bytes)"
done
[ "$d" = / ] && break; d=$(dirname "$d")
done
試しにホーム直下へ CLAUDE.md を置くと、プロジェクトの外にある1本も一覧に出てきました。設定を疑う前に、注入されている文脈を疑います。実際に読み込まれたファイルは、セッションの中の /context でも確認できます(同出典)。
エージェントの報告を実数で確かめる仕組みは「AIの「完了しました」を信じない仕組み」に書きました。
知識ベースは3層に分け、期限を持たせる
メモが増えたら、知識ベースとして整理します。編集部は「LLM Wiki」と呼ばれる形を使っています。元の資料(Raw)、LLM がまとめたページ(Wiki)、書き方の決まり(Schema)の3層です。知識ベースが放置される一番の理由は手入れの手間なので、その手入れを LLM に任せます。
各ページには status、confidence、review_due を持たせ、期限切れを lint で拾います。手元で動かした lint です。
import sys, re, pathlib, datetime
today = datetime.date(2026, 10, 16)
for p in sorted(pathlib.Path(sys.argv[1]).glob("*.md")):
fm = dict(re.findall(r'^(\w+):\s*(.+)$', p.read_text().split('---')[1], re.M))
miss = [k for k in ("status", "confidence", "review_due") if k not in fm]
if miss: print(f"{p.name}: 欠落 {miss}"); continue
if datetime.date.fromisoformat(fm["review_due"]) < today:
print(f"{p.name}: 期限切れ {fm['review_due']}")
a.md: 期限切れ 2026-09-30
c.md: 欠落 ['confidence', 'review_due']
AI が作ったものと、人が書いたノートは、置き場所を分けます。編集部のノート置き場は、9割以上が機械の生成物になっていました。「探しにくい」の本当の原因は、フォルダの構成ではなく、生成物が混ざっていたことでした。解決したのは、整理ではなく隔離です。
公式の資料が無い分野の知識には、主張ごとに「公式」「コミュニティ」「異論あり」「反証済み」の等級を付けて、実測で育てます。
まとめ
MEMORY.mdは先頭200行か25KBまで。日本語の索引なら、行数で管理する。- 索引の各行に「いつ開くか」を書く。メモは読まれて初めて効く。
- メモには方針だけを書く。状態は引き継ぎ文書と git、値は判断の直前に取り直す。
- 上の階層の CLAUDE.md と、注入された文脈を疑う。
- 知識ベースは3層に分け、期限を lint で拾い、生成物は隔離する。