乐于分享
好东西不私藏

为什么你的 OpenClaw 总是记不住? 记忆系统探索之 MEMORY.md

为什么你的 OpenClaw 总是记不住? 记忆系统探索之 MEMORY.md

大家好,打算和大家聊几篇 OpenClaw 的记忆系统。这个也是很多新接触的朋友最容易感到困惑的地方。也是把自己逐步学习过程中,自己体会到的经验,觉得值得分享的地方和大家聊聊。

OpenClaw 的记忆系统分几个层级和子系统,如果都聊的话,需要分几篇内容来写,这次主要跟大家聊的是 MEMORY.md

这个文件只要你稍微用过一段时间 OpenClaw 一定是见过的,它就在我们的 workspace 根目录上,大家理解上也是最直观的, 所以我们就先从它开始。

当然 MEMORY.md 也可以参与向量索引,不过这个我们放在以后的文章再聊,这次只聊它作为 bootstrap 文件的相关机制。

MEMORY.md

首先,MEMORY.md 这个文件不依赖任何记忆插件就可以运转起来的。 但是大家首先要知道一个点,不能把它当作数据库来用,它是有上限的,默认 20000 个字符,而且还受全局系统 bootstrap 文件长度 60000 个字符的限制,稍后给大家详解。

他和这几个文件放在一起,都叫做系统 bootstrap 文件:AGENTS.md,SOUL.md,TOOLS.md,IDENTITY.md,USER.md,HEARTBEAT.md,BOOTSTRAP.md,MEMORY.md

读取它们的函数是 loadWorkspaceBootstrapFiles, 在代码文件 openclaw/src/agents/workspace.ts, 有兴趣的话大家可以去这里看具体实现。

不过读取出来之后,真正进入模型 prompt 时还有一层字符预算限制。接下来我长话短说,上面几个 bootstrap 文件,默认每个文件最多注入 20000 字符,并且它们加在一起的注入长度默认不能超过 60000 个字符。

而且是按照如下顺序逐个计入 prompt 注入预算:

AGENTS.mdSOUL.mdTOOLS.mdIDENTITY.mdUSER.mdHEARTBEAT.mdBOOTSTRAP.mdMEMORY.md

MEMORY.md 是最后一个被计入注入预算的,意思是什么呢,就是说如果前面几个文件加在一起,已经把 60000 个字符的总预算用得差不多了,那么你的 MEMORY.md 可能根本没机会被注入到这次 prompt 里。

当然,我们可以通过 json 配置文件,来增大这两个阈值, 比如:

{  agents: {    defaults: {      bootstrapMaxChars: 40000,      bootstrapTotalMaxChars: 80000,    }  }}

但是我们也不能无限的增大这个值,这几个 bootstrap 的原理是这样, 他们在一次运行中,会全都作为系统提示词提交给大模型,如果过大,就会增大模型上下文处理的压力。 这个不细讲,只给大家做一个粗算,按照默认的 60000 个字符上限来算,就会占用 15000-20000 个 token 左右。

大家只要知道这一点,超过了限定长度,MEMORY.md 注入给模型的内容会被截断。注意这个截断,不是改写原始 MEMORY.md 文件,也不是做压缩,而是只在本次 prompt 注入内容里保留一部分。截断一旦发生,被截掉的部分在这次模型 run 里就等于模型看不到。

详细的截断规则,我让 agent 助理帮忙整理出来,这个属于大家有兴趣可以看,没时间可以跳过。

举个例子。

如果前面的 AGENTS.md / SOUL.md / TOOLS.md / IDENTITY.md / USER.md / HEARTBEAT.md / BOOTSTRAP.md 合计已经占用了 45,000 字符,那么 MEMORY.md 这次最多只能注入:

min(20,000, 60,000 - 45,000)= min(20,000, 15,000)= 15,000

所以这次模型最多只能看到 MEMORY.md 的 15,000 字符,而不是 20,000 字符。

如果前面的文件已经占用了 59,950 字符,剩余预算只有 50 字符。源码里还有一个最小文件预算限制,小于 64 字符时,后续 bootstrap 文件会直接跳过。这样 MEMORY.md 就可能完全不会被注入。

确定了 MEMORY.md 这次可用的字符预算以后,OpenClaw 会判断 MEMORY.md 的原文长度有没有超过这个预算。

如果没有超过,就完整注入。

如果超过了,就会发生截断。

这里的截断不是摘要,不是压缩,也不是让模型先读一遍再总结。它只是机械地保留开头和结尾,中间删掉。

具体规则是:

最终注入内容 = 原文开头 + 截断提示 + 原文结尾

OpenClaw 会先从当前文件预算里扣掉截断提示和换行符占用的字符,得到真正可用来保留原文内容的预算。这个预算可以理解为 contentBudget

然后它会把 contentBudget 按比例分给头部和尾部:

头部 ≈ contentBudget * 75%尾部 ≈ contentBudget * 25%

这里要注意,75% / 25% 不是指“保留原文的前 75% 和后 25%”。

它指的是:

在本次允许注入的字符预算里,扣掉截断提示以后,剩下的空间按 75% / 25% 分给头部和尾部。

比如,MEMORY.md 原文有 100,000 字符,而这次 MEMORY.md 的注入预算是 20,000 字符。

OpenClaw 不是保留原文前 75,000 字符和后 25,000 字符。那样加起来还是 100,000,没有任何意义。

它实际做的是:

20,000 字符总预算- 截断提示和换行符占用的字符= 可用于保留原文的 contentBudget

假设截断提示和换行符占用了大约 120 字符,那么:

contentBudget = 20,000 - 120 = 19,880

然后:

头部 ≈ 19,880 * 75% = 14,910 字符尾部 ≈ 19,880 * 25% = 4,970 字符

最后注入给模型的内容大概就是:

原文前 14,910 字符[...truncated, read MEMORY.md for full content...]…(truncated MEMORY.md: kept 14910+4970 chars of 100000)…原文后 4,970 字符

中间大约 80,000 字符会直接消失。

这就是为什么说 MEMORY.md 不是一个无限容量的“模型记忆”。它更像是一个会被放进 system prompt 的长期记忆文件。文件可以很长,但每次模型 run 实际看到的内容,受 prompt 注入预算限制。

总结

MEMORY.md 虽然叫做记忆文件,但是我们一定不要把他当数据库用。 它在 bootstrap 文件加载列表中的优先级是最低的,你前面的几个文件如果内容过长,很容易就会被截断。

MEMORY.md 适合放一些你的偏好, 特殊的一些处理工作流的习惯,不要把他当作知识库用。我们日常和 agent 对话过程中,它会帮我把适合作为习惯偏好的记忆写到这个文件中。大多数情况下, 甚至都不需要我们亲自来编辑这个文件。

如果你发现你写在 MEMORY.md 文件中的内容被模型忽略了,或者好像被它忘了。 你就可以检查一下你另外几个 MD 文件的长度, 也可以检查 MEMORY.md 自己的长度,是否都符合预设上限。有不小的概率是这个原因。

这次先和大家聊这么多, OpenClaw 的记忆体系还有其他的子系统,不只是 MEMORY.md,后面继续会再写内容和大家一起讨论。