乐于分享
好东西不私藏

OpenClaw源码解析(十六):记忆系统从提问到检索返回都经历了什么

OpenClaw源码解析(十六):记忆系统从提问到检索返回都经历了什么

EDITOR’S NOTE

核心文件:

– src/agents/memory-search.ts
– src/memory/search-manager.ts
– src/memory/manager.ts
– src/agents/tools/memory-tool.ts


1. 总体主线

一条涉及“过去发生过什么”的问题,在 OpenClaw 当前实现里的典型路径是:

AppendixTEXT

用户提问-> system prompt 指导模型先调用 memory_search-> memory_search tool 解析当前 agent/session 上下文-> getMemorySearchManager(...)-> 根据 config 选择 builtin 或 qmd backend-> manager 搜索已索引记忆-> tool 返回 snippets-> 模型如果要精读,再调用 memory_get-> 读取指定路径/行段-> 当前 turn 使用这些记忆片段继续回答

所以在运行时里,memory 系统更像:

一个“按需召回层”


2. 记忆系统的运行时入口不是 manager,而是 tool

从 agent 侧看,最直接的入口是:

  • memory_search
  • memory_get

定义在:

  • src/agents/tools/memory-tool.ts

这意味着“记忆”在当前主路径里不是隐式魔法,而是:

  • 被 system prompt 强制建议使用的工具
  • 在需要时由模型显式调用

3. tool 会先解析“这次搜索属于哪个 agent”

memory-tool.ts 不会直接全局搜,而是先:

  • 从 agentSessionKey 推导 agentId
  • 读取当前 config
  • 判断这个 agent 是否启用了 memory search

这说明记忆系统天然带有 agent 作用域。

也就是说:

  • 不同 agent 可以有不同记忆配置
  • 默认 store path 也是按 agent 隔离
  • manager cache key 也包含 agentId

4. resolveMemorySearchConfig(…):记忆运行参数不是原始配置,而是动态配置

memory 的真正运行参数不是 config 文件原样,而是 ResolvedMemorySearchConfig

  • sources
  • provider
  • remote
  • experimental.sessionMemory
  • fallback
  • model
  • local
  • store
  • chunking
  • sync
  • query.hybrid
  • cache

这说明 OpenClaw 的记忆系统一开始就不是“读几个 markdown 文件”,而是一个完整的运行时配置域。


5. manager 的创建入口:getMemorySearchManager(…)

真正创建 manager 的统一入口是:

  • src/memory/search-manager.ts

它的职责不是搜索,而是:

根据 backend 配置和运行目的,返回一个真正可用的 MemorySearchManager

这里有两个很关键的设计点。

5.1 manager 有 backend 路由层

不是只有一个 MemoryIndexManager

当前至少有:

  • builtin MemoryIndexManager
  • QmdMemoryManager

5.2 QMD 不是替代 builtin,而是 primary + fallback 模式

当 backend 配成 qmd 时:

  • 先尝试创建 QmdMemoryManager
  • 若成功,再包一层 FallbackMemoryManager
  • fallbackFactory 指向 builtin MemoryIndexManager

这说明 QMD 的设计哲学是:

更强的外部后端可以优先使用,但 builtin 仍然是保底生存线。


6. manager 不是每次都新建,存在缓存

builtin manager 有:

  • INDEX_CACHE
  • INDEX_CACHE_PENDING

QMD manager 也有:

  • QMD_MANAGER_CACHE

作为 agent 运行时的一部分被缓存复用。

这样做的好处包括:

  • 避免重复初始化数据库/embedding provider
  • watcher、session listener、interval sync 能持续工作
  • manager 内部可以保持 dirty 状态、fallback 状态、provider 状态

7. 一次 search 到 manager 后,先不一定立刻搜

MemoryIndexManager.search(…) 里,在真正执行检索前还会做两件事:

  • warmSession(…)
  • 如果 sync.onSearch 且 dirty,则异步 sync(…)

这说明记忆搜索不是“数据库里有什么就搜什么”,而是:

在搜索时还会顺便推动索引尽量刷新到较新状态。

7.1 warmSession(…)

如果配置了 sync.onSessionStart,manager 会在某个 session 第一次搜索时触发一次同步预热。

7.2 sync.onSearch

如果当前索引 dirty,系统会在 search 时发起同步。

注意:

  • 这里通常是异步触发,不一定阻塞当前 search
  • 设计目标是“尽量新鲜”,不是“每次搜之前强制全量重建”

8. search 本身分两种运行模式

MemoryIndexManager.search(…) 里有一个非常重要的分叉:

8.1 provider 存在:hybrid / vector search 路径

有 embedding provider 时,系统会:

  • 做 query embedding
  • 做 vector 检索
  • 如果 FTS 可用,再加 keyword 检索
  • 最后 mergeHybridResults

8.2 provider 不存在:FTS-only 降级路径

如果 embedding provider 不可用,但 FTS 在,系统仍然能搜索:

  • 只走关键词检索

这很重要,因为它说明 OpenClaw 的 memory 系统不是“没有 embeddings 就完全瘫痪”,而是:

可以退化成 FTS-only 继续工作。

这也解释了为什么它用 sqlite + FTS5 而不是只用纯向量库。


9. 为什么能 FTS-only 降级:因为 provider 创建本身就是可失败设计

createEmbeddingProvider(…) 的设计很值得单独记。

它支持:

  • openai
  • gemini
  • voyage
  • mistral
  • ollama
  • local
  • auto
  • auto 模式下所有远程 provider 都缺 key
  • 指定 provider 缺 key 且允许 fallback / 降级

此时会返回:

  • provider: null
  • providerUnavailableReason

然后 manager 就进入 FTS-only 模式。

这说明当前记忆系统在 provider 选择上有很强的工程韧性。


10. memory_get 的运行时角色:不是搜索,而是精读

memory_get 的用途不是“再搜一遍”,而是:

  • 在 memory_search 找到候选后
  • 精确读取某个 markdown 文件的全部或局部行段

这也是为什么 system prompt 会要求:

  • 先 memory_search
  • 再 memory_get

这背后的策略是:

先用便宜的检索找候选,再用精确读取控制上下文体积。


11. manager 的 status/probe 能力说明它不仅是“搜索器”

MemorySearchManager 接口除了 search/readFile 外,还有:

  • status()
  • sync()
  • probeEmbeddingAvailability()
  • probeVectorAvailability()
  • close()

这说明 memory manager 不是纯 query executor,而是一个运行时服务对象。

它还负责:

  • 暴露当前后端状态
  • 同步索引
  • 探测 provider/vector 能力
  • 生命周期关闭

12. 记忆系统在运行时里还有哪几条旁路

主路是 memory_search / memory_get,但还有几条和记忆强相关的运行时旁路:

  • /new / /reset 触发的 session-memory hook
  • pre-compaction memory flush
  • experimental sessions source

这三条不直接属于“检索当前问答”,但它们决定:

当前会话经验何时会沉淀成以后可被搜索的长期记忆。


13. 这一篇最重要的结论

  1. memory 系统的运行时主入口是工具,不是自动隐式注入。
  2. manager 是缓存复用的服务对象,不是一次性查询函数。
  3. backend 路由层允许 builtin / qmd 并存,并支持 fallback。
  4. embedding provider 失败不一定导致记忆系统失效,因为还能退到 FTS-only。
  5. memory_search -> memory_get 是一条有明确预算意识的两阶段召回链。