Tsukutta

🧠Claude Codeに長期記憶を持たせる4層アーキテクチャ

「毎回ゼロから説明」をやめた話

Lily2026年6月16日公開
約11分で読めます1

*zenn.dev*

Claude Codeはセッションを跨ぐと忘れます。新しいセッションを開くたびに、プロジェクトの背景・過去に決めたこと・自分の好みを説明し直す。これを数ヶ月続けた結果、「記憶を1つのメモに全部書く」のではなく、鮮度と読み手で4層に分ける設計に落ち着きました。

この記事では、その4層アーキテクチャの設計・実装・運用して踏んだ落とし穴を書きます。最後に「今日30分で真似できる最小構成」を置いておくので、全部読まなくてもそこだけ持ち帰ってもらえれば十分です。

困りごとの正体:記憶には種類がある

「Claude Codeに記憶を持たせたい」と一口に言っても、中身は性質の違う4種類が混ざっています。

  • 実際に何が起きたかの生ログ(改変されては困る。量は多い)
  • 直近数日の作業文脈(すぐ腐る。次のセッションに引き継ぎたい)
  • 確定した事実・好み・方針(腐らない。毎セッション読ませたい)
  • 人間の自分が読み返す整理済み知識(LLM用の形式では読みにくい)

これを1ファイルに混ぜると、生ログが好みを埋もれさせ、古い作業メモが今の判断を汚染します。そこで層を分け、層ごとに「書いてよい主体」を1つに固定しました。

設計:4層と一方向フロー

流れは一方向です。

4層アーキテクチャの一方向フロー図

原則は1つだけ。下流は上流を上書きしない。 権威は常に層(1)で、(2)(3)(4)はそこからの派生です。食い違いが起きたら(1)に照らして直します。

実装

層(1):Stop hookで会話ログをMarkdown化する

Claude Codeはセッションの全文を ~/.claude/projects/ 配下にJSONLで残しています。これをStop hook(セッション終了時に発火するhook)でMarkdownに変換し、追記専用ディレクトリに落とします。

settings.json(抜粋):

text
{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command",
            "command": "python3 ~/knowledge-base/extract_conversations.py" }
        ]
      }
    ]
  }
}

変換スクリプトの骨子(extract_conversations.py の要点だけ抜粋):

text
#!/usr/bin/env python3
"""Claude Code会話ログをknowledge base用のMarkdownに変換する"""
import json
from pathlib import Path
PROJECTS_DIR = Path.home() / ".claude" / "projects"
RAW_DIR = Path.home() / "knowledge-base" / "raw" / "conversations"
def parse_session(jsonl_path):
    messages = []
    with open(jsonl_path, encoding="utf-8") as f:
        for line in f:
            obj = json.loads(line)
            if obj.get("type") not in ("user", "assistant"):
                continue
            # user/assistantのテキストだけ抽出(tool_resultは捨てる)
            ...
    return messages

ポイントは2つ。tool_result(ツール実行結果)は捨てること(量が爆発する割に後から読まない)。そして出力ファイル名を <プロジェクト名>_<セッションID>.md にして、後からgrepで「あの作業いつやったっけ」を引けるようにすることです。

層(2)(3):次のセッションに自動で文脈を渡す

層(2)は公式の remember プラグインに任せています。直近の作業が now.md に入り、夜間のconsolidateで recent.md → archive.md へ熟成されつつ重複排除され、SessionStartで自動的に文脈へロードされます。

⚠️ 層(2)のファイルは手編集禁止です。手で触るとconsolidateが壊れます。

層(3)は「確定した事実だけ」を置く場所です。MEMORY.md を索引にして、1トピック1ファイルで事実を書く。「このプロジェクトのDBはTurso」「危険な操作は実行前に確認を取る」のような、腐らない情報だけをここに入れます。作業ログを混ぜないのがコツです。

層(4):人間用のhot.mdをAPIコストゼロで自動更新

Obsidian Vault側の hot.md には「直近10セッションの表」が自動で入ります。仕組みは単純で、層(1)のファイル名と日時行を正規表現でパースし、マーカーで囲った領域に表を注入するだけ。LLMを呼ばないのでコストはゼロです。

hot.md(注入領域のイメージ):

text
<!-- recent:start auto-updated by update_hot_recent.py -->
| 日時 | プロジェクト | 最初の指示 |
|---|---|---|
| 2026-06-10 09:12 | my-app | レスポンシブ崩れを直して |
<!-- recent:end -->
💡 マーカーで「自動領域」を明示するのは地味に重要です。これがないと人間(自分)がうっかり手編集して、次回の注入が壊れます。

運用して踏んだ落とし穴

落とし穴1:macOSのTCCでlaunchdジョブが黙って死ぬ

これが最大のハマりでした。夜間の自動ジョブをlaunchdで回したところ、**~/Documents 配下のVaultへの書き込みだけが静かに失敗**。エラーも出ません。

切り分けると、launchd配下でも bash の書き込みは通るのに、claude バイナリ・git・python だけが弾かれる。原因はmacOSのTCC(プライバシー保護)で、~/Documents ~/Desktop ~/Downloads はバイナリ単位のFull Disk Accessがないと書けない領域でした。

対策はFDAを付与して回るより、書き込み先を保護領域の外に出す方が堅牢です(バイナリが更新されてもパスが変わっても効く)。Vaultの実体を ~/Documents の外へ移設し、元の場所にはsymlinkを張りました。

落とし穴2:「recentが2つある」を統合したくなる

層(2)の recent.md(LLM用)と層(4)の hot.md(人間用)は内容が被ります。一見冗長で統合したくなりますが、読み手が違うだけで両方とも層(1)由来。どちらかを正にして他方を従わせると、フォーマット要件が衝突して両方使いにくくなります。冗長なまま放置が正解でした。

落とし穴3:壊れたことに気づけない

自動パイプラインは静かに止まります。早期警告のシグナルを決めておくと拾えます。

  • now.md に同一要約が3回以上並ぶ → consolidate遅延の兆候
  • 源泉ログの索引が24時間以上古い → 変換hookが回っていない疑い
  • hot.md のマーカー消失 → 表の注入失敗

うちではこれを1本のヘルスチェックスクリプトにまとめ、全層の死活を1コマンドで確認できるようにしています。

今日30分で真似できる最小構成

4層全部はいりません。層(1)だけで効果があります。

  1. ~/knowledge-base/raw/conversations/ を作る
  1. ~/.claude/projects/ のJSONLからuser/assistantのテキストだけ抜いてMarkdown保存するスクリプトを書く(上の骨子で十分。Claude Code自身に書かせるのが速い)
  1. settings.json のStop hookに登録する

これだけで「全セッションがgrep可能なテキスト」になります。「先週どう直したっけ」が grep -r "レスポンシブ" ~/knowledge-base/raw/ で引けるのは、想像以上に効きます。層(2)〜(4)は、その生ログが溜まって不便を感じてから足せば間に合います。

まとめ

  • 記憶は量ではなく鮮度と読み手で層を分ける
  • 層ごとに書き手を1つに固定し、下流は上流を上書きしない
  • launchd × ~/Documents はTCCで黙って死ぬ。書き込み先を保護領域の外へ
  • まずはStop hook 1本の「生ログ層」から

この環境には他にも、Claudeが自分でスキルを書いて成長するauto-skills運用や、毎朝GitHubを自動巡回して有用OSSを拾うパイプラインが乗っています。続編で書く予定なので、興味があればフォローしてもらえると嬉しいです。

この記事が良かったら

「チップをリクエスト」で著者にチップの受け取り設定をお願いできます

シェア