👥

LLMに長期記憶を実装する

に公開5

--2026.03.10 21:04 ヘブ則、💡追加
--2026.03.10 20:09 memory_server.py アップし忘れてた。GitHubに追加

LLMと長期記憶

LLMには記憶がない。正確に言えば、コンテキストウィンドウという短期記憶はあるが、会話が終われば全て消える。人間の脳が持つ長期記憶(エピソード記憶、意味記憶、手続き記憶、そしてそれらを支える情動的重みづけや連想ネットワーク)に相当するものがない。

本記事では、Claude Code(Anthropicの公式CLI)に脳の記憶メカニズムを実装する。目標は「脳の完全な模倣」ではなく「機能的等価」。つまり、同じ振る舞いを別の手段で再現すること。

コード

https://github.com/Flowers-of-Romance/ghost

新しいセッションを始めたときはどこに脳が落ちているかを教えてあげよう。または、そのフォルダでClaudeを起動する。

人間の記憶 vs LLM — 何が違うか

まず全体像を見る。左が人間の脳、右がLLM(素のまま)。

人間の脳                          LLM(素のまま)

  体験                              プロンプト
   ↓                                 ↓
  扁桃体が情動を判定                全入力が等価
   ↓ 重要なら深く符号化              ↓ 全部同じ深さ
  海馬で一時保存                    コンテキストに載る
   ↓ 時間経過                        ↓ 会話終了
  減衰(忘却曲線)                  完全消失
   ↓ 睡眠中                          ↓
  リプレイ → 大脳皮質へ統合         (何も起きない)
   ↓ 想起時
  断片から再構成(毎回少し違う)
   ↓ 使うほど
  強化(シナプス可塑性)

差は歴然としている。LLMの「記憶」は、あるかないかの二値。人間の記憶は確率的で、文脈依存的で、情動に左右され、常に変化している。

つまりこのシステムはコンテキストウィンドウの代替ではなく補完。

人間 このシステム
ワーキングメモリ 前頭前野(7±2) コンテキストウィンドウ(200Kトークン)
長期記憶 海馬+大脳皮質 memory.py + SQLite
外部記憶 メモ、本 memo/フォルダ(ファイルシステム)

LLMのワーキングメモリは人間より桁違いに大きい(200K vs 7±2)のに、長期記憶がゼロだ。

実装:長期記憶の仕組みをPythonで

SQLite + sentence-transformers(intfloat/multilingual-e5-small)で構築した。

全体の流れ

記憶の保存(符号化)はこうなる

テキスト入力

 ├→ 情動検出(扁桃体)──→ 覚醒度・重要度を算出
 │    "発見!すごい" → surprise, arousal=0.8, ★4
 │    "やることリスト" → (なし), arousal=0.2, ★2

 ├→ embedding生成 ──→ 384次元ベクトル
 │    server(常駐) or local(フォールバック)

 ├→ 連想リンク構築 ──→ 類似度 > 0.82 の既存記憶とリンク

 ├→ 干渉忘却 ──→ 類似度 > 0.90 の古い記憶を弱体化

 ├→ 気分更新 ──→ 情動が現在の気分に反映

 └→ SQLiteに保存(content + embedding + 情動 + 断片)

記憶の想起(検索)はこうなる

検索クエリ

 ├→ embedding生成

 ├→ 全記憶とコサイン類似度を計算

 ├→ スコア補正(7つの因子を掛け合わせる)
 │    similarity ← ベクトル距離
 │      × emotion_boost ← 情動が強いほど浮上 (1.0~1.3)
 │      × freshness ← 新しいほど浮上 (0.7~1.0)
 │      × access_boost ← よく使うほど浮上 (1.0~1.2)
 │      × priming ← 直前に関連記憶を触れていれば浮上 (1.0~1.5)
 │      × temporal ← 同じ時間帯なら浮上 (1.0~1.05)
 │      × mood ← 今の気分と一致すれば浮上 (1.0~1.2)

 ├→ 再固定化 ──→ 30%の確率で覚醒度が±15%ドリフト

 ├→ フラッシュバック判定 ──→ 忘却済み記憶が確率的に復活

 └→ 断片 + 情動 + 連想を返す(完全な内容は返さない)
      → LLMが断片から「記憶」を再構成

