乐于分享
好东西不私藏

OpenClaw 专题第六期 :OpenClaw 是如何存储、更新、调用和约束记忆的

OpenClaw 专题第六期 :OpenClaw 是如何存储、更新、调用和约束记忆的

前面几期,我们已经讲了 OpenClaw 的主链路、上下文装配、Prompt、Agent 和工具调用。  
这一期进入另一个真正决定系统能不能长期稳定运行的部分:`记忆系统`。
很多人一提到记忆,第一反应都是:
  • 把聊天记录存下来
  • 接一个向量库
  • 需要时再检索回来给模型
这套理解不算错,但远远不够。
因为一个真正可用的记忆系统,必须回答的不只是“记住什么”,而是:
  • 记忆存在哪里
  • 什么内容能进入记忆
  • 记忆什么时候更新
  • 谁能读这份记忆
  • 什么时候自动调用,什么时候按需检索
  • 作用域怎么隔离
  • 长会话压缩后,哪些信息还能继续活着
如果这些问题没答清楚,记忆越多,系统越乱。
OpenClaw 在这件事上的思路很明确:  
记忆不是模型脑子里的隐藏状态,而是 Workspace 里的可见文件,加上一层可检索、可约束、可分作用域的 Runtime 机制。
这也是它和“普通聊天机器人只留历史记录”的根本区别。
一、先把概念立住:OpenClaw 的记忆不是黑盒,而是外部状态系统
官方文档对 Memory 的定义很直接:OpenClaw 通过在 Agent Workspace 里写 Markdown 文件来“记住事情”,模型只会记住真正落盘的内容,没有隐藏记忆状态。
这件事非常重要。
因为很多系统的记忆是黑盒的:
  • 你不知道它到底记了什么
  • 你不知道它什么时候更新了
  • 你不知道它为什么这次召回、下次不召回
  • 你也很难人工修改和纠偏
OpenClaw 则反过来,把记忆做成:
  • `可见`
  • `可编辑`
  • `可审计`
  • `可检索`
  • `可约束`
所以,OpenClaw 的 Memory 本质上不是“神秘的模型能力”,而是一套由 Runtime 管理的外部状态系统。
二、OpenClaw 把记忆存在哪里
按照官方文档,OpenClaw 当前有三类主要记忆文件。
1. `MEMORY.md`
这是长期记忆。
里面放的是:
  • 稳定事实
  • 长期偏好
  • 已确认的决策
  • 跨会话仍然有效的信息
例如:
  • 用户偏好简洁回复
  • 某团队默认使用某套规则
  • 某个长期项目的固定约束
这类信息不是“今天有效、明天就过期”的临时上下文,而是值得长期保留的内容。
2. `memory/YYYY-MM-DD.md`
这是每日记忆。
它更像每日工作笔记,保存的是:
  • 当天的运行观察
  • 短期连续事项
  • 最近两天可能继续用到的信息
官方文档明确说明,OpenClaw 会自动加载今天和昨天的 daily notes。  
这意味着它天然区分了:
  • 长期稳定记忆
  • 短期连续记忆
这两个层次如果不分,系统很快就会被昨天的临时事项污染长期记忆。
3. `DREAMS.md`
这是可选文件,主要用于 Dream Diary 和 dreaming sweep summary,不是普通会话的主记忆入口。
所以从实现结构看,OpenClaw 不是把所有记忆塞进一个数据库字段,而是先把记忆做成几类可读文件,再围绕它们建立索引、检索和调用机制。
三、Memory 只能放本地吗:不能这么理解
很多人看到这套文件结构后,会立刻得出一个结论:
“OpenClaw 的 Memory 只能存在本地 Markdown 文件里。”
这个理解不准确。
更准确的说法是:  
OpenClaw 默认把 Markdown 文件作为记忆的事实源,但它的记忆检索层和后端能力并不一定局限在本地文件本身。
这件事要分三层看。
1. 默认事实源是本地 Markdown
OpenClaw 官方默认的记忆组织方式是:
  • `MEMORY.md`
  • `memory/YYYY-MM-DD.md`
  • `DREAMS.md`
这些文件是可见、可编辑、可审计的。  
也就是说,OpenClaw 默认不是把记忆藏在一个远程黑盒数据库里,而是先把“记住了什么”落成可读文件。
这是一个很重要的设计选择,因为它天然适合工程治理。
2. 但索引和检索不等于“只能读本地一个目录”
官方文档里有 `memorySearch.extraPaths` 配置。  
这意味着,除了默认 workspace,OpenClaw 还可以把 workspace 之外的 Markdown 目录纳入记忆索引。
也就是说:
  • 团队共享文档目录
  • 外挂的知识笔记目录
  • workspace 外的项目资料目录
