乐于分享
好东西不私藏

OpenClaw记忆系统进阶详解:三层存储、知识图谱和做梦的工程细节

OpenClaw记忆系统进阶详解:三层存储、知识图谱和做梦的工程细节

上一篇没讲透的东西

上一篇记忆系统的文章里,我们讲了 OpenClaw 和 Claude Code 的记忆哲学差异——一个用向量数据库,一个用 markdown 文件。但很多工程细节没有展开。

这篇是进阶版。我们来聊几个更深入的问题:三层架构的数据到底存在哪里?知识图谱为什么不用图数据库?六因子评分的每一项到底怎么算?”做梦”机制的三个阶段具体在干什么?

如果你不关心实现细节,跳过这篇完全没问题。但如果你想自己搭一套类似的记忆系统,这篇可能会帮你少踩一些坑。

三层架构:每一层都有自己的存储

上一篇我画了一个简化的三层图:编排层、存储层、知识层。这个描述不够准确——实际上每一层都有自己独立的数据存储,不是”一层存数据,其他层只做逻辑”。

memory-core (编排层)不只做编排,它自己存了三类数据:

存什么
存在哪
召回追踪(谁被召回几次、分数多少)
SQLite KV 表
向量索引 + 全文索引
SQLite 表(chunks_vec + chunks_fts
长期记忆、每日记忆、梦境日记
磁盘文件(MEMORY.mdmemory/*.mdDREAMS.md

而且 memory-core 内置了一个 SQLite 向量搜索后端——这是默认后端,不装任何额外插件就能工作。

memory-lancedb (存储层)是 memory-core 的可选替代后端,不是必需品。它和 memory-core 内置的 SQLite 后端竞争同一个插件槽位,同一时间只能激活一个。选了 LanceDB 就不用内置的 SQLite 向量搜索,反过来也一样。

memory-wiki (知识层)完全独立。它把结构化知识存在 Obsidian 风格的 markdown 文件里(~/.openclaw/wiki/main/),有自己的 SQLite KV 表做同步状态追踪。它可以单向读取 memory-core/lancedb 的数据作为补充素材,但从不往里写。

更准确的关系图:

memory-core(内置 SQLite 向量后端 + 编排 + 生命周期)    ↕ 可替换(同一插件槽位)memory-lancedb(更强的嵌入式向量后端)memory-wiki(独立的结构化知识库,可选单向读取上面的数据)

为什么选 LanceDB 而不是别的向量数据库

源码里没有写明选型理由,但从架构可以推断:

嵌入式、零运维。代码里就是 lancedb.connect(dbPath) 传一个本地路径,不需要跑独立的数据库服务器。和 OpenClaw 用 SQLite 做状态存储的思路一致——自包含,装完就能用。如果选 Pinecone/Weaviate/Qdrant ,都需要额外的服务端进程。

云存储透传dbPath 直接支持 s3:// 和 gs:// URI ,企业部署时数据可以存在云端对象存储里。

Arrow 原生。底层用 Apache Arrow 列式格式,向量运算效率高。

除了 LanceDB , OpenClaw 还支持其他记忆后端:

后端
类型
特点
内置 SQLite
默认
零依赖,够用
LanceDB
可选升级
更强的向量搜索
QMD
外部
本地 sidecar ,带 reranking
Honcho
外部
AI 原生跨会话记忆

大多数用户用默认的 SQLite 就够了, LanceDB 是给对向量搜索性能有更高要求的场景准备的。

知识图谱:为什么不用图数据库

memory-wiki 本质上就是一个知识图谱。有节点( entity/concept 页面)、有边( relationships )、有属性( claims 、 personCard )。但它没有用 Neo4j 这样的图数据库,而是用文件系统 + YAML frontmatter 实现。

原因和选 LanceDB 一样——零依赖。跑一个 Neo4j 实例对个人用户太重了。用 markdown 文件还有几个额外好处:用户可以直接用 Obsidian 浏览编辑, Git 友好可以版本控制,不需要学 Cypher 查询语言。

代价是多跳遍历能力弱。但 OpenClaw 的使用场景主要是单跳查询(”Brad 是谁”、”谁负责 Teams”),文件系统够用了。

Claim :一个聪明的建模取舍

如果你试过设计通用知识图谱 schema ,大概率卡在同一个问题上:谓词定义不完

传统知识图谱把知识建模成三元组:主体 → 谓词 → 客体。问题是谓词需要预定义(works-atlocated-inreports-to…),每换一个领域就得扩展谓词表,永远定不完。

OpenClaw 的做法是放弃结构化谓词,用自然语言声明( Claim )作为原子单位

claims:  - text: "Brad 负责 Microsoft Teams 相关的路由"    confidence: 0.9    status: supported    evidence:      - kind: maintainer-whois        sourceId: source.maintainers        confidence: 0.8        privacyTier: local-private

text 就是一句人话。结构化不在内容里,在外面包的元数据上——你有多确定( confidence )、这个说法目前还成不成立( status : supported/contested/refuted/superseded )、你凭什么这么说( evidence 带来源和置信度)。

这个设计的核心洞察是:传统图谱把结构放在”能说什么”上(谓词 schema ), OpenClaw 把结构放在”怎么评估说的对不对”上( confidence/evidence 框架)。 后者是领域无关的——不管你描述什么领域的知识,”这条信息有多可靠”这个问题都适用。

节点之间的关系( relationship )也是松散类型——kind 是自由字符串,不是封闭枚举。你可以写 collaborates-withdepends-on、随便什么都行。

这个设计的代价:检索不确定性

自由文本的 Claim 有一个明显的问题:同一件事可能有不同的表述,搜不到

"Brad 负责 Microsoft Teams 相关的路由""Teams 方面的问题找 Brad""Brad 是公司的 Teams 专家"

搜 “Teams” 三条都能命中。但搜 “谁管协作工具”?全部漏掉——因为没有一条 claim 的文本里出现”协作工具”。

OpenClaw 靠三层补救:

半结构化锚点personCard.lanepersonCard.askForaliases 这些字段虽然也是自由文本,但相对稳定,搜人时优先匹配这些字段。

向量桥接。启用 shared corpus 后,模糊查询会走 memory-core/lancedb 的向量搜索,通过语义相似度把”协作工具”和”Microsoft Teams”关联上。

定期审计wiki_lint 会检测孤立节点、断裂链接、低置信度条目,提醒你补全。

但说实话,这三层加起来也不如固定 schema 的检索可靠。这是用检索确定性换建模灵活性的 trade-off 。 OpenClaw 赌的是 AI Agent 场景下 LLM 会自动换词重试,对单次检索精度的容忍度更高。

另外值得注意的是, wiki 的检索不调 LLM。它预编译一个 agent-digest.json 索引文件,检索时就是普通的字符串匹配和字段过滤,纯代码逻辑,零 token 成本。 LLM 只在创建新页面时介入(从对话里提取结构化信息写进 YAML )。

六因子评分的计算细节

每条短期记忆的重要性由六个因子加权计算:

最终分数 = 频率×0.24 + 相关性×0.30 + 多样性×0.15          + 新近度×0.15 + 巩固度×0.10 + 概念覆盖×0.06         + Dreaming阶段加成(最多+0.15)

逐个拆解:

频率( 24%)log1p(信号总数) / log1p(10)。对数缩放意味着从 1 次到 2 次的提升远大于从 9 次到 10 次——防止刷量。 10 次信号得满分。

相关性( 30%,权重最高):每次被搜索命中时,搜索引擎给出一个 0-1 的相似度分数,所有分数取平均值。命中质量比命中次数更重要。

多样性( 15%)max(不同查询数, 不同召回天数) / 5。一条记忆如果只被同一种问题触发,说明它太窄了;被 5 种不同的问题触发得满分。

新近度( 15%):指数衰减, 14 天半衰期。e^(-ln2/14 × 天数)。 14 天后 0.5 分, 28 天后 0.25 分。

巩固度( 10%):两个子因子按 55:45 混合——在多少个不同的日子被召回( 5 天满分),以及第一次和最后一次召回之间跨了多少天( 7 天满分)。直接模拟心理学的间隔重复效应

概念覆盖( 6%,权重最低):自动从记忆文本提取概念标签(约 50 个领域术语词表 + CJK 分词),标签数 / 6 得分。

晋升门槛:分数 ≥ 0.75 、信号总数 ≥ 3 、不同触发场景 ≥ 2 ,三个条件同时满足才能进入 MEMORY.md

Dreaming 三阶段:每一步在做什么

每天凌晨 3 点的定时任务按顺序执行 Light → REM → Deep :

浅睡( Light )——用最便宜的模型、最低思维级别:

读取当天的 memory/YYYY-MM-DD.md 和会话转录,切片去重,登记为短期召回信号。给命中的条目记录 lightHits,贡献最多 +0.06 的分数加成。把暂存的条目列表传给下一阶段。

REM——用最慢的模型、最高思维级别、最贵的预算:

优先处理 Light 暂存的条目。聚合所有条目的概念标签,识别跨记忆的模式(”这几条都和数据库有关”)。筛选”可能的持久真理”。给命中条目记录 remHits,贡献最多 +0.09 的分数加成。

深睡( Deep )——执行晋升:

用完整的六因子公式 + Light/REM 加成给所有候选者排名。达标的写入 MEMORY.md。如果记忆整体健康度跌破 0.35 (比如长时间没有新记忆晋升),触发恢复机制,回看 30 天找高置信度候选者紧急晋升。

一个细节:每个阶段结束后会用子 Agent 生成一段诗意的梦境叙事写进 DREAMS.md, prompt 是”像一个碰巧会写代码的诗人那样写”。纯装饰,不影响逻辑,但翻到时会觉得很有趣。

长期记忆会衰减吗?

短期记忆有 14 天半衰期的衰减, 30 天未晋升则不再参与评估。

晋升后的长期记忆不衰减。一旦写进 MEMORY.md,六因子评分就不再重新计算了。存活时间取决于预算淘汰——MEMORY.md 有 10,000 字符上限,满了之后按 FIFO 淘汰最早的自动晋升条目。

注意: FIFO 淘汰不看分数高低。一条六因子满分的记忆和一条刚过门槛的记忆,谁先进的谁先被挤掉。用户手动写入的内容永远不被淘汰。

这意味着一个极端情况:如果用户手写内容超过 10,000 字符,MEMORY.md 会突破预算上限无限膨胀,同时自动晋升机制完全失效。这是一个设计盲区——系统信任用户不会写太多,但没有防护。

如果我来设计,会怎么改

基于对 OpenClaw 记忆系统的分析,有几个可以改进的方向:

FIFO 换成按分数淘汰。当前的 FIFO 策略太粗暴——先进的未必不重要。按重要性分数淘汰(低分先出)会更合理。

wiki 的 relationship kind 用半封闭枚举。完全自由文本的 kind 导致同一种关系可能有多种写法,影响检索。建议提供一个推荐枚举列表(collaborates-withmanagesdepends-on 等),同时允许自定义扩展。

给手写内容也加预算管理。至少给个警告——当手写内容超过阈值时提醒用户清理,而不是默默地让文件膨胀、挤掉自动晋升。


这是 OpenClaw 系列的最后一篇技术深潜。全系列基于公开源码分析,核心数据结构和算法参数均来自 extensions/memory-core/src/short-term-promotion.ts 和 extensions/memory-wiki/ 源码。