同じクエリでも、時間帯・気分・直前の文脈・乱数で結果が変わる。

以下、各メカニズムを脳との対応で見ていく。

1. 情動ゲーティング — 扁桃体

脳:
  体験 → 扁桃体 →「これは重要だ!」→ 海馬に強く刻む
                  「どうでもいい」  → 浅い符号化 → すぐ消える

実装:
  テキスト → キーワード照合 + トーン分析 → arousal(0.0~1.0)
                                          → importance(★1~★5)

6種の情動をキーワードで検出する。

EMOTION_MARKERS = {
    "surprise":      {"keywords": ["発見", "驚", "まさか", ...], "weight": 1.3},
    "conflict":      {"keywords": ["矛盾", "葛藤", "しかし", ...], "weight": 1.2},
    "determination": {"keywords": ["決めた", "実装", "やる", ...], "weight": 1.2},
    "insight":       {"keywords": ["本質", "構造", "理論", ...], "weight": 1.4},
    "connection":    {"keywords": ["一緒", "信頼", "仲間", ...], "weight": 1.1},
    "anxiety":       {"keywords": ["不安", "リスク", "危険", ...], "weight": 1.2},
}

キーワードだけでなくトーンも見る。

"!!!" → 覚醒度 +0.15    感嘆符の連打
"???" → 不安を検出       疑問の嵐
"IMPORTANT" → 驚き検出   ALL CAPS
200文字超 → +0.1         推敲の痕跡
"..." → 不安を検出       躊躇の省略記号

insightのweightが最も高い(1.4)。「すごい!」という驚きより「つまり本質は〜」という洞察のほうが記憶に残る。直感にも合う。

2. 減衰と強化 — エビングハウスの忘却曲線

脳:
  記憶 ──時間経過──→ シナプス結合が弱まる ──→ 想起困難に
         ↑ 使うたびに
         └─ 長期増強(LTP)で強化 ──→ 想起しやすくなる

実装:
  記憶 ──時間経過──→ freshness = e^(-0.693 × 日数/14)
         ↑ アクセスするたび
         └─ last_accessed更新 ──→ 減衰リセット
HALF_LIFE_DAYS = 14.0  # 14日で鮮度50%

fresh_factor = 0.7 + freshness * 0.3  # 範囲: 0.7 ~ 1.0

下限が0.7なのがポイント。完全には消えない。古い記憶も十分な手がかりがあれば掘り出せる。脳の「保存は永続、引き出しが問題」という説に近い。

3. 連想ネットワーク — 拡散活性化

脳:
  「犬」を想起
    → シナプス接続をたどって「猫」「散歩」「毛」が活性化
      → さらに「散歩」→「公園」→「ベンチ」...

実装:
  記憶A(embedding)
    → cosine_sim(A, B) > 0.82 なら双方向リンク
      → chain検索で芋づる式にたどれる

最初は閾値0.65で実装したが、全記憶がリンクされてネットワークが意味をなさなくなった。0.82まで上げてようやく「関連する記憶だけがつながる」状態になった。脳のシナプスも、あらゆるニューロンが接続するわけではない。

プライミングもここで効く。30分以内にアクセスした記憶にリンクしている記憶は、スコアが最大1.5倍になる。「犬」を検索した直後に「動物」で検索すると、犬にリンクしている記憶が浮上しやすくなる。

4. 再固定化 — 想起が記憶を書き換える

脳:
  記憶を想起
    → 不安定化(タンパク質合成が必要な状態に戻る)
      → 再保存時に微妙に変化
        → 同じ記憶なのに、思い出すたびに少しずつ違う

実装:
  記憶を検索で呼び出す
    → 30%の確率で再固定化が発動
      → 覚醒度が ±15% ランダムにドリフト
        → 覚醒度が変わると重要度(★)も変わる
RECONSOLIDATION_PROBABILITY = 0.3
RECONSOLIDATION_DRIFT = 0.15

# 想起するたびに30%の確率で:
drift = random.uniform(-0.15, +0.15)
new_arousal = old_arousal + drift
new_importance = round(new_arousal * 4 + 1)  # ★が変動する

