powered by TechFeed
表示モード
Deep Dive

LLMの「忘れる・矛盾する」問題を解決しようとしたら、Datalogエンジンを自作することになった話

8月29日、pwning.systemsが「I accidentally turned LLM memory into program analysis :: pwning.systems」と題した記事を公開した。LLMエージェントに「より良い記憶」を持たせようとした実験が、気づけばDatalogエンジンの自作に帰着していた——そんな経緯と実装の詳細が紹介されている。「LLMのメモリ問題はデータベースの問題だった」という結論に至るまでの道筋は、LLMエージェント設計を考える上で実践的な示唆を含んでいる。

8月29日、pwning.systemsが「I accidentally turned LLM memory into program analysis :: pwning.systems」と題した記事を公開した。LLMエージェントに「より良い記憶」を持たせようとした実験が、気づけばDatalogエンジンの自作に帰着していた——そんな経緯と実装の詳細が紹介されている。「LLMのメモリ問題はデータベースの問題だった」という結論に至るまでの道筋は、LLMエージェント設計を考える上で実践的な示唆を含んでいる。


LLMが「忘れる・矛盾する」問題の本質

LLMを使った脆弱性調査では、数時間の調査セッションになると同じ問題に繰り返し直面する。モデルがすでに否定した仮説を再び提案したり、後から覆った観測結果をもとに推論し続けたりする。

既存のメモリシステム——会話履歴やObservationを埋め込みベクトルで保存し、関連部分を都度検索する手法——はそれなりに機能するが、本質的な問題を解決しない。

たとえば以下のような状態を考える。

object_a points to object_b       # 最初の観測
attacker can control object_b     # 上記から導いた結論
object_a does not point to object_b  # 2時間後に覆った

ベクトル検索でこれらを検索すると、矛盾した情報が混在したままLLMに渡される。あとはLLMが「どの結論がまだ有効か」を正しく判断してくれることを祈るだけだ。

これはプログラム解析における古典的な問題と同じ構造だと気づいた。「ファクトが変わったとき、影響を受ける導出結果だけを差分更新する」——この課題はDatalogが長年取り組んできたものだ。


Datalogとは何か、なぜプログラム解析で使われてきたか

Datalogは宣言的ロジックプログラミング言語で、「こう計算せよ」ではなく「ファクトとルール」を記述する。1980年代にデータベース研究から生まれ、その後プログラム解析の分野で広く採用された。SouffleDoopといったツールがポインタ解析・型推論・静的セキュリティ解析に使われているのは、Datalogが「依存関係の追跡」と「差分更新」を構造的に扱えるためだ。ファクトが変化したとき、その影響を受ける導出結果だけを再計算・無効化できる性質は、長時間にわたって状態が変化し続けるプログラム解析ワークフローに適している。そしてそれは、LLMエージェントが行う長時間の調査セッションとも構造が一致する。


Datalogエンジンを自作することになった

そこで作られたのが Lemmalog だ。

アーキテクチャの骨格は明快で、役割を2つに分割する。

  • LLMが担う部分:自然言語・ソースコード・デバッガ出力などファジーな情報を構造化ファクトに変換する
  • Lemmalogが担う部分:ファクトとルールから新たなファクトを導出し、変化があれば差分更新する

たとえば以下のように書く。

controls(attacker, object_a).
points_to(object_a, object_b).
kernel_object(object_b).

controls_kernel_object(Attacker) :-
    controls(Attacker, ObjectA),
    points_to(ObjectA, ObjectB),
    kernel_object(ObjectB).

エンジンはここから controls_kernel_object(attacker) を自動導出する。そして points_to(object_a, object_b) が覆れば、それに依存する結論を自動的に無効化する。LLMに「さっきの観測は間違いでした」と伝えて後はよろしく、ではない。


Retractionとprovenanceが鍵

最も興味深い実装上の問題は、ファクトの削除(Retraction)だ。

たとえば cab の両方から独立に導出されているとき、a を削除しても c は残る。ab の両方が消えて初めて c も消える。これは脆弱性調査において重要で、「あるエクスプロイトプリミティブが使えないとわかっても、別のパスが残っていれば結論は生き続ける」という状況に対応する。

Lemmalogはこのためにファクトがどのように導出されたかの依存関係(provenance)を追跡する。これは差分更新を正しく動かすための機能だったが、副産物として「なぜこの結論に至ったか」を尋ねられるようになった。

candidate_3_is_exploitable
|
+-- attacker_controls_pointer
|   |
|   +-- observation_41
|
+-- pointer_reaches_target
    |
    +-- observation_57
    +-- rule_12

LLMが「このポインタは攻撃者が制御できると既に確認しました」と自信満々に言うとき、その根拠がLemmalogの管理状態に存在しなければ、それはサポートされていない主張だと判断できる。ハルシネーション自体は防げないが、根拠のない結論がサイレントに調査状態に混入するのを防げる。

さらにvalidity interval(有効期間)をファクトに付与することで、「今は無効だが過去に有効だったファクト」を保持できる。「なぜあのとき primitive_a を試みたのか」を後から説明できる。


ベクトルDBとの違い

「ベクトルDBで足りるのでは」という疑問は自然だが、記事ではこう整理している。

  • Retrievalが解く問題:「この質問に関係する過去の情報は何か?」
  • Lemmalogが解く問題:「これまでに学んだことから、今現在何が真か?」

コサイン類似度はファクトが後から覆ったことを知らない。Lemmalogはそれを知っている。記事では両者を組み合わせるのが現在のアプローチとしている。


実際に性能は上がるか

Lemmalogは**MemEval**ベンチマークで評価されている。ファクト抽出にClaude Sonnet(元記事記載のバージョン表記に依拠)を使い、回答モデルはベンチマーク標準のものを使用。なお、MemEvalにはLongMemEvalとLoCoMoの2つのサブセットが含まれるが、元記事でスコアが報告されているのはLongMemEvalのみとなっている。

LongMemEval(102問)での結果は以下の通り。

システム F1
PropMem 0.550
SimpleMem 0.480
Lemmalog 0.463 ± 0.010
OpenClaw 0.244
Full Context 0.222

PropMemやSimpleMemには及ばないが、GPT-4.1にフルコンテキストを渡した場合(F1: 0.197)の2倍以上のF1スコアを達成している。

注目すべきはトークン効率だ。

  • フルコンテキスト:約104,000トークン/質問
  • Lemmalog:約2,700トークン/質問(約38分の1

カテゴリ別では、Knowledge Update(知識の更新)でLemmalog(0.579)がPropMem(0.528)を上回っている。「事実が覆る」ことを正しく扱える構造が、このカテゴリで有利に働いたと考えられる。


LLMのメモリ問題を突き詰めると、結局はデータベースの問題だったというのがこの記事の結論だ。LLMはあくまでファジーな情報を構造化するフロントエンドとして使い、状態管理は別のエンジンに委ねる。プログラム解析の分野で40年以上磨かれてきたDatalogの設計思想が、LLMエージェントのアーキテクチャ問題を解くヒントになるという視点は、エージェント設計を考える上でひとつの実用的な示唆を提供している。

詳細はI accidentally turned LLM memory into program analysis :: pwning.systemsを参照していただきたい。