1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kagura Memory Cloud で LLM の長期記憶を海馬と大脳皮質に分ける

1
Last updated at Posted at 2026-05-20

はじめに — 1,494 個のメモリで、edge の 87% が消えていた

Kagura Memory Cloud は私が個人で開発している MCP (Model Context Protocol) ベースの「LLM 共有記憶」です。Claude, Cursor, Gemini CLI など MCP 対応クライアントから同じ context を recall できるよう、長期記憶を メモリ単位メモリ間の関係 (edge) で構造化して保持します。

ある日、開発用 context (kagura-dev) の状態を確認したら、奇妙な数字が出てきました。

-- 2026-05-19 00:30 UTC 時点
SELECT COUNT(*) FROM neural_memory_edges WHERE context_id = 'abfd654d…';  -- 4
SELECT COUNT(*) FROM sleep_actions a JOIN sleep_reports r ON r.id = a.report_id
WHERE r.context_id = 'abfd654d…' AND a.action_type = 'create_edge';        -- 31

1,494 個のメモリ。Sleep Maintenance が 31 本の edge を作った。なのに残ったのは 4 本。生存率 13%、消失率 87%。残った 4 本もすべて当日生まれの新しい edge で、過去の edge はゼロでした。

これは普通に壊れている。今回はその原因を突き止めて直した話です。直してみたら、結局のところ「LLM の長期記憶は脳と同じく 2 層に分けるべき」という、神経科学では 1995 年に決着していた話に行き着きました。


何が起きていたか — Hebbian 学習だけの世界

Kagura Memory Cloud の neural memory layer は、最初は古典的な Hebbian 学習 で edge を作っていました。「Neurons that fire together, wire together」というやつです。

具体的には、ユーザーが recall(query="...") した時に同時に取れたメモリ同士に edge を張り、回数を重ねるほど edge の重みを上げます。実コードはこんな感じ:

# backend/src/neural/hebbian.py
#
#   Δw_ij ← η · (a_i · C_i) · (a_j · C_j) − λ · w_ij
#
# - η: learning rate
# - a_i, a_j: activation strengths of the co-activated nodes
# - C_i, C_j: confidence / trust scores (poisoning defense)
# - λ: L2 decay coefficient (prevents weight explosion)
# - w_ij: current edge weight, clamped to [0.0, 3.0]

Hebbian は良い性質を持っていて、「一緒に思い出された」「関連付けてユーザーが使った」という事実を 使用頻度のトレース として記録します。これだけで edge ベースの探索 (explore(seed=memory_id)) が動くし、誰かが似た2つを連続でアクセスすれば自動で edge が育つ。

代わりに古い edge は 時間で減衰 させます。これも普通の設計です:

# backend/src/neural/decay.py
#
#   w_ij(t + Δt) = w_ij(t) · exp(−decay_rate · Δt)
#
# After decay, w_ij < prune_threshold rows are removed.

使われない edge は消える。これも妥当に見えました。「1,494 メモリで 87% 消失」が観測されるまでは


N² の壁 — Hebbian がスケールしない理由

問題は edge の作り方が 2 種類混在していた ことです。

Kagura Memory Cloud には Hebbian co-activation とは別に、Sleep Maintenance という夜間バッチがあります。これは context 内のメモリ全体を見渡して、コサイン類似度が高い (≥ 0.5) ペアを LLM に判定させ、「意味的に関連する」と判定された pair に edge を張ります。

ここが落とし穴で、Sleep が張った edge も 同じ neural_memory_edges テーブル に書き込まれ、同じ Hebbian decay の対象 になっていました。

ここで簡単な確率計算をしてみます。context に N 個のメモリがあるとして、「特定ペア (i, j) が一定時間内に同時に recall される」確率はざっくり 1/N² で減ります。N が大きくなるほどあるペアが co-recall される確率はゼロに近づく。