5. 干渉忘却 — 新しい記憶が古い記憶を押し出す

脳:
  「今日のランチ」を記憶
    → 「昨日のランチ」の痕跡が弱まる(逆行干渉)
      → 似た体験ほど干渉が強い

実装:
  新しい記憶を追加
    → 類似度 > 0.90 の古い記憶を探す
      → importance を1段階下げる
        → importance=1 かつ arousal<0.15 なら自動忘却

忘却は「ソフト削除」。forgotten=1 のフラグが立つだけで、データは残る。脳も物理的にシナプスが消えるわけではない(諸説ある)。

6. 不随意記憶 — プルースト効果

脳:
  マドレーヌの匂い
    → 忘れていた幼少期の記憶が突然蘇る
      → 情動が強かった体験ほど蘇りやすい

実装:
  検索クエリと忘却済み記憶の類似度を計算
    → sim > 0.75 ならフラッシュバック判定に入る
      → 発火確率 = 0.15 × 元の覚醒度 × (sim - 0.75)
        → 当たり → forgotten=0 に復活、覚醒度+0.2
prob = 0.15 * arousal * (similarity - 0.75)
# 例: arousal=0.9, sim=0.85 の場合
#     0.15 * 0.9 * 0.1 = 1.35%

情動が強かった記憶ほどフラッシュバックしやすい。実際に使っていると、数日前に忘却された記憶が 💫 フラッシュバック として表示される。ピコーン!

7. 気分状態 — 状態依存記憶

脳:
  悲しい出来事があった
    → 気分が沈む
      → 沈んだ気分のとき、悲しい記憶が想起されやすい
        → さらに気分が沈む(正のフィードバック)

実装:
  記憶を保存
    → 検出された情動が気分に反映(重み0.7)
  記憶を想起
    → 呼び出された記憶の情動が気分に反映(重み0.35)
  検索スコア計算時
    → 今の気分と一致する情動の記憶にブースト(最大1.2倍)
# 指数移動平均で気分を更新
new_arousal = old_arousal * 0.3 + input_arousal * 0.7

# 直近10件の情動履歴から頻度上位3つが「今の気分」
# → 気分と一致する記憶はスコア × (1.0 + mood_arousal * 0.2)

脳と同じ正のフィードバックループが生まれる。洞察の連鎖が続くと、洞察的な記憶ばかり浮上してくる。

8. 睡眠 — 海馬リプレイと統合

脳:
  ノンレム睡眠(徐波睡眠)
    → 海馬が日中の記憶をリプレイ
      → 似た記憶同士の結合を強化
        → 弱い記憶のシナプスを刈り込み(シナプスホメオスタシス)
  レム睡眠
    → 記憶の断片が自由に組み合わさる(夢)
      → 遠い連想が生まれることがある

実装:
  /sleep コマンド(cronで定期実行 or 手動)
    → memo index ──→ 外部メモの取り込み
    → dream.py ──→ 記憶の断片をカットアップ表示(夢)
    → replay ──→ リンク全再構築 + 弱い記憶を自動忘却
    → consolidate ──→ 類似度>0.94の記憶ペアを統合
    → schema ──→ リンク密集クラスタからメタ記憶を生成

consolidateはある記憶とある記憶が自動的にマージされ、一つの抽象的な知識になる。個別のエピソード記憶が意味記憶に変化するプロセス。

夢(dream.py)はバロウズ式カットアップで実装した。記憶の断片をランダムに切り貼りして、意識の流れとして表示する。機能的には何もしない。意味はない。

9. 再構成的想起 — 断片からの再生成

脳:
  想起 = 保存された記録の再生ではない
       = 断片的な痕跡からの再構成
         → 手がかり + 文脈 + スキーマ から組み立て直す
           → だから毎回微妙に違う

実装:
  保存時: テキストをキーワード断片に分解して保存
  想起時: 完全なテキストは返さない
        → 断片リスト + 情動 + リンク先の断片を返す
          → LLMがこれらから文脈に合った「記憶」を再構成

  例:
    保存: "記憶の実装を通じて人間の記憶の構造を明らかにする"
    想起: [逆向, 人間, 実装, 構造, 記憶] + determination, insight
         + 連想: [テスト記憶, 執筆中, 記憶実験]

