OpenClaw源码解析(十七):记忆存储与索引:有哪些文件,怎么落库,为什么这样选

OpenClaw源码解析(十七):记忆存储与索引:有哪些文件,怎么落库,为什么这样选当前实现里 长期记忆相关的数据源至少分三类 默认路径是 workspace MEMORY md workspace memory md 这里的语义最直接 用户或系统明确写下来的长期记忆 通过 extraPaths 配置 可以把额外文件或目录纳入记忆源 这些也必须是 markdown 这不是默认长期记忆 而是可选来源 两种路径会让 workspace workspace

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



当前实现里,长期记忆相关的数据源至少分三类。

默认路径是:

  • /MEMORY.md
  • /memory//*.md

这里的语义最直接:

  • 用户或系统明确写下来的长期记忆

通过 extraPaths 配置,可以把额外文件或目录纳入记忆源。

这些也必须是 markdown。

这不是默认长期记忆,而是可选来源。

两种路径会让 session 内容进入记忆相关系统:

  • builtin memory 开启 experimental  sources: [“sessions”]
  • QMD backend 配置 sessions export/index

所以 session transcript 在 OpenClaw 里很特殊:

  • 它天然是短期工作记忆的原材料
  • 但在特定配置或导出链下,也可以成为长期记忆数据源

这里要非常明确。

机制是:

  • session transcript
  • context engine
  • system prompt / prompt assembly

特点:

  • 面向当前或相邻几轮任务
  • 直接参与 prompt
  • 更强调当前状态连续性

机制是:

  • markdown 文件
  • 可选 session transcript 索引/导出
  • sqlite / qmd 索引
  • memory search / memory get 按需召回

特点:

  • 不直接自动进入 prompt
  • 先索引、后检索
  • 更强调跨会话持久性

所以不要把 “session transcript 被索引” 理解成“短期记忆和长期记忆没区别”。

区别仍然在于:

  • 一个是当前运行时工作集
  • 一个是可召回历史库

当前 builtin backend 使用:

  • node:sqlite

这是 Node 内建 sqlite 接口,通过 src/memory/sqlite.ts 做 require 包装。

从当前代码结构看,选择 sqlite 的工程动机很明显:

  • 本地单机使用为主
  • 零服务依赖
  • 同时承载 metadata、FTS、embedding cache 很方便
  • 配合扩展可以做向量检索

这非常符合 OpenClaw 的整体风格:

默认优先本地、可携带、低运维成本的内建能力。

因为不同 Node 发行版不一定带 node:sqlite

所以这层包装的作用是:

  • 把缺失 sqlite runtime 的情况变成可理解的错误
  • 而不是让系统抛出模糊的 builtin module 错误

ensureMemoryIndexSchema(…) 定义了几张核心表。

存:

  • 索引元信息

比如:

  • provider/model
  • chunk 配置
  • sources
  • vector dims

存:

  • path
  • source
  • hash
  • mtime
  • size

它的意义是:

记录当前有哪些源文件,以及这些文件内容是否变化。

存:

  • chunk id
  • path
  • source
  • 起止行
  • hash
  • model
  • text
  • embedding
  • updated_at

这张表是 builtin recall 的核心文本块表。

存:

  • provider
  • model
  • provider_key
  • hash
  • embedding
  • dims
  • updated_at

这是非常值得学习的一张表。

它说明系统不是每次索引都重新算 embedding,而是:

对同样文本 hash 的 embedding 进行跨次缓存。


chunks_fts

用途:

  • 关键词检索
  • 支撑 hybrid recall
  • 也支撑 FTS-only 降级模式

chunks_vec

用途:

  • 向量检索

但它不是必然可用的,是:

  • 可选
  • 动态探测
  • 带超时加载

这说明 builtin backend 本质上是:

sqlite 元数据表+ FTS5+ 可选 sqlite-vec+ embedding cache

而不是只有一张“向量表”。


因为这两者解决的问题不一样。

  • 不依赖 embedding provider
  • 对精确关键词/文件名/术语更敏感
  • 可作为降级路径
  • 对语义相近表达有更强召回
  • 对口语化 query 更友好

这解释了为什么 current design 不是“纯 vector DB”:

  • 纯 vector 容易丢掉精确 lexical hit
  • 纯 FTS 又难处理语义近邻

所以 builtin backend 的技术选型体现的是:

不是追求“最潮数据库”,而是追求单机可用前提下的召回韧性。


resolveStorePath(…) 默认给的是:

  • stateDir/memory/ .sqlite

这意味着:

  • 每个 agent 默认有自己独立的 memory DB
  • 不同 agent 可以有不同记忆视图

这和 OpenClaw 其他能力按 agent 隔离的设计是一致的。

所以记忆不是全局一锅端,而是默认 agent-scoped。


