Claude Code に第二の脳を持たせた — hook でセッションの記憶を自動蓄積する

目次

3ヶ月ほど前に Notion + Linear をやめて Obsidian に全部まとめた

そのとき「Claude Code との相性がいい」と書いた。ローカルの Markdown ファイルだから、Claude Code で自由に操作できる、と。

その話の続き。Obsidian に器を作ったあと、次のステップとして「Claude Code がその vault に自分で記憶を書くようにした」。

問題

Claude Code はセッションをまたいで記憶を保持しない。

自分で困った実例がある。前回のセッションで「こういう設計方針で進める」と合意したはずなのに、次のセッションで同じ議論を振り出しから繰り返す、というパターン。最初はそういうものだと思っていたが、同じ説明を何度もする羽目になると、積み重ねがゼロになっていく感覚がある。

Claude Code 側に非はない。仕様通りの動作だ。

じゃあ、その「記憶」を外に書き出して、次回のセッション開始時に読ませればいいのでは、という発想になる。

作った仕組み

3層構成になっている。

L0(生ログ): ~/tako3_sessions/<プロジェクト名>/YYYY-MM-DD-<スラッグ>.md

毎回のセッション終了時(Stop hook)に、会話の内容が自動で保存される。90日で3000件以上ある。検索はできるが量が多い。

L2(蒸留): ~/Obsidian/projects/<canonical>/memory.md

生ログから「次に役立つ情報だけ」を抜き出した要約。技術的な決定、重要なエラーとその解決、次回への引き継ぎ。作業ログの羅列は書かない、というルールを明示的に設けている。

横断カタログ: ~/Obsidian/projects/_memory_index.md

全プロジェクトの1行サマリーを集めたテーブル。読者は AI、人間は見ない。

| project | summary | updated |
|---------|---------|---------|
| tako3.ch | 個人ブログ。Astro 5 + Cloudflare Pages | 2026-06-24 |
| umidesign_2026 | コーポレートサイト。Astro + CF Pages | 2026-06-24 |

名寄せ: ~/Obsidian/projects/_canonical.json

同じプロジェクトが複数の表記で参照される問題の解決。"tako3ch": "tako3.ch" のように正規名にマッピングすることで、~/tako3_sessions/tako3ch/~/tako3_sessions/tako3.ch/ が同じ memory.md に紐づく。

hook の流れ

実際に何が起きているかというと、こういう流れになっている。

セッション終了時(Stop hook)

memory-distill.sh が起動する。やることはシンプルで、pending ファイルを書き出すだけ。

~/.local/state/pending-memory/tako3.ch.pending.json

中身はこういうもの。

{
  "canonical": "tako3.ch",
  "memory_file": "~/Obsidian/projects/tako3.ch/memory.md",
  "archive_file": "~/Obsidian/projects/tako3.ch/memory_archive.md",
  "memory_index": "~/Obsidian/projects/_memory_index.md",
  "date": "2026-06-24"
}

次のセッション開始時(SessionStart hook)

session-start-memory.sh が起動する。pending ファイルがあれば、その中のファイルパスを使って「以下のタスクを最初のメッセージに応答する前に実行してください」という指示を、additionalContext として Claude に注入する。

あわせて memory.md の全文と _memory_index.md の全文も注入する。

実際の蒸留(memory.md への書き込み)は、次のセッションの Claude 自身が実行する。

claude -p を使わない設計

ヘッドレス呼び出し(claude -p)でバックグラウンドに蒸留させる案は早い段階で除外した。

Max サブスクリプションの対話セッションは定額だが、claude -p のヘッドレス呼び出しは従量課金になる。毎セッション末に自動で claude -p を走らせると、コストが積み上がる。

今の方式だと、蒸留の主体は対話中の Claude 自身なので、追加コストはゼロ。

バナー地獄からの脱出

最初は別の方式で作っていた。

Stop hook で {"decision": "block"} を返すと、セッション終了時に Claude がブロックされてそのまま蒸留を書く、という動き。その場で記憶を書けるので理論上はきれいな設計だった。

ただ、毎回セッションが終わるたびにバナーが出て「蒸留を実行してください」というメッセージが表示される。短い確認作業でも、git 操作でもバナーが出る。鬱陶しくなってきた。

現在の方式(pending ファイル + 次回 SessionStart 注入)に変えたのはそれが理由。memory-distill.sh.bak として旧版が残っている。

旧方式現方式
Stop hook で decision: blockStop hook で pending ファイルを置くだけ
その場で蒸留(バナー表示)次回 SessionStart で注入・蒸留
毎セッション末にバナーが出るバナーなし

設計の肝

memory.md に書く情報について、明示的にルールを設けている。

  • 書く: 技術的な決定、採用したアーキテクチャ、重要なエラーと解決策、次回への引き継ぎ、ユーザーの好みとフィードバック
  • 書かない: 作業ログの羅列、1回しか発生していない一時的な問題、CLAUDE.md に既に書いてあること

行数が 200 行を超えたら、古い項目を memory_archive.md に移す。本丸の memory.md は「今すぐ役立つ情報」だけに保つ。

_memory_index.md の読者は AI だという前提を明確にしている。人間が読むためのものじゃない。横断カタログとして Claude に注入して、別プロジェクトの話が出たとき即座に参照できる状態にすることが目的。

正直に書く

まだ完璧ではない。

蒸留が一度も完走していないプロジェクトもある。 pending ファイルは立つが、次のセッション開始時に「蒸留してください」という指示を受け取ったあと、別の話題を優先するなどして蒸留がスキップされることがある。今の tako3.ch がそれで、pending は残っているが memory.md はまだ空。

2系統のメモリが並存している。 hook + Obsidian の蒸留系統と、Claude Code 標準のプロジェクトメモリ(~/.claude/projects/.../memory/、failure-learning や feedback-workflow が書く場所)は別物で、自動的には統合されていない。

hook のスクリプト本体は dotfiles に入っていない。 settings.json の配線だけ chezmoi で管理していて、スクリプト本体は ~/.claude/hooks/ に直置き。新しい環境に移行するときに手動でコピーが必要になる。(これは近いうちに直したい)

まとめ

完璧なシステムではないが、「前回合意した設計を忘れた」「同じ説明を3回した」という状況はほぼなくなった。

プロジェクトが増えるほど、この仕組みの恩恵を感じる。Obsidian に器を作ったことで、Claude Code が書いた記憶をそのまま自分のノートとしても参照できる。

次は hook スクリプトを chezmoi に取り込んで、環境移行でも一発で復元できるようにするつもり。