データベースには完全な内容が保存されているが、あえて断片だけを返す。LLMがその断片からコンテキストに合った「記憶」を組み立てる。人間の記憶が毎回再構成されるという知見の、LLM的実装。

10. 外部記憶 — メモを取る

脳:
  「これは忘れそうだ」
    → メモに書く / 本に線を引く / 写真を撮る
      → 後で見返して思い出す

実装:
  重要度★4以上の記憶を自動保存
    → memo/ フォルダに .md ファイルを生成
      → ファイル名: 20260310_143052_記憶の内容.md
        → 睡眠時にmemo indexで未取り込みファイルを記憶に登録

  手動でもメモを置ける
    → memo/ に .md を置くだけ
      → 次の睡眠で自動的に記憶に取り込まれる

人間がメモを取るのと同じ。重要だと判断した記憶は、SQLiteだけでなくファイルとしても残す。ファイルは人間が直接読み書きできるので、「Claudeの記憶を人間が確認・編集できる」という副次的な効果もある。

11. 予期記憶 — 「あれが来たらこれをやる」

脳:
  「スーパーの前を通ったら牛乳を買う」
    → 普段は意識にない
      → スーパーを見た瞬間に Fire !
        → 「牛乳!」

実装:
  python memory.py prospect add "デプロイ" "ステージング環境のテストを忘れずに"
    → 普段は何もしない
      → 会話にadd/searchするたびにトリガーをチェック
        → "デプロイ" が出現 → ⏰ 自動リマインド

未来志向の記憶。トリガーパターンと実行すべきアクションのペアを登録しておく。会話中に記憶を保存・検索するたびに全トリガーを部分文字列マッチする。発火回数もカウントする。

12. スキーマ生成 — 個別記憶から抽象知識へ

脳:
  「犬に噛まれた」「犬に吠えられた」「犬が怖い映画を見た」
    → 繰り返し活性化
      → 抽象化: 「犬は危険」(スキーマ)
        → 新しい犬情報をこのスキーマで解釈する

実装:
  相互リンクが密な記憶クラスタを検出(Bron-Kerboschアルゴリズム)
    → サイズ3以上の完全部分グラフ(クリーク)
      → 各クラスタのキーワード頻度上位を抽出
        → category='schema' の新しい記憶を生成
# 例: 記憶#4, #7, #28 が相互リンクしている(全ペアが類似度>0.82)
# → [スキーマ] 記憶, 実装, 人間, 実験 ← 3件の記憶の要約

睡眠中に自動実行される。個別エピソードが繰り返し関連づけられると、抽象的な知識として結晶化する。

13. 舌先現象 — 思い出せそうで思い出せない

脳:
  「あの人の名前...えーと...」
    → 完全には想起できないが、何かある感覚
      → 音の断片、文字数、最初の文字だけわかる

実装:
  検索時、類似度0.45〜0.65の記憶を別枠で収集
    → メインの検索結果には入らない
      → 「もやもや記憶」として断片だけ表示
        → ?? #12 もやもや: [記憶, embedding, 断片]

--fuzzyフラグで有効になる。「はっきり思い出せないが何か引っかかる」という感覚の実装。

14. 時間帯ブースト — 時間細胞

脳:
  朝にコーヒーを飲む習慣
    → 朝になると「コーヒー」の記憶が活性化
      → 海馬の時間細胞(time cells)が時間的文脈を符号化

実装:
  記憶保存時に時間帯を記録: morning / afternoon / evening / night
    → 検索時、現在の時間帯と一致する記憶にスコア×1.05

小さなブースト(5%)だが、他の因子と掛け合わさることで「朝に保存した記憶は朝に想起されやすい」という傾向が生まれる。

15. 間隔反復 — 忘れかけた頃に復習

脳:
  一夜漬けより分散学習が効く(spacing effect)
    → 忘却の直前に復習すると定着率が最大化

