Max Nardit
← 記事
15 mindev

Anthropic のエージェントメモリの構成要素を Claude Code で読む:MEMORY.md を監査し、トランスクリプトを残し、自前のドリーミングを回す

Claude Code はメモと起動時のインデックスを無期限に残す一方、その裏付けになるトランスクリプトはデフォルトで 30 日後に削除し、両者を突き合わせる仕組みは持っていません。Anthropic の結果を見ると、その突き合わせには価値があります。ローカルで実行し、反映する前に結果を読む方法をまとめました。

目次

11 月 6 日、Anthropic のエンジニアリングブログに「The components of agent memory」という長い記事が出ました。メモリを「Claude が経験から書き、後のセッションで読むコンテキスト」と定義し(重み、in-context learning、compaction は対象外)、4 つの層に分け、そのいくつかで実験しています。Claude Code のドキュメントと並べて読むと、デフォルト設定の食い違いが見えてきます。Claude Code はメモを誰かが消すまで残し、その裏付けになるトランスクリプトは 30 日で削除します。ところが記事の主な修復の仕組みは、この 2 つを突き合わせることで成り立っています。

まず数字です。CL-Bench 1.0 のデータベースタスクで、メモリは Opus 5.5 の平均報酬を 0.655 から 0.838 に、Opus 4.7 を 0.383 から 0.700 に引き上げました。Sonnet 4.6 は 0.330 から 0.364 とほとんど動きませんが、メモリを読んで仮説を検証するよう指示すると 0.641 に達します。効いたのは保存した内容を読んで検証することで、保存量を増やすことではありませんでした。

以下は、この記事を読んだあとに Claude Code の環境で実行したい監査です。最後に、Anthropic がドリーミング(dreaming)と呼ぶ統合処理をコピー上で回し、diff で確認します。

Anthropic のエージェントメモリの構成要素、4 つの層とは何か?

下から順に、トランスクリプト、メモリノート、メモリ表現、注入コンテキストです。トランスクリプトは過去のセッションを検索できるイベントログです。ノートは Claude が書き残すと決めたもので、メモリディレクトリ内のファイルなどが当たります。表現はその上に Claude が維持する構造化された形で、リンクでつながった wiki が代表例です。注入コンテキストは最初のプロンプトより前に毎回のセッションへ読み込まれるものです。

ドリーミングは層の間で情報を動かす処理です。Anthropic の説明では、ノートを元のトランスクリプトと照らして検証し、誤りだとわかったものを書き直し、表現を構築・整理し、ノートを注入層へ昇格させることもあります。記事で一番わかりやすい例は Pokémon Red のプレイです。ポケモンセンターの場所についての誤ったノートのせいで、Claude は Mt. Moon で一方通行の段差を 3 回も降りてしまいました。ドリーミングがそのノートをセッションのトランスクリプトと照合して書き直したあと、Claude は洞窟を抜けられました。

二度読みたいのは wiki の結果です。良い面と悪い面の両方があるからです。約 350,000 ファイルの Stainless のコーパスでは、ドリーミングを回すセッション数が増えるにつれ、wiki 単体の評価基準達成率は 13% から 48% まで上がりましたが、それでも普通の検索の 53% には負けました。wiki と検索の併用は 61% に達し、質問あたり約 20,000 トークンを節約しています。複数文書にまたがる統合が必要な Harvey の LAB の合併関連の質問では、wiki が 87% 対 73% で検索に完勝しました。一方、別の LAB タスクでは wiki がソースの 1.77 倍、115 KB に対して 203 KB まで膨らみ、記事は「実質的に何も抽象化しなかった」と書いています。表現層は答えを組み立てる必要がある場面で役に立ちますが、それ自体が圧縮になるわけではありません。何かがそう保たない限り、圧縮にはなりません。

Claude Code では各メモリ層がどこにあるのか?

4 層のうち 2 つは組み込み、1 つは組み込みだが期限切れで消え、残り 1 つは自分で作らない限り存在しません。Claude Code 2.1.319 の場合は次のとおりです。