都可以成为 OpenClaw 的 memory source。
3. 后端还可以外部化
官方文档给出的 memory backend 不止默认 builtin engine 一种,还有:
  • `QMD`
  本地优先的 sidecar,支持更强的检索能力,也支持索引 workspace 外目录
  • `Honcho`
  偏平台级的 AI-native memory backend,支持 cross-session memory、user modeling、multi-agent awareness
所以,OpenClaw 当前最核心的设计,不是“memory 必须在本地”,而是:
  • 记忆事实源要可见
  • 记忆边界要可控
  • 检索层可以替换
  • backend 可以扩展
这比一开始就把所有记忆都丢进远程黑盒,更适合长期治理。
四、OpenClaw 怎么把记忆变成“可检索记忆”
光有文件还不够。  
文件能存,但不代表模型能稳定找到。
OpenClaw 的做法是:`文件是事实源,索引是检索层。`
按照官方 Builtin Memory Engine 和 Memory Search 文档,默认 builtin engine 会做几件事。
1. 每个 Agent 拥有自己的索引
官方文档写得很清楚,builtin engine 使用的是 `per-agent SQLite database`。
这件事非常关键,因为它天然体现了第一层作用域边界:
记忆索引默认按 Agent 隔离,而不是全局共享一个大记忆池。
否则不同 Agent 会非常容易相互污染。
2. 对 Markdown 记忆做切片
官方文档给出的默认方式是:
  • 索引 `MEMORY.md`
  • 索引 `memory/*.md`
  • 切成大约 `400 tokens` 的 chunk
  • chunk 之间保留大约 `80 tokens` overlap
这背后的意义很直接:
  • 不把整篇笔记原样塞给模型
  • 让检索粒度更适合召回
  • 兼顾局部语义和上下文连续性
3. 并行跑两条检索链路
OpenClaw 的 `memory_search` 不是单一路径。  
官方文档明确写了,它会并行跑:
  • `Vector search`
  • `BM25 keyword search`
然后合并结果。
这两条路径分别解决不同问题:
  • Vector search 解决“语义近似”
  • BM25 解决“精确词、ID、错误码、配置键”
这很重要,因为真实记忆召回里,很多内容不是“语义像”就够了。  
例如某个工单号、某个配置项、某个错误字符串,靠向量检索经常不稳定,必须保留 lexical path。
4. 使用 Hybrid Search
官方明确把这套机制叫 hybrid search。  
也就是说,它不是“有 embedding 就全靠 embedding”,而是:
语义召回和关键词召回一起跑,再由 Runtime 合并结果。
这比很多一上来就“全向量化”的方案稳得多。
五、OpenClaw 的记忆是怎么更新的
记忆系统最容易翻车的地方,不是检索,而是写入。  
因为一旦什么都写,后面一定会脏。
OpenClaw 当前的更新机制,至少有三层。
1. 显式写入
最直接的方式是用户明确要求:
  • “记住我偏好 TypeScript”
  • “记住我喜欢先看结论”
  • “记住这个群以后默认查这个知识库”
这种情况下,Agent 会把内容写到合适的记忆文件里。
这说明 OpenClaw 允许“显式记忆写入”,而不是只能靠系统自己猜。
2. 文件变化自动重建索引
官方 Builtin Memory Engine 文档里写得很清楚:
  • 文件变化会触发重建
  • watcher 有 debounce
  • embedding provider、model、chunking 配置变化时,会自动全量重建索引
这意味着,OpenClaw 的记忆更新不是“写完文件,检索层还不知道”,而是文件和索引保持联动。
3. 压缩前的自动 memory flush
这是 OpenClaw 很关键、也很像 Runtime 能力的一点。
根据官方 Compaction 和 Session Management Deep Dive 文档,在会话接近上下文上限、即将 compaction 之前,OpenClaw 会先触发一次 `silent memory flush turn`。
它的目的很明确:
在对话被压缩总结之前,先把值得长期保留的信息提醒 Agent 写到记忆文件里,避免 compaction 把关键信息冲掉。
这个动作有几个特点:
  • 默认开启
  • 发生在 compaction 前
  • 用户看不到回复
  • 每个 compaction cycle 只跑一次