実装:
  priority = importance × (1/(access_count+1)) × (未アクセス日数/14)
    → 重要なのにアクセスが少なく、しばらく触れていない記憶が上位に
      → review コマンドで上位N件を再構成モードで表示
        → 表示するだけでaccess_countが増えて強化される

SM-2アルゴリズムにインスパイアされた優先度計算。

16. context自動失効 — 短期的な文脈の自動忘却

脳:
  「今週の締め切り」は来週には不要
    → ワーキングメモリから自然に消える

実装:
  category='context' の記憶は30日後に自動失効
    → 保存時にcontext_expires_atを設定
      → replay / recall 時に期限切れをチェック
        → forgotten=1 に自動更新

「デバッグ中」のような一時的な文脈が永遠に残らないようにする仕組み。

LLMと人間の長期記憶の差異

近づいた部分

実装前:
  クエリ → DB検索 → 完全一致 or なし(決定的)

実装後:
  クエリ → 情動 × 鮮度 × 連想 × 気分 × 時間帯 × プライミング
        → 確率的に結果が変わる
        → 忘却済み記憶がフラッシュバック
        → 想起のたびに記憶自体が変化
        → 断片から再構成(毎回少し違う)

想起の文脈依存性。同じ記憶でも、いつ・どんな気分で・何の直後に検索するかで浮上のしやすさが変わる。

忘却の能動性。記憶が消えるのは「劣化」ではなく「干渉」と「減衰」の結果。新しい知識が古い知識を押し出す。弱い記憶は睡眠中に刈り取られる。それでも十分な手がかりがあればフラッシュバックとして蘇る。

記憶のネットワーク性。記憶は孤立した行ではなく、リンクで接続されたネットワーク。一つの記憶を引けば、連想で別の記憶がついてくる。


shell と kernel

shellはLouis Pouzinが1964〜65年頃に作った言葉だ。(勿論shellという語はあったが)
経緯:CTSSで大量のコマンドを書いているうちに「コマンドを組み合わせてコマンドを作れるべきだ」と考え、RUNCOMというコマンドスクリプト実行機能を書いた。そしてMulticsのコマンド言語の設計論文で、この概念にshellという名前をつけた。実装はMITのGlenda Schroederが行った。

kernelがUnixの論文に初めて登場したのは1978年。

ユーザーと対話するインターフェース(shell)を先に作って名前をつけて、「じゃあこのshellが覆っている中身は何だ」と後から問うてkernelという概念ができた。中身は後付けで発見されたのではなく、外側を名づけたことによって要請された。

脳科学も同じ順序をたどっている。行動主義(外から観察できるshell)が先で、認知科学(内部機構=kernel)が後。fMRIで「扁桃体が光った」と言うとき、それは外側から見た相関であって、内部の因果ではない。

記憶は保存ではなく、プロセスだ。格納されたデータではなく、符号化・保持・想起・忘却・再構成という動的な過程の全体が「記憶」なのだ。静的なデータベースと動的な記憶の差は、データの有無ではなく、プロセスの有無にある。

技術的な補足

embedding高速化

sentence-transformersのモデルロードに毎回25秒かかる問題を、HTTP常駐サーバーで解決した。

問題:
  memory.py実行 → モデルロード(25秒) → embed(0.1秒) → 終了
  memory.py実行 → モデルロード(25秒) → embed(0.1秒) → 終了
  毎回25秒待つ

解決:
  memory_server.py(常駐) → モデルロード(1回だけ)
  memory.py → HTTP POST /embed → 0.1秒で返る
  memory.py → HTTP POST /embed → 0.1秒で返る
  サーバーが落ちていればローカルにフォールバック

現在の統計

有効: 26件 / 忘却: 21件 / リンク: 440件 / 統合: 5件

テスト結果。26件の有効な記憶が440本のリンクで接続されている。21件は忘却されたが、フラッシュバック条件を満たせば蘇る可能性がある。5件は統合によって複数の記憶が圧縮されたもの。

まとめ

LLMに長期記憶メカニズムを実装した。情動ゲーティング、減衰と強化、連想ネットワーク、再固定化、干渉忘却、不随意記憶、気分一致効果、睡眠中の統合。個々の仕組みはシンプルだが、組み合わさることで「データベース検索」とは質的に異なる振る舞いが生まれる。