Layer                 Claude Code                                         Lifetime
transcripts           ~/.claude/projects/<project>/*.jsonl                swept once older than
                      (subagents under <session>/subagents/)              cleanupPeriodDays (default 30)
memory notes          ~/.claude/projects/<project>/memory/*.md            kept until edited or deleted
representations       none built in
injected context      CLAUDE.md files + first 200 lines or 25KB of        every session
                      memory/MEMORY.md

トピックファイルは起動時には読み込まれません。必要だと判断したときに Claude が読み、その判断はほぼ MEMORY.md にある 1 行の項目だけを頼りにしています。ノート層のうちコンテキストウィンドウに確実に入るのはインデックスだけです。これは Markdown メモリについてのエッセイ で書いた地図の問題と同じで、制限を超えた先のノートは、最初から探そうと思ったエージェントにしか見つかりません。

ドリーミング自体は Claude Code にはありません。Anthropic はこれを Claude Managed Agents の dreams として提供しており、アクセス申請と dreaming-2026-04-21 ベータヘッダーが必要なリサーチプレビューです。dream はメモリストアと 1 から 100 のセッショントランスクリプトを受け取り、重複を統合し、矛盾した項目を置き換えた新しいストアを書き出します。入力ストアには一切手を加えないので、出力を確認してから採用するか捨てるかを選べます。API が使えない環境でも、この設計は真似する価値があります。

MEMORY.md を Claude Code の 200 行・25KB 制限と照らしてどう確認するか?

すべてのインデックスのサイズを測り、加えてサイズ制限からは見えない 2 点を探します。もう存在しないファイルを指すインデックス行と、インデックスに一度も出てこないトピックファイルです。次のスクリプトは全プロジェクトに対してこの 3 つを調べます。

shell
#!/usr/bin/env bash
# Audit Claude Code auto memory indexes: size against the 200-line / 25KB load,
# index lines pointing at missing files, topic files the index never names.
# Raw-file numbers: Claude Code measures the index after stripping frontmatter
# and HTML comments, so treat this as an approximation.
# Usage: memory_audit.sh [projects_dir]   (default ~/.claude/projects)
ROOT="${1:-$HOME/.claude/projects}"
for idx in "$ROOT"/*/memory/MEMORY.md; do
  [ -f "$idx" ] || continue
  dir=$(dirname "$idx")
  lines=$(awk 'END { print NR }' "$idx")     # counts a last line with no newline
  bytes=$(wc -c < "$idx")
  status=ok                                   # 25KB taken as 25,600 bytes
  if [ "$lines" -gt 200 ] || [ "$bytes" -gt 25600 ]; then status=CUT
  elif [ "$lines" -gt 160 ] || [ "$bytes" -gt 20480 ]; then status=near   # my 80% mark
  fi
  links=$(grep -oE '\]\([^)]+\.md\)' "$idx" | sed -E 's/^\]\(//; s/\)$//' | sort -u)
  dangling=0
  while IFS= read -r f; do
    [ -n "$f" ] && [ ! -f "$dir/$f" ] && dangling=$((dangling+1))
  done <<< "$links"
  orphans=0                                   # whole file name, linked or not
  for f in "$dir"/*.md; do
    b=$(basename "$f"); [ "$b" = MEMORY.md ] && continue
    grep -qE "(^|[^[:alnum:]_.-])${b//./\\.}([^[:alnum:]_.-]|\$)" "$idx" || orphans=$((orphans+1))
  done
  printf '%-5s %4d lines %6d bytes  dangling=%d orphans=%d  %s\n' \
    "$status" "$lines" "$bytes" "$dangling" "$orphans" "$(basename "$(dirname "$dir")")"
done | sort -k2 -nr

手元のマシンでの出力の上位です(プロジェクト名は置き換えています)。

near   184 lines  10393 bytes  dangling=0 orphans=0  project-a
ok     110 lines   9578 bytes  dangling=0 orphans=2  project-b
ok      90 lines  10204 bytes  dangling=0 orphans=3  project-c

制限を超えたインデックスはありません。一番大きいインデックスで効いてくるのは行数の制限です。25KB を 200 行で割ると 1 行あたり 128 バイトで、このインデックスは約 56 バイトなので、200 行に達するのは 11KB 前後です。バイト制限が問題になるのは項目が長くなったときで、たいていはトピックファイルに置くべき内容が入っています。near の判定は自分で決めた 80% のしきい値で、Claude Code がリマインダーに使う基準とは関係ありません。

最初に見るべきは orphans の列です。サイズは Claude Code がすでに見張っています。Claude が MEMORY.md を書いたあと、制限に近いインデックスなら短くするよう Claude に促し、超えていればエラーを返します。ところが、手動編集や、統合処理で行がまとめて消されたことでインデックスから外れたトピックファイルは誰も見張っていません。ファイルはディスクに残っており、Claude もフォルダの一覧や検索で見つけられますが、起動時のインデックスはもうその存在を知らせません。探しに行かないセッションには見えないままです。orphan ごとに、MEMORY.md に行を戻すか、ファイルを消すかのどちらかを選んでください。dangling はその逆のケースで、行を消すだけで済みます。

orphan のチェックは 2 回間違えました。ファイル名を単純な部分文字列で照合すると、名前が別ファイル名の末尾と一致する本物の orphan を見逃します。note.md が other_note.md の陰に隠れるわけです。リンク先としてだけ照合すると、インデックスがバッククォートで名前を挙げているノートまで orphan と判定してしまいます。Claude はそれを問題なく読めるのに、です。自分のフォルダでも、このバージョンは誤検出を 1 件出しました。名前を単語全体として照合すれば、どちらの問題も避けられます。

監査ではもう 1 つ見つかりました。Claude Code は Claude がメモリファイルを書くとき、frontmatter に modified タイムスタンプを記録しますが、手元ではこれを持つトピックファイルがほとんどありません。代わりになるのはファイルの mtime ですが、証拠としては弱いものです。わかるのはファイルが最後に変わった日時で、内容が最後に正しかった日時ではありませんし、コピーや復元でリセットされることもあります。古いノートを判定するためではなく、どのノートから再確認するかを決めるために使ってください: ls -lt --time-style=+%F ~/.claude/projects/<project>/memory/。

ドリーミングに必要なトランスクリプトを Claude Code はどれくらい残すのか?

デフォルトで 30 日です。しかも削除は何の通知もなく行われます。セッショントランスクリプトの保持期間は cleanupPeriodDays で決まり、デフォルトは 30、最小は 1 です。セッション開始後のバックグラウンド処理が、それより古いトランスクリプトファイルをメッセージなしで削除します。メモリディレクトリは削除対象から明示的に除外されています。ノートは誰かが編集するか消すまで残ります。

これを記事と並べてみます。ドリーミングはノートをトランスクリプトと照らして直しますが、Anthropic は未解決の課題として、ノートが「最初から間違っている」ことがあると認めています。デフォルトではノートは元のセッションより長く生きるので、そのトランスクリプトが削除される古さになると、ノートの誤りを示せたはずの証拠は消え、ノートだけが残ります。ノートが長く生きるほど、照合できる証拠は減っていきます。

手元の設定ではトランスクリプトを 36500 日保持しており、実質的に無期限です。何らかの統合処理を回したいなら、~/.claude/settings.json の値を、再確認するつもりのノートの古さより十分大きくしてください。

json
{
  "cleanupPeriodDays": 365
}

コストは 2 つあります。活発なプロジェクトではトランスクリプトがどんどん溜まりますし、そこにはセッションを通ったものがすべて入っています。ツールの結果、ファイルの内容、出力されたシークレットもです。これを読む処理は直近の範囲だけをコピーし、終わったらコピーを消すべきで、下のスクリプトはその両方を行います。

Claude Code のメモリ指示には何を書くべきか:メモリの目的か、使い方のルールか?

目的です。Anthropic ははっきり書いています。「Claude にはメモリが何のためにあるかを伝え、使い方は伝えないほうがうまくいくことがわかりました」。チームが「失敗だけを記録する」という固定ルールを加えると、Opus 5 はメモリによる伸びのうち 0.234 を、Opus 4.7 は 0.150 を失いました。Sonnet 4.6 が失ったのは 0.020 だけで、もともと失うほどの伸びがなかったからです。Sonnet 4.6 に効いたのはメモリを読んで仮説を検証させるプロンプトで、スコアはほぼ倍になりました。

Claude Code に組み込まれた auto memory の指示は両方が混ざっています。目的(将来の会話で役立つことを保存する)と、ルール(4 種類のノート、コードから導けることは保存しない、CLAUDE.md にすでにあることは保存しない)が並んでいます。CLAUDE.md に何かを足してもこのデフォルトは消えないので、動かせるのは何を足すかだけです。証拠が指しているのは、形式のルールをもう 1 つ増やすことではなく、目的についての一文です。たとえばプロジェクトの CLAUDE.md にこう書きます。

markdown
## Memory
Memory exists so a future session repeats nothing I already corrected and
keeps what was tested here. Before acting on a memory note, check it against
the code or a command; if they disagree, trust the check and fix the note.

CL-Bench の結果はデータベースタスクのもので、Claude Code のセッションではありません。そのまま当てはまるかは、測定結果ではなく妥当な賭けとして扱ってください。2 文目はドリーミングの安上がりな半分です。ノートは使われる瞬間に検証され、それは誤ったノートが実害を生む瞬間でもあります。とはいえ、モデルが読み飛ばすかもしれない一文であることに変わりはなく、それは このファイルに書くあらゆるルールの限界 です。次に紹介する処理は、エージェントが確認を忘れないことに頼りません。

Claude Code のメモリに対して自分でドリーミングを回すには?

まずメモリフォルダを git で管理します。採用した変更はコミットになり、悪い変更は revert で戻せます。次にフォルダと直近のトランスクリプトをスクラッチディレクトリにコピーし、ヘッドレスの claude -p にノートとトランスクリプトを照合させてコピーだけを書き直させ、diff を読みます。ここで一番重要な Anthropic の dreams の性質、つまり入力に一切手を加えないという点をそのまま再現しています。

shell
#!/usr/bin/env bash
# Local dream pass: check a copy of Claude Code auto memory against recent transcripts.
# Usage: dream.sh <full path to ~/.claude/projects/<project>> [days, default 14]
set -euo pipefail
[ $# -ge 1 ] || { echo "usage: dream.sh <project dir> [days]" >&2; exit 64; }
PROJ=$(realpath -e -- "$1"); DAYS="${2:-14}"
[[ "$DAYS" =~ ^[1-9][0-9]*$ ]] || { echo "days must be a positive integer" >&2; exit 64; }
[ -d "$PROJ/memory" ] || { echo "no memory/ in $PROJ" >&2; exit 66; }
WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT    # transcripts never outlive the run
OUT="${OUT:-$PWD/dream-$(date +%F)}"; mkdir -p "$OUT"
cp -r "$PROJ/memory" "$WORK/memory"
# top-level transcript files modified in the last $DAYS days
find "$PROJ" -maxdepth 1 -type f -name '*.jsonl' -mtime -"$DAYS" -exec cp -t "$WORK" -- {} +
ls "$WORK"/*.jsonl > /dev/null 2>&1 || { echo "no transcripts in the window" >&2; exit 65; }
cd "$WORK"
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 claude -p "$(cat <<'PROMPT'
./memory is an agent's memory. The *.jsonl files here are recent session transcripts.
Memory exists so a future session repeats nothing the user already corrected and keeps what was tested.
Check every note in ./memory against the transcripts. Where a transcript contradicts a note,
rewrite the note to match the latest evidence. Where a correction or tested rule appears in the
transcripts and no note holds it, add one and index it in ./memory/MEMORY.md. Leave notes the
transcripts do not mention unchanged. Edit only files under ./memory. Then write ./CHANGES.md:
one line per change, with the transcript file and the line that justifies it.
PROMPT
)" --restricted --tools "Read,Grep,Glob,Edit,Write" --permission-mode acceptEdits \
   --no-session-persistence < /dev/null > "$OUT/run.log" 2>&1
rc=0; diff -ruN "$PROJ/memory" "$WORK/memory" > "$OUT/memory.diff" || rc=$?
[ "$rc" -le 1 ] || exit "$rc"
cp -r "$WORK/memory" "$OUT/memory"; cp "$WORK/CHANGES.md" "$OUT/" 2>/dev/null || true
echo "review: $OUT/memory.diff  $OUT/CHANGES.md  (proposed files in $OUT/memory)"

安全性を担っているのはプロンプトではなくフラグです。--restricted はファイル系ツールを作業ディレクトリ、つまりスクラッチのコピーに閉じ込め、--tools でコマンドを実行できるツールは 1 つも残しません。--no-session-persistence は、この処理自身のトランスクリプトが保存されるのを防ぎます。保存されれば、読み込んだばかりの内容すべての新しいコピーが新しいプロジェクトフォルダにでき、保持期間の設定どおりに残ってしまいます。CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 は処理自身がメモリを書くのを止めます。trap はスクリプト終了時にコピーしたトランスクリプトを消し、diff、提案されたファイル、CHANGES.md は確認用に $OUT に残ります。知っておくべき制限が 2 つあります。範囲は「直近 N 日に更新されたファイル」なので、昨日再開した長いセッションは丸ごと入ります。また、各セッションフォルダ内のサブエージェントのトランスクリプトは含まれません。この処理は他の実行と同じように課金され、読むトランスクリプトの量に応じて増えるので、最初は数日分から始めてください。

Claude Code 2.1.319 で、実際のメモリではなく合成した環境に対して実行しました。ノートは 2 つ(テストは npm test で実行し、ローカルの Redis が必要。staging は staging ブランチからデプロイ)で、トランスクリプトは 11 月 1 日付の 4 行です。その中でユーザーは、プロジェクトは先週 pnpm に移行し Redis はもう使っていないと言い、エージェントは 214 件のテストが通ったと報告しています。3 回の実行すべてで、処理はテストのノートを書き換え、デプロイのノートには手を付けませんでした。どのトランスクリプトもデプロイに触れていなかったからです。うち 2 回は以前のバージョンのスクリプトによるもので、日付も書き加えました。以下はその 2 回目です。

diff
-Run tests with `npm test`; the suite needs a local Redis on port 6379.
+Run tests with `pnpm test` (project moved from npm to pnpm in late Oct 2026; `npm test` is outdated).
+No Redis needed: tests use an in-memory fake. Verified 2026-11-01: 214 tests passed.

欲しかった結果そのものですが、その中の 1 点が、フォルダを確認せずに書き戻すのではなく diff を読むべき理由になっています。「Late Oct 2026」はトランスクリプトにありません。ユーザーが言ったのは「先週」で、処理がそれを独自に日付へ変換しました。次の行の日付とテスト件数はトランスクリプトにありますが、この月はありません。もっともらしい推測ではありますが、同時に、何か月も事実として扱われるファイルに入った、出典のない新しい主張でもあります。CHANGES.md はこの根拠として 3 行目を挙げていましたが、3 行目にそんな内容はありません。3 回目の実行では日付なしでノートが書かれました。確認し、手を入れ、採用するファイルを手作業でコピーして戻し、コミットしてください。

ドリーミングでもエージェントメモリの何は直せないのか?

トランスクリプトだけを見る処理には、どのセッションも目撃していない変化は見えません。直せるのはログと矛盾するノートで、Mt. Moon のノートは同じプレイの中で誤りが露呈する種類のものでした。チームメイトのコミットで変わった依存関係や、会議で決まったことは、どのトランスクリプトにも 1 行も残らず、処理は古いノートを以前と同じ自信で残し続けます。読めるメモリもやはりメモリである と書いたとき、欠けていたのは、世界が動いたときにセッションの外から届くシグナルでした。ログとの照合はそれを補ってくれません。Anthropic の wiki のパイプラインは主張をソース文書とも照合しており、ローカルの処理でもリポジトリや git log を読ませることはできます。トランスクリプトとの照合で変わるのは、誰がそれを行うかです。タスクの途中のエージェントではなく、独立した 1 ステップになります。Anthropic は、これには計算コストがかかり、すべてのメモリを継続的に検証するのは現実的でないかもしれないと率直に認めています。

ドリーミングが正しく捉えているのは書き込みの形です。入力ストアには手を付けず、出力は採用するかどうかを選べる別のストアになります。これは、文書としてのメモリに必要だと論じたバージョン付きの記録そのものです。ローカルでは、処理の前の git コミットがそれに当たります。同じ記事は、あのエッセイのもう 1 つの論点、つまり誰がどのページに書いてよいかについても証拠を出しています。合併条件を 1 つの共有テーブルに書き込んだエージェントは 57 件中 41 件の事実しか保てませんでしたが、契約ごとに 1 ページを設け、条件を直接引用した構成では 57 件すべてを保てました。

表現層もノートと同じくらい疑ってかかるべきです。重みは 膨らむファイルをそもそも持たない ことでこの問題を避けていますが、その代わり別のところで代償を払っています。ソースより大きくなった wiki は、作られた目的の質問でどれだけ高いスコアを出しても、何も抽象化していません。Claude Code 向けに作るなら、隣に検索を置き、インデックスと同じ基準で測ってください。読み込みにどれだけかかるか、そして一度も読まれない部分がないかです。

MEMORY.md が 200 行に近づいているなら、削るのが当然の手で、それで問いの半分には答えられます。インデックスは制限内に収まります。ただ、どの項目がまだ正しいかはわかりません。その問いには証拠が必要で、デフォルトの設定ではその証拠は 1 か月で削除されます。次の削除が来る前に cleanupPeriodDays を引き上げ、照合できるトランスクリプトが残っているうちに削ってください。

01 / ディスカッション

コメント欄はありません。議論は X で行っています。

Max Nardit@mnardit

02 / ほかの記事

  1. バイブコーディングとエージェンティック・エンジニアリングの違い: セッションが終わった後もコードを検査し続けるもの

    Simon Willison は、コードをレビューし、それを説明できる人がいるかどうかで線を引きます。ただ、そのレビューは一度きりで、チャットが閉じた後もコードは変わり続けます。エージェンティック・エンジニアリングが名前を言い換えただけのものでないなら、違いは、誰も見ていないときに動き、しかも意図した理由で失敗するのを確かめたチェックにあります。

  2. Google の SLEEP フレームワークとエージェントの記憶ファイル: 重みはコンテキスト税をなくすが、アドレスも消す

    記憶を重みに統合することは、呼び出しのたびにメモを読み直すコストへの妥当な解決策です。ただし同時に、記憶に対するオペレーターのあらゆる操作に必要なもの、つまり指し示せる対象も失われます。SLEEP では昨夜の分ですら取り出せるブロックになっていないため、ロールバックの単位はチェックポイントになり、忘れるコストは忘れる対象の古さとともに増えます。重みの記憶を安全に運用できるのは、保管した記録から作り直すキャッシュとして扱う場合だけです。

  3. Claude Code の MCP Tasks と elicitation: 誰も答えないかもしれない質問

    Claude Code は長いジョブを MCP サーバーに渡し、あとから状況を確認するようになりました。渡した時点で、ジョブはもうクライアントのものではありません。バックグラウンドに移ったあとは時間制限がなく、終了時のキャンセルもなく、実行中のタスクからの質問にはターミナルの前にいる人しか答えられません。期限と後片付けはサーバー作者の仕事になります。