builtin manager 索引 markdown 文件时会经过:

  • listMemoryFiles(…)
  • buildFileEntry(…)
  • chunkMarkdown(…)

负责:

  • 找到  MEMORY.md
  • 找到  memory//*.md
  • 找到  extraPaths  下的 markdown
  • 过滤掉 symlink
  • 去重

负责:

  • 读文件
  • 算 hash
  • 记录相对路径、mtime、size

负责:

  • 按大致 token 预算切块
  • 保留 overlap
  • 记录每个块的 startLine / endLine

这里默认用的是一个粗略换算:

  • tokens * 4 ~= chars

这表明当前 chunking 是偏工程化近似,而不是 tokenizer 精细切块。


如果启用了 sessions source,或 QMD 需要导出 sessions,会走:

  • listSessionFilesForAgent(…)
  • buildSessionEntry(…)

buildSessionEntry(…) 很值得注意:

  • 只提取  user  /  assistant
  • 跳过 tool messages
  • 只保留 text 内容
  • 做  redactSensitiveText(…)
  • 最终拼成:
    • User: …
    • Assistant: …

也就是说,session 进入长期记忆索引前不是原样 JSONL,而是:

提炼成对 recall 更友好的纯文本会话摘要流。

这个转换很重要,因为它明确告诉你:

  • 长期记忆检索不想索引完整工具协议噪声
  • 更重视人类可理解的 user/assistant 语义

MemoryManagerSyncOps 里有几种触发源:

  • warmSession(…)
  • dirty 时由  search(…)  触发
  • chokidar 监听 markdown 变化
  • onSessionTranscriptUpdate(…)
  • 增量计算 bytes/messages 变化

  • 定时刷新

所以 builtin backend 并不是“手动 rebuild 一次”的模式,而是:

watcher + on-demand sync + interval sync 的混合模型。


因为 session transcript 变化比 markdown 文件更频繁。

如果每写一行 JSONL 就立刻重建索引,代价太高。

所以系统会累计:

  • 新增字节数
  • 新增消息数

只有达到阈值才把该 session 标成 dirty 并触发同步。

这是一种很典型的“高频事件 -> 低频索引刷新”的节流设计。


当前代码里,如果没有 embedding provider,sync 会:

  • 直接跳过 embedding sync

日志里明确叫:

  • FTS-only mode

这再次说明 builtin memory 的核心目标不是“非得有向量库”,而是:

先尽量让记忆系统有可用搜索,再争取更好的语义召回。


QMD backend 不是复用 builtin sqlite schema,而是使用:

  • QMD 命令行工具
  • QMD 自己维护的 index.sqlite
  • XDG config/cache 目录

QMD manager 会为每个 agent 准备:

  • agentStateDir/qmd/xdg-config
  • agentStateDir/qmd/xdg-cache/qmd/index.sqlite

所以它本质上是:

外部索引系统,以 OpenClaw agent 为作用域进行托管。


QMD 不直接认 OpenClaw 的 files/chunks 表,而是认 collections。

它会解析并维护:

  • default memory collections
  • custom path collections
  • optional sessions collection

每个 collection 有:

  • name
  • path
  • pattern
  • kind

这说明 QMD 路径更接近:

多集合文档索引系统

而 builtin 更接近:

单 sqlite 库里的多 source 混合索引

QMD 不是直接搜 OpenClaw session JSONL。

它会:

  • 先导出 session 为 markdown 到 exportDir
  • 再把这些导出文件纳入 collection

这样做的好处是:

  • QMD 处理的是统一文档格式
  • 与 builtin 的 session text 抽取思路类似,都是先把 transcript 变成 recall 友好的文档

所以在 QMD 路径里,session memory 更像:

会话导出文档化,再交给外部索引器。


因为 QMD 有自己的额外风险:

  • CLI 不可用
  • collection 元数据损坏
  • update/search 超时
  • MCP/mcporter 链路问题

而 builtin 至少能在本地 sqlite 上生存。

这就是为什么 search-manager 的架构不是:

  • “qmd 开了就彻底替代 builtin”

而是:

  • “qmd 优先,builtin 保底”

  1. builtin backend 的真实技术栈是  sqlite + FTS5 + 可选 sqlite-vec + embedding cache
  2. 长期记忆文件默认是  MEMORY.md  和  memory/*/.md ,可扩展到 extraPaths 和 sessions。
  3. session transcript 进入长期记忆时会被转译成更适合 recall 的纯文本格式。
  4. sync 不是手工 rebuild,而是 watcher、search、session update、interval 共同驱动。
  5. QMD backend 是一套外部集合索引体系,不是 builtin sqlite 的简单替皮。

小讯
上一篇 2026-04-12 20:20
下一篇 2026-04-12 20:18

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/252385.html