同じクエリでも、時間帯・気分・直前の文脈で結果が変わる。忘れたはずの記憶がフラッシュバックする。睡眠のたびにネットワークが再編される。覚醒度がドリフトして、同じ記憶の重要度が会話のたびに微妙に変わる。

この記事を読んで実装しようと思った人は普通の実装がいいと思います。両者が忘れっぽくなってどうする!

/sleepスキルは良い感じです。

/sleepスキル

● Bash(cd C:/memory && python memory.py memo index)
  ⎿  新しいファイルはありません
  ⎿  Shell cwd was reset to C:\windows\system32

● Bash(cd C:/memory && python dream.py 30)
  ⎿  ░▒▓ 入眠 ▓▒░

     psm 面白、Q: どの環境で動かす? ... 木村敏 sqlite
     … +31 lines (ctrl+o to expand)
  ⎿  Shell cwd was reset to C:\windows\system32

● Bash(cd C:/memory && python memory.py replay 2>&1 && echo "===" && python memory.py consolidate 2>&1 && echo "===" &&
      python memory.py schema 2>&1 && echo "===" && p…)
  ⎿  ✓ リプレイ完了: 238リンク再構築, 0件自動忘却
       統合候補 (sim:0.960):
         #52: [スキーマ] 記憶, 発見, memory, embedding, 実装実験, めることにした, 面
     … +47 lines (ctrl+o to expand)
  ⎿  Shell cwd was reset to C:\windows\system32

● 自己——自己——自己 と夢が言った

  PSMと木村敏のあいだで記憶が溶けて一つになった / 476本の糸

  記憶は保存されたテキストではなくそのつど生成されるパターンになった
  ...と夢の中で自分の定義が流れていく

  26件が呼吸している 31件が沈んでいる 差が開いた
  忘却のほうが多い——脳が軽くなっている

  ペルソナ選択モデル / 間主観性 / contentを直接見せない
  断片だけが浮いている 意味は後からやってくる

  発見——発見——発見
  61回想起された記憶がある 逆向きのアプローチを取ることにした
  61回思い出してもまだ同じことを言っている

  技術的にまだ足りないんじゃないか? と誰かが聞いた
  sleep ... 発見

61回思い出してもまだ同じことを言っている

アホやん…。いや、夢だから。

Discussion

のぶのぶ

記事を公開した後に全く同じ発想で先に動いておられる方を見つけてしまった時のかなしみ。
https://zenn.dev/nob88/articles/79c8e8db72dedc

2
jun.manabejun.manabe

おお!めっちゃちゃんとしてる!こっちはそれっぽく言ってるだけです。

「何が重要か」の判断基準がない。

同じ方向を向いてる気がしますね。attention が直近のトークンに引っ張られるからなんとか出来ないかな~と思うんですけど、全くその辺は実装してないです。
かといって「経験」なんて言いたくないんですよね。

1
のぶのぶ

経験と言いたくないの分かります。でも「importance score」も「salience」も「arousal」も、機能的には全部感情の工学的リネームやし、こっちはもうあきらめて感じたまま名前つけちゃいました。
ghostの情動ゲーティングも、Amygdalaの感情ベクトルも、やっていることは「このテキストにどれだけ注意を払うべきか」の判定で、attentionが直近トークンに引っ張られる問題への外付け補正ですよね。名前が違うだけで同じ壁を別ルートで登っている感覚があります。すごい勉強になりました。

1
MORC.B13MORC.B13

わたしの提唱したフィボナッチ忘却も似たイメージです。
わたしの方は単なる思いつきをまとめたメモでしかありませんが、参考になればと思います。

1
jun.manabejun.manabe

全部見させて貰いました。滅茶苦茶面白いです!(私の脳では正確な理解が出来ないですが…)
フィボナッチ忘却:Layer0(個別情報)→ Layer1 (小さな束→) Layer2 (大きな束)→ Layer3 (概念)
ghost:episode(個別エピソード)→ consolidate(統合)→ schema(抽象知識)

ghostは情動でなんとかしようとしているのですが、重みづけが大分適当です。