这比很多系统“先压缩,压缩后再想办法补记忆”稳得多。
六、OpenClaw 是在什么时候调用记忆的
这不是“有记忆就每轮全量塞进去”,而是分层调用。
1. 会话开始时的基础加载
官方 Memory 文档明确写了:
  • `MEMORY.md` 会在每个 DM session 开始时加载
  • 今天和昨天的 `memory/YYYY-MM-DD.md` 会自动加载
这意味着,OpenClaw 有一层基础记忆引导。  
也就是在正式进入当前对话之前,先把最稳定、最接近当前时间窗口的记忆作为底座带进来。
2. 运行中的 `memory_search`
如果基础加载不够,Agent 可以调用 `memory_search` 做按需召回。
例如用户问:
  • “我之前说过默认回复风格是什么?”
  • “上次我们约定的命名规则是什么?”
  • “这个项目的长期约束是什么?”
这时候系统不该全量展开所有记忆,而是通过检索拿回相关片段。
3. Active Memory 的前置阻塞调用
OpenClaw 还有一个更主动的能力:`Active Memory`。
根据官方文档,它是一个 `optional plugin-owned blocking memory sub-agent`,会在主回复之前先跑一轮受限记忆子代理。
它只允许调用两个工具:
  • `memory_search`
  • `memory_get`
如果没有相关记忆,就返回 `NONE`。
这说明 OpenClaw 对记忆调用是很克制的:
  • 不是每次都强塞大量记忆
  • 不是无边界放开任意工具
  • 而是在主回复前插入一个受限 recall step
七、OpenClaw 如何控制记忆的作用域
这是记忆系统能不能长期可用的底线。
1. Agent 作用域
默认 builtin engine 使用 `per-agent SQLite database`。  
也就是说,不同 Agent 的记忆索引默认分开。
这可以避免:
  • 招聘 Agent 读到周报 Agent 的记忆
  • 一个执行 Agent 读到另一个 Agent 的长期偏好
  • 多 Agent 系统互相污染长期记忆
2. Session 作用域
前面第四期已经讲过,OpenClaw 的运行是 `single serialized run per session`。  
而且所有 persistence 都先落到 session 轨道里。
这说明:
  • 会话历史是会话级
  • 当前运行状态是会话级
  • 不是所有 session 痕迹都会直接升级为长期记忆
这一步非常关键,因为“会话连续性”不等于“长期记忆沉淀”。
3. Chat Type 作用域
Active Memory 文档里写得很清楚:
默认 `allowedChatTypes: [“direct”]`。
也就是说,Active Memory 默认只在 direct-message style sessions 里跑,而不是默认在 group 或 channel 里全开。
这背后的逻辑很稳:
  • 私聊更适合长期个性化
  • 群聊里个性化记忆更容易越界
  • 多人上下文下记忆调用必须更谨慎
4. Runtime Eligibility 作用域
Active Memory 不是只要开插件就会跑。  
它还有严格运行条件:
  • 插件启用
  • 当前 agent 被列入目标 agents
  • chat type 命中 allowedChatTypes
  • 当前 session 必须是 eligible interactive persistent chat session
并且它不会在这些场景跑:
  • headless one-shot runs
  • heartbeat/background runs
  • generic internal agent-command paths
  • sub-agent/internal helper execution
也就是说,OpenClaw 很明确地区分了用户交互场景、后台执行场景和内部工作场景,不是所有地方都适合记忆调用。
八、OpenClaw 的记忆调用时序是什么
如果把这一套串成完整时序,大致是这样:
第一步:接收消息并解析 session
系统先确定这条消息属于哪个 session,以及是不是一个符合条件的交互式持久会话。
第二步:装配基础上下文
这一步会带上:
  • 当前消息
  • 最近会话窗口
  • session 状态
  • 基础 memory files
  • `MEMORY.md`
  • 今天和昨天的 daily notes
第三步:如果启用了 Active Memory,先跑受限记忆子代理
这个子代理在主回复前运行,只能调用:
  • `memory_search`
  • `memory_get`
它的输出不是直接回复用户,而是给主 Agent 一份“这轮是否需要引用长期记忆”的受限结果。
第四步:主 Agent 正式推理
主 Agent 基于:
  • 当前消息
  • 当前 session 状态
  • 已加载基础记忆
  • 可选的 active memory recall
  • 工具能力
进行正式决策。
第五步:必要时显式调用记忆检索
如果主 Agent 仍然需要查更细的历史,它还可以显式调用 `memory_search`。
第六步:执行工具、流式输出、持久化当前 run
完成本轮任务,把消息、工具调用、结果、事件等写回 session transcript。
第七步:接近上下文上限时,先 memory flush,再 compaction
这一步是 OpenClaw 很重要的防丢设计:
  • 先 silent flush durable facts to memory files
  • 再 compaction conversation