N (メモリ数) 1/N² 直感
100 1 / 10,000 たまに reinforce される
1,000 1 / 1,000,000 ほぼ reinforce されない
1,494 (実測) 1 / 2,232,036 reinforce ほぼゼロ

decay は時間で必ず効くので、reinforce が来ない pair の edge は 必ず消える。そして 意味的に類似しているという事実は、reinforce しなくても変わらない静的な性質 です。それを Hebbian decay にさらしていたのが category error でした。

Sleep が頑張って「この 2 つは意味的に関連します」と発見しても、翌日には消えている。Sleep の労力が全部無駄になっていたわけです。

これに気付いた瞬間、配置すべき設計が見えました。


海馬と大脳皮質 — Complementary Learning Systems の話

神経科学に Complementary Learning Systems theory (McClelland, McNaughton & O'Reilly, 1995) という枠組みがあります。「脳は短期と長期で別の場所を使って記憶している」という有名な仮説です。

海馬 (hippocampus) 大脳皮質 (cortex)
担当 エピソード的連合 (短期) 意味的連合 (長期)
形成 個別の経験で速く形成 経験を繰り返し見て遅く形成
性質 動的・経験依存・上書きされやすい 静的・構造的・上書きされにくい
消失 使われないと数日で消える 内容そのものが意味を持つので残る

Karpathy も最近 LLM の memory を語る文脈で同じ比喩を使っていました。記憶を 「いつ思い出されるか (episodic)」「何の話か (semantic)」 で分けるのは、生物が 5 億年かけて見つけた最適解の一つなんでしょう。

Kagura の問題に戻ると、これと完全に符合します:

  • Hebbian edge = 海馬的: 「最近 co-recall された」という episodic な痕跡。reinforce しなければ消えていい。
  • Sleep が発見する意味的 edge = 大脳皮質的: メモリの内容そのものが類似しているという 静的な性質。reinforce に依存して維持すべきではない。

そして、せっかくなのでもう一種類追加します:

  • Declared edge = ユーザー宣言: 「これとこれは関連する」と人間が create_edge MCP tool で明示的に宣言した edge。これも当然消えてはいけない。

直し方 — origin discriminator の導入

設計は単純です。neural_memory_edgesorigin カラムを足し、3 値で分類します。

ALTER TABLE neural_memory_edges
  ADD COLUMN origin VARCHAR(20) NOT NULL DEFAULT 'hebbian';
ALTER TABLE neural_memory_edges
  ADD CONSTRAINT valid_edge_origin
  CHECK (origin IN ('hebbian', 'semantic', 'declared'));

そして decay は hebbian だけを対象にする:

# backend/src/neural/decay.py
edges_decayed = await edge_repo.bulk_decay_weights(
    user_id, decay_factor, only_origin='hebbian',  # ← この一行が肝
)
edges_pruned = await edge_repo.prune_weak_edges(
    user_id, self.config.prune_threshold, only_origin='hebbian',
)

これで semanticdeclared の edge は 時間で消えない。代わりに月 1 回 semantic_edge_reverify cron が走って、endpoint memory が削除された孤児 edge だけを掃除します (O(edges)、O(N²) ではない、というのが重要)。

origin 誰が作るか 重みの意味 decay 対象
hebbian runtime の recall() co-activation 使用頻度トレース (0.0–3.0) yes (毎晩)
semantic Sleep の edge_discovery コサイン類似度 (0.5–1.0) そのもの no
declared ユーザーが create_edge で宣言 ユーザー指定 (default 1.0) no

Sticky-upsert — 「降格しない」保証

origin を導入したら、もう一つ気をつけるべきことがあります。同じメモリペアに対して時間差で複数の origin が候補になる可能性です。

例えば: あるペアが昨日 Sleep に「意味的に関連する」と判定されて origin='semantic' で edge が張られた。今日たまたまそのペアが co-recall されて、Hebbian の path も発動した。

この時、何も考えずに INSERT … ON CONFLICT DO UPDATE で上書きすると、originsemantic から hebbian降格 してしまう。すると翌日からこの edge も decay 対象になり、また消える。N² の壁が再発する

なので upsert は 片方向に固定 しました:

  • hebbiansemantic, declared への 昇格 は OK (情報量が増える)
  • semantic, declaredhebbian への 降格 は禁止

invariant としてシンプルで、テストも書きやすい。create_or_update_edgeON CONFLICT 節に CASE 式を一個書けば終わりです。


ボーナス: edge の origin と「memory の scope」は別物

おまけの整理として、Kagura には memory レベルの分類も存在します。Memory.scopeworking(短期)と persistent(長期)の 2 値で、Sleep Consolidation phase が working memory を access pattern と LLM 判断で persistent に 昇格 させます。

ここで「working memory は Hebbian 管理になるの?」という質問が出ました。答えは No、独立した 2 軸です:

何の lifetime 誰が管理
Memory.scope memory node の寿命 Sleep Consolidation phase
Edge.origin edge の寿命 DecayManager / semantic_edge_reverify

ただし 間接連動はあります: Hebbian の co-recall が増える working memory は access frequency が上がるので、Consolidation が「これは promote すべき」と判断しやすい。Hebbian は edge 寿命を直接管理しないが、node 昇格の signal を間接供給する。

神経科学アナロジーで言えば、「edge consolidation」(Hebbian → Semantic)「node consolidation」(working → persistent) が別タイムスケール・別メカニズムで動いている 2 階層構造です。


デプロイ後 — 数字で見る効果

実際にこの設計を v0.16.x で deploy して、kagura-dev context のバックフィル (過去メモリに対する 1 回だけの cosine 類似度計算) を走らせました。

SELECT origin, COUNT(*) FROM neural_memory_edges
WHERE context_id = 'abfd654d…' GROUP BY origin;

 origin   | count
----------+-------
 hebbian  |    86
 semantic |  1929

4 本だった edge が、2,015 本 になりました。semantic は decay の対象外なので、明日も明後日も残り続けます。今後 Sleep が走るたびに新しい意味的 edge が積み上がり、hebbian はその上で「最近どう使われたか」のトレースを保ち続ける。

explore(seed_memory) の graph traversal が、ようやくまともな密度のグラフ上で動くようになった、というのが体感です。「explore したら関連メモリが芋づる式に出てくる」状態。


注意点と限界

ひとつ釘を刺しておきたいのは、海馬/大脳皮質の比喩は 教育的な類比 (pedagogical analogy) であって、実装としての主張ではない ということです。Kagura のコードは LSTM や Differentiable Neural Computer のように脳の構造を真似ているわけではありません。「両者の lifetime semantics が、Complementary Learning Systems の 2 系列と対応している」というだけです。神経科学の overclaim はやめておきましょう。

もう一つ、今回の origin は edgeを「どう作られたか」だけで分類しています。「どういう関係なのか」についてはedge_typeという別軸が既に存在し、related_to / depends_on / learned_from などを持ちますが、粒度は粗く、純粋な「因果」「時間的前後」「上位下位」のような分類はまだ持っていません。さらに edge_type にはsemantic_similarity / declared_link のように origin と概念重複する値も残っており、taxonomyの整理は別 issue として継続中です。これを将来どう分けるかは「N² で消える」とは独立した話題なのでここでは扱いません。


まとめ

LLM の長期記憶を扱う時、「最近一緒に思い出された」と「内容として似ている」を区別しないと、N² で scale しません

  • Hebbian decay は episodic な痕跡には正しい挙動。
  • だが意味的近傍は静的な性質で、decay の対象にしてはいけない。
  • これを 1 カラムの origin discriminator で分け、decay loop を hebbian だけに当てる。
  • ついでに sticky-upsert で「降格させない」invariant を確保する。

設計判断としては小さな変更ですが、Sleep が見つけた知識が「翌日には消えている」状況から「ちゃんと積み上がる」状況への転換でした。同じ「記憶が消える」問題を踏んでいる方の参考になれば。

参考リンク

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?