意外将LLM记忆转化为程序分析:Datalog引擎Lemmalog
意外将LLM记忆转化为程序分析
做LLM Agent记忆和推理的同学必看,这篇把记忆系统当成程序分析来做,用Datalog维护事实依赖,能自动失效错误结论,思路很硬核,值得照着实现一遍。
I accidentally turned LLM memory into program analysis
我意外地将LLM记忆变成了程序分析
[ 28 Aug 2026 ] // JORDY ZOMER // 19 MIN READ
[ 2026年8月28日 ] // 乔迪·佐默 // 阅读时间19分钟
Over the past few months I have been playing around quite a bit with LLM agents, particularly for vulnerability research.
过去几个月里,我一直在大量尝试使用LLM代理,特别是在漏洞研究方面。
They are becoming surprisingly good at navigating large codebases, explaining unfamiliar subsystems and helping explore potential attack surfaces. However, once an investigation starts taking a few hours, I kept running into the same problem: the model would slowly lose track of what we had actually established.
它们在导航大型代码库、解释不熟悉的子系统以及帮助探索潜在攻击面方面变得出奇地擅长。然而,一旦调查开始持续数小时,我总会遇到同样的问题:模型会慢慢失去对我们已确定事实的追踪。
It might suggest an approach that we had already ruled out, forget that an assumption turned out to be false, or confidently continue reasoning from an observation that was no longer valid. Obviously, telling an LLM that something is wrong does not necessarily mean that it will stop believing all of the things that depended on it :)
它可能会提出我们已经排除的方法,忘记某个假设已被证明是错误的,或者自信地基于一个不再有效的观察继续推理。显然,告诉LLM某件事是错误的,并不一定意味着它会停止相信所有依赖于此的结论:)
I initially started looking into memory systems because I wanted to make LLMs more useful for complex vulnerability research and reduce this type of hallucination.
我最初开始研究记忆系统,是因为我想让LLM在复杂的漏洞研究中更有用,并减少这类幻觉。
There are of course already plenty of solutions for giving LLMs memory. Usually this involves storing old conversations or observations somewhere, embedding them, and then retrieving the most relevant pieces whenever the model needs them again.
当然,已经有很多为LLM提供记忆的解决方案。通常这涉及将旧对话或观察存储在某处,进行嵌入,然后在模型再次需要时检索最相关的部分。
This works reasonably well, but there was something about it that bothered me.
这效果还不错,但有些东西让我感到困扰。
During a vulnerability research sesh, I don’t just want the model to remember what we said.
在漏洞研究过程中,我不只是希望模型记住我们说过的话。
I want it to maintain what we currently know.
我希望它能维护我们当前已知的信息。
Imagine that during an investigation we establish the following:
想象一下,在调查过程中我们确定了以下内容:
attacker controls object_a
object_a points to object_b
object_b is a kernel objectattacker controls object_a
object_a points to object_b
object_b is a kernel objectFrom this, we may conclude that the attacker can control a kernel object.
由此,我们可能得出结论,攻击者可以控制一个内核对象。
A normal memory system could store all of these observations and retrieve them again whenever we ask about the exploitability of the bug. The LLM then figures out the same conclusion.
一个普通的记忆系统可以存储所有这些观察,并在我们询问漏洞可利用性时再次检索它们。然后LLM会得出相同的结论。
Great!
太好了!
However, suppose that two hours later we discover in LLDB that object_a does not actually point to object_b, and that our previous observation was based on a wrong assumption.
然而,假设两小时后我们在LLDB中发现object_a实际上并不指向object_b,而我们之前的观察是基于一个错误的假设。
At that point our memory may contain something like:
那时我们的记忆可能包含类似这样的内容:
object_a points to object_b
attacker can control object_b
object_a does not actually point to object_bobject_a points to object_b
attacker can control object_b
object_a does not actually point to object_bNow we retrieve some subset of these memories and hope that the LLM correctly figures out which conclusions are still valid.
现在我们检索这些记忆的子集,并希望LLM能正确判断哪些结论仍然有效。
This started to feel a little familiar to me.
这开始让我感到有些熟悉。
This looks like program analysis
这看起来像程序分析
A lot of the work I normally do involves program analysis.
我平时做的很多工作都涉及程序分析。
When analysing a program, we usually have a bunch of facts about the program and some rules that derive additional facts from them.
分析程序时,我们通常有一堆关于程序的事实,以及一些从这些事实推导出额外事实的规则。
For example, imagine we know:
例如,想象我们知道:
calls(foo, bar)
calls(bar, baz)calls(foo, bar)
calls(bar, baz)We could define a rule stating that if one function calls another function, which itself can reach a third function, then the first function can reach the third function as well.
我们可以定义一个规则:如果一个函数调用了另一个函数,而后者本身又能到达第三个函数,那么第一个函数也能到达第三个函数。
Eventually we calculate a fixed point containing everything we can derive from the program. More importantly, if one of our input facts changes, there are plenty of techniques for updating only the affected results instead of rerunning everything from scratch.
最终,我们计算出一个固定点,包含我们能从程序中推导出的所有内容。更重要的是,如果我们的某个输入事实发生变化,有很多技术可以只更新受影响的结果,而不是从头重新运行所有内容。
This is also exactly what I wanted from an LLM during vulnerability research.
这正是我在漏洞研究期间希望从LLM那里得到的。
If an observation changes, I don’t want the model to reconstruct the entire investigation from a transcript and hopefully notice all of the consequences. I want the affected conclusions to become invalid automatically.
如果某个观察结果发生变化,我不希望模型从转录中重建整个调查过程,并希望它能注意到所有后果。我希望受影响的结论能自动失效。
When looking at the problem from this perspective, I started wondering why we were making the LLM reconstruct its entire state over and over again.
从这个角度看待问题时,我开始疑惑为什么我们要让LLM一遍又一遍地重建其整个状态。
What if we just maintained it?
如果我们只是维护它呢?
And this is how I somehow ended up writing a Datalog engine for LLMs :)
就这样,我不知怎么地最终为LLM编写了一个Datalog引擎 :)
Datalog
Datalog
Before we continue, it is probably useful to briefly explain what Datalog actually is.
在继续之前,简要解释一下Datalog到底是什么可能很有用。
Datalog is a declarative logic programming language. Instead of writing instructions describing how something should be calculated, we describe facts and rules from which new facts can be derived.
Datalog是一种声明式逻辑编程语言。我们不是编写描述如何计算某事的指令,而是描述事实和规则,从中可以推导出新的事实。
For example, we could store the following facts:
例如,我们可以存储以下事实:
controls(attacker, object_a).
points_to(object_a, object_b).
kernel_object(object_b).controls(attacker, object_a).
points_to(object_a, object_b).
kernel_object(object_b).And then define the following rule:
然后定义以下规则:
controls_kernel_object(Attacker) :-
controls(Attacker, ObjectA),
points_to(ObjectA, ObjectB),
kernel_object(ObjectB).controls_kernel_object(Attacker) :-
controls(Attacker, ObjectA),
points_to(ObjectA, ObjectB),
kernel_object(ObjectB).From our existing facts, the engine can therefore derive:
根据我们现有的事实,引擎因此可以推导出:
controls_kernel_object(attacker).controls_kernel_object(attacker).Nothing particularly exciting yet.
目前还没有什么特别令人兴奋的。
However, suppose we later discover that:
然而,假设我们后来发现:
points_to(object_a, object_b).points_to(object_a, object_b).was incorrect.
是不正确的。
If controls_kernel_object(attacker) was derived from that fact, we know exactly which conclusion depends on the observation that just changed, and we can automatically invalidate it.
如果controls_kernel_object(attacker)是从那个事实推导出来的,我们确切知道哪个结论依赖于刚刚改变的观察结果,并且我们可以自动使其失效。
This is considerably nicer than putting all of the old information into a prompt and asking an LLM to hopefully notice the same thing.
这比把所有旧信息放入提示中并希望LLM能注意到同样的事情要好得多。
Lemmalog
Lemmalog
This eventually turned into Lemmalog.
这最终演变成了Lemmalog。
The basic idea is that an LLM should not necessarily be responsible for maintaining its own knowledge. Instead, I split the problem into two parts.
基本思想是,LLM不一定需要负责维护自己的知识。相反,我把问题分成了两部分。
The LLM handles the fuzzy part:
LLM处理模糊的部分:
"LLDB shows that the freed object is later reused
as the destination of the write."
|
v
freed(object_a)
reused_as(object_a, write_target)"LLDB shows that the freed object is later reused
as the destination of the write."
|
v
freed(object_a)
reused_as(object_a, write_target)And Lemmalog handles the deterministic part:
而Lemmalog处理确定性的部分:
facts
|
v
rules
|
v
derived factsfacts
|
v
rules
|
v
derived factsThis means that the LLM is still responsible for understanding natural language, source code, debugger output and all the other messy information that appears during an investigation.
这意味着LLM仍然负责理解自然语言、源代码、调试器输出以及调查过程中出现的所有其他杂乱信息。
LLMs happen to be quite good at this.
LLM恰好在这方面表现得相当出色。
But once that information has been converted into structured facts, we no longer need the model to repeatedly determine all of its consequences. The database can do that instead.
但一旦信息被转化为结构化事实,我们就不再需要模型反复推导其所有后果。数据库可以代劳。
Retractions
撤回
One of the first interesting problems I ran into was removing facts.
我遇到的第一个有趣问题之一就是移除事实。
Adding facts to a Datalog database is relatively straightforward: add the new fact and evaluate any rules which may now produce additional results.
向Datalog数据库添加事实相对直接:添加新事实并评估任何可能因此产生额外结果的规则。
Removing something is a little more annoying.
移除某些东西则稍微麻烦一些。
Take the following example:
以下面的例子为例:
a.
b.
c :- a.
c :- b.a.
b.
c :- a.
c :- b.Here c has two separate reasons for being true.
这里c为真有两个独立的原因。
If we remove a, we cannot simply remove c, because b still provides another derivation for it. However, if we remove both a and b, c should disappear as well.
如果我们移除a,不能简单地移除c,因为b仍然为它提供了另一个推导。然而,如果我们同时移除a和b,c也应该消失。
This turns out to be quite important during vulnerability research, because a conclusion may be supported by multiple observations.
在漏洞研究过程中,这一点非常重要,因为一个结论可能由多个观察结果支持。
For example:
例如:
candidate_3_is_exploitablecandidate_3_is_exploitablemay remain true even if one particular exploit primitive turns out not to work, because there is another independent path to the same result.
即使某个特定的利用原语被证明无效,结论可能仍然成立,因为存在另一条独立的路径通向相同的结果。
So Lemmalog has to keep track of how facts were derived and update their support when something changes.
因此,Lemmalog必须跟踪事实是如何被推导出来的,并在某些变化时更新它们的支持依据。
Conveniently, this also gives us another useful property:
方便的是,这也给了我们另一个有用的特性:
we can ask why something is true.
我们可以询问某事物为何为真。
Why?
为什么?
Imagine we have been running an agent for a few hours while investigating something and it eventually concludes:
想象一下,我们在调查某事时运行了一个代理数小时,它最终得出结论:
candidate_3_is_exploitablecandidate_3_is_exploitableThat is nice, but I would also quite like to know why.
这很好,但我也很想知道为什么。
Because Lemmalog already tracks the dependencies of derived facts, we can ask it for the provenance of a conclusion. For example, we may get something that conceptually looks like this:
因为Lemmalog已经跟踪了推导事实的依赖关系,我们可以向它询问结论的来源。例如,我们可能会得到概念上看起来像这样的东西:
candidate_3_is_exploitable
|
+-- attacker_controls_pointer
| |
| +-- observation_41
|
+-- pointer_reaches_target
|
+-- observation_57
+-- rule_12candidate_3_is_exploitable
|
+-- attacker_controls_pointer
| |
| +-- observation_41
|
+-- pointer_reaches_target
|
+-- observation_57
+-- rule_12If observation_41 later turns out to be incorrect, we know that this conclusion may no longer be valid, and because the database knows this as well, it can remove the affected conclusions automatically.
如果observation_41后来被证明是错误的,我们知道这个结论可能不再有效,而且由于数据库也知道这一点,它可以自动移除受影响的结论。
This was originally mostly necessary to make incremental evaluation work correctly, but it turns out that being able to ask an AI agent why it believes something is quite useful as well :)
这最初主要是为了确保增量评估正确工作,但事实证明,能够询问AI代理为何相信某事物也非常有用:)
It also addresses one of the more annoying failure modes I encountered with LLM-assisted research. Sometimes a model will confidently say something like:
它还解决了我遇到的LLM辅助研究中最令人烦恼的失败模式之一。有时模型会自信地说出这样的话:
更进一步:量化金融体系
看懂新闻只是起点——沿量化金融路径,把它变成能交付的工程能力