这样即使旧对话被压缩,关键事实也已经进了可检索记忆层。
九、从本地代码看,OpenClaw 当前已经体现出的“记忆前提”是什么
虽然当前仓库里没有完整展开一个独立的 Memory Service,但本地代码已经能看出几件很重要的事。
1. 它明确区分了配置、上下文和运行结果
在 [weekly-report-runner.mjs](/Users/liuyuhui/Work/Workspace/OpenClaw/scripts/weekly-report-runner.mjs) 里,可以直接看到:
  • `loadConfig`
  • `buildContext`
  • `createPageApi`
  • `result` 输出
  • 通知模板渲染
这说明这套系统不是“把所有信息都塞进记忆”,而是先区分:
  • 什么是稳定配置
  • 什么是本轮上下文
  • 什么是本轮执行结果
这是记忆系统做稳的前提。  
因为只有先把配置、状态、结果和记忆分开,后面才不会把一切都错误地写成 memory。
2. 它明确区分了页面现场和长期事实
在 [weekly-report.page.mjs](/Users/liuyuhui/Work/Workspace/OpenClaw/config/weekly-report.page.mjs) 里,可以看到它会先检查:
  • 当前页面是不是登录页
  • `window.appData` 是否存在
  • `bookId` 是否可读
后面再去读取:
  • 目录树
  • 父节点
  • 模板节点
  • 当前文档状态
这些都说明 OpenClaw 当前的设计更接近:
页面状态是运行时现场,不是长期记忆。
这点很关键。  
因为如果把“这次页面有没有登录”“这次 DOM 有没有读到 bookId”这种临时结果直接写成长期记忆,系统只会越来越脏。
3. 它在角色边界上已经体现出隔离思路
在 [recruitment-agent-simple-sop.md](/Users/liuyuhui/Work/Workspace/OpenClaw/docs/recruitment-agent-simple-sop.md) 里,主控 Agent、需求分析 Agent、审批协同 Agent、人才搜寻 Agent、简历评估 Agent 等角色边界是分开的。
这虽然不是 Memory 文档,但它说明 OpenClaw 当前整体设计已经默认接受一个事实:
不同 Agent 的职责不同,因此它们能读、能写、该保留的上下文也不应该天然共享。
这和官方文档里 `per-agent` 记忆索引的思路是对齐的。
十、为什么很多 Agent 的记忆最后会变成污染源
记忆系统翻车,通常不是因为“没记住”,而是因为“记住了不该记的东西”。
最常见的问题有六类:
1. 把所有历史都当记忆
聊天、日志、错误、工具输出、用户偏好、知识片段混在一起。  
结果是什么都能召回,什么都不可靠。
2. 没有长期 / 短期分层
昨天的临时问题、上周的任务痕迹、半年前的长期偏好全部混在一起,模型很难判断哪个更该优先。
3. 没有作用域约束
一个用户在私聊里的偏好,被召回到群里;一个 Agent 的历史,被另一个 Agent 用到;一个 session 的临时状态,被升级成全局记忆。
4. 没有更新时间和失效机制
用户早就改口了,系统还按旧偏好执行;某个规则早就失效了,系统还在持续召回。
5. 把故障结果写成长期事实
一次 OCR 失败、一次接口超时、一次网页结构异常,如果被写进长期记忆,后面就会反复污染决策。
6. 把记忆调用做成“每轮全量灌入”
这会导致:
  • token 膨胀
  • 相关性下降
  • 旧信息干扰新问题
  • 个性化变成噪声化
OpenClaw 当前的设计路线,恰恰是在避免这些问题:
  • 文件化存储
  • Agent 级隔离
  • Session 级状态边界
  • 受限的 Active Memory
  • compaction 前 flush
  • hybrid search 而不是单一路径召回
这套组合的核心目标不是“多记”,而是“记得住、找得到、有边界”。
下期预告
下一期我们继续把镜头拉回 OpenClaw 本体:
《从周报自动化脚本看 OpenClaw:Browser Runtime、页面适配层与任务调度》
重点讲:
  • OpenClaw 为什么不是只会调 API 的 Agent
  • Browser Runtime 在它的执行体系里扮演什么角色
  • 页面适配层为什么是稳定执行的关键
  • cron 与交互式 Agent Run 是怎么接起来的
  • 这些能力说明 OpenClaw 已经成型了哪些运行时机制