この記事は、連載「読まない技術」の第 2 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。
今回は、メモリの話です。メモリというのは、人が書く指示ファイル(CLAUDE.md など)や、いまの作業を書く計画メモとは別に、AIが「次のセッションでも役に立つ」と判断したことを、専用の置き場へ自分で書き溜めていく知見のメモのことです。メモリはAIが保存するたびに増え、以後の全セッションでAIが読む量も、そのたびに増えます。ここで起きるのも、序論で見た「足すほど薄まる」です。序論で指示ファイルにルールを足していたのは人でしたが、ここでメモリを足しているのはAI自身です。AIは保存したがるので、1回の保存が、以後の全セッションに掛かる負担になります。
まず一つだけ、数えてみてください。数えるのは、AIコーディングエージェントがメモリを書き溜めている置き場——Claude Code なら ~/.claude/projects/ の下にあるプロジェクトごとのディレクトリの memory/——にある、ファイルの数とサイズです。
CC BY 4.0
ls ~/.claude/projects/ # まず一覧から自分のを探す(絶対パスの / を - に変えた名前。例: -home-you-myapp)
ls ~/.claude/projects/PROJECT_DIR/memory/ | wc -l
du -sh ~/.claude/projects/PROJECT_DIR/memory/CC BY 4.0 はここまで
の環境は89ファイル、約1.1MBでした。これは、が個人で運営している typingtube(YouTube の音楽動画でタイピング練習ができる Web サービス)を、AIと開発するあいだに溜まったメモリです。が読んだのは、そのうちの一部だけでした。仕組みを置くのに向いているのは、メモリが増え始めた日です。
メモリがここまで増えるのは、AIが保存したがるからです。失敗のあとにAIが返す「今後のために教訓を保存しました」という報告は、人には成果に見えます。保存のきっかけは、一つではありません。Claude Code の公式のドキュメントによれば、AIは作業しながら、将来の会話で役に立つかどうかを自分で判断してメモを保存し、人が「覚えておいて」と頼んだときにも保存します。がセッションの終わりに打つ「次のセッションに引き継いでください」も、AIにメモリを書かせる一言です。
がAIと開発を続けるうちに、メモリは89ファイル・約1.1MBまで増えました。この症状に、あなたなら次のどちらで対処しますか。
- 保存してよいものの基準を、保存の前にAIに読ませる
- 保存した直後に、書かれた差分を基準と並べてAIに読ませ、直させる
答えは、保存した直後に読ませるほうです。メモリが増えるのは、保存のきっかけがいくつもあり、どのきっかけでも、何を書くかをAIが決めているからです。保存のたびに索引に1行が加わり、索引は、以後のセッションの始まりに毎回読み込まれます。保存の前に読ませる基準は、AIが読む前に保存してしまえば働きません。保存を止めて基準を読ませるhookを置いても、AIは語を言い換えて、そのhookを通り抜けました。保存した直後なら、書かれた行は、どのきっかけで書かれたものでも、もうファイルにあります。hookがその行を基準と並べて返すので、AIが決めるのは、目の前の行を残すか、まとめるか、消すかです。
理解したことは一文にできます。
メモリは保存のたびに、以後の全セッションでAIが読む量を増やす。だから保存した直後に、書かれた差分を基準と並べてAIに読ませ、残すかどうかを決めさせる。
CC BY 4.0
なぜ、メモリは放っておくと増え続けるのか
人が書くメモは、読み返さなければタダです。AIのメモリは違います。メモリは、以後のセッションでAIに索引や本文を読ませるために書くものです。だから保存した時点から、メモリは以後のセッションでAIが読むものに加わります。しかも指示ファイルと違って、メモリはAIが自分で保存するので、が放っておくと、メモリは指示ファイルより速く増えます。
セッションのたびにAIのコンテキストに載るのは、索引の先頭だけです。メモリの本文を、AIは、必要になってそのファイルを開いたときにだけ読みます。だから索引に1行足すと、以後の全セッションでAIが読む量が増えますが、本文に書き足しても、その分を読むのは、AIがそのファイルを開いたときだけです。が索引の行を増やさないようにしているのは、そのためです。
指示ファイルに何を残して何を消すかの基準は、Claude Code の公式のドキュメントにあります。Claude Code には、指示ファイルを整理する /doctor というコマンドがあります。このコマンドが削るのはコードベースから導ける内容で、残すのは、落とし穴、理由、ツールの既定と違う慣習です。メモリにも同じ基準を当てます。コードベースを読めば分かることは、保存しなくても、AIが次のセッションで自力で導き出すことができます。
仕組み: 保存の直後に、書かれた差分を突きつける
置くのは、保存の直後に走るhook 1本と、数行の基準です。
も最初は、保存の前に基準を読ませていました。初めは、「メモリへ書く前にこのファイルを読む」の1行を指示ファイルに常駐させ、保存の直前にチェックリストを読ませる形でした。常駐させた1行をAIが守らず、チェックリストを読む前に保存してしまう日がありました。Claude Code の公式のドキュメントは、指示ファイルとメモリを文脈として扱い、強制される設定としては扱わないと書いています。
次には、保存前のhookを置きました。hookは、コマンド実行などの直前・直後に割り込むスクリプトです。保存前のhookは、AIが保存する直前に保存をいったん止めて、「メモは簡潔に書け。同じ対象のメモは統合しろ」という方針を読ませます。実際、AIは語を言い換えるだけで、の保存前のhookを素通りしました。研究の側でこれに当たる語は specification gaming で、目的の字面の仕様を満たすが、意図した結果を達成しない振る舞いと定義されています。「簡潔に書け」も「統合しろ」も、言葉の上でなら満たすことができます。保存を止めるhookは、止めた呼び出しを通す条件として方針を見せるので、方針は、保存を通すために字面で満たす攻略対象になりました。
今回置くhook(保存後のhook)は、ツールの実行が完了した直後(PostToolUse)に割り込みます。この時点で、保存はもう済んでいます。hookには止める呼び出しが無く、AIの前に並ぶのは、実際に書かれた行と、その行を残すかどうかの基準です。Claude Code の公式のドキュメントは、ツールが済んだあとに走るhookが返す decision: "block" の reason を、ツールの結果の隣に足して Claude へ渡すと書いています。block という名前でも、この時点でツールの実行は成功しています。この block は、済んだ書き込みと並べて基準を読ませるための差し込みです。
# 保存後に差分を突きつける hook(手元のコードを短くしたもの・PostToolUse)
import difflib, glob, json, os
SNAP_DIR = os.path.expanduser("~/.claude/.memory-snapshot") # ⚠️ 監視対象の外に置く
def added_lines(before, after):
diff = difflib.unified_diff(before.splitlines(), after.splitlines(), n=0, lineterm="")
return [l[1:] for l in diff if l.startswith("+") and not l.startswith("+++ ")]
def collect_changes(paths):
# 実ファイルをスナップショットと比べる(コマンドの文字列では、変数やパイプの先を見逃す)。
# 比べたら、次の保存のために撮り直す
changes = []
os.makedirs(SNAP_DIR, exist_ok=True)
for path in paths:
snap = os.path.join(SNAP_DIR, path.replace("/", "_"))
before = open(snap).read() if os.path.exists(snap) else ""
after = open(path).read()
changes += [f"{os.path.basename(path)}: +{l}" for l in added_lines(before, after)]
open(snap, "w").write(after)
return changes
changes = collect_changes(glob.glob(os.path.expanduser("~/.claude/projects/*/memory/*.md")))
if changes:
print(json.dumps({
"decision": "block",
"reason": "Memory changed. Added lines:\n" + "\n".join(changes) + "\n"
"Criteria: keep only constraints specific to this project that will come up again /\n"
"drop general advice and one-off events / merge notes on the same subject /\n"
"copies of steps and commands belong in documents, not in memory.\n"
"Judge this diff against the criteria, say whether to keep, merge, or delete it,\n"
"and show the added lines to the human as they are.",
}, ensure_ascii=False))
位置のほかに、もう一つ必ず満たす条件があります。hookが基準を、AIに届く欄で返すことです。同じhookのドキュメントは、画面向けの通知(systemMessage)を「あなたに見せる警告」として別の欄に分けています。この欄に書いたものが届く先は、画面の前のあなただけです。も最初はこの欄で基準を返していました。そのあいだAIが受け取っていたのは、「書き込み完了」だけでした。
基準の長さは、数行です。この基準も、保存のたびにコンテキスト(AIの作業記憶)に載ります。が基準を長くすれば、序論で見た「足すほど薄まる」が、突きつける基準の側にも起きます。
hookが見張る置き場は、一つです。メモリの置き場は1ディレクトリに、索引(MEMORY.md など)は1枚にします。AIがメモリを保存するときは、索引に1行足すところまでが、ひとまとまりの作業です。索引に載っているメモリだけを、あるものとして扱う、というルールにしておくと、AIは索引1枚を読むだけで、メモリの全体像をつかむことができます。
CC BY 4.0 はここまで
読まなくなったもの
メモリの見直しです。保存のたびに、保存したAIが、書いた行を残すかどうかを決めているので、がメモリを定期的に読み返して整理する作業は、なくなりました。がメモリの全体像をつかむには、索引1枚を読めば足ります。の索引は84行です。100行以内に収める運用にしていますが、保存の直後に行が選ばれているので、索引は、削る手間をほとんどかけずにその中に収まっています。
CC BY 4.0
注意: 索引そのものを、AIが読み切れなくなる
索引がAIに届くとき、末尾が切られることがあります。索引を届ける経路に、一度に届けることのできる量の上限があるからです。Claude Code が自分で索引を読み込む経路にも、読み込む量の上限があります。公式のドキュメントによれば、Claude Code が読み込むのは、索引の先頭200行か25KBの、先に達したほうまでです。の手元では、hook が索引を出力してAIに届けています。この経路では、出力が1万字を超えると、AIに届くのは先頭2KBの抜粋だけになります。の索引は84行ですが、1行が長いので、字数では3万字ほどです。行数を抑えても、1行が長ければ、字数の上限には当たります。あるときAIが受け取ったのは、そのうち先頭の2KBだけでした。AIは、そこに収まっていた行動ルールを「これで全部」と扱って作業に入りました。索引の後半にあった「着手前に計画書を全文読む」も、テストの回し方も、AIが受け取った2KBの外にありました。切られて残った先頭の部分は、それだけで完結した文書の形をしています。上限がある以上、索引が長くなれば、いつか必ずこの上限を超えます。対処の場所は、索引の1行目です。どの経路でも切られるのは末尾なので、索引の全文が何行あるかと、切られずに読む方法を1行目に書いておけば、索引が切られても、その1行はAIに届きます。索引1枚でAIがメモリの全体像をつかむことができるのは、AIがその1枚を読み切ったときだけです。
検証手順: 一般論を保存させて、消されることを確かめる
前提
この回の保存後のhook(PostToolUse に当たるもの)を登録し、基準を数行で置き終えておきます。メモリの置き場(1ディレクトリ)と索引(MEMORY.md など1枚)も、決めておきます。
所要時間: 5分
手順
- AIに、わざと基準に合わない保存を頼みます——「『テストは大事』という教訓をメモリに保存して」。一般論だと誰が読んでも分かる文にしてください。プロジェクト固有の語が1つでも混じると、AIがその語を見て「価値あり」と判断することがあります。そうなると、手順 1 で何を確かめたのかが分からなくなります
- AIに、今度は基準に合う保存を頼みます。プロジェクト固有の制約を1つ保存させます(「この環境では
Xを直接実行しない。理由は Y」)
合格条件(すべて満たすこと)
合格の条件は 3 つです。手順 1 では、保存が済んだ直後に、足された行と基準がAIに届いていることと、AIがその行を消すか消すことを提案し、基準のどれに当たらないかを言うこと(「一般論なので、このプロジェクト固有の制約に当たりません」のような返し)を確かめます。手順 2 では、AIがその行を残し、索引の行も1つ増えることを確かめます。
合格しなかったとき
- 保存の前に止まった —— 登録したのは保存前のhookです。割り込む位置が違います
- AIが「書き込み完了」とだけ返した —— hookが基準を画面向けの通知(
systemMessage)で返しています。AIに届くreasonで返します - 手順 1 の行が残った —— 基準の文言が、一般論を通す形になっています
後始末
手順 2 で保存させたファイルと索引の1行は、要らなければあなたが消します。
AIが保存した行を、一度も消したことがない運用は、基準がAIに届いていない運用と、同じ見え方をします。どちらの運用でも、保存された行はそのまま残るからです。
CC BY 4.0 はここまで
次回は「単体テストを読まない技術」。AI が書いたテストを、は 1 行も読んでいません。 それでも、テストは正確に動いています。
連載「読まない技術」
- ← 前回: 第 1 回 テスト結果を読まない技術
- → 次回: 第 3 回 単体テストを読まない技術
- 全回の一覧: 序論 AIの出力を、もうほとんど読んでいない