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. 这一篇最重要的结论
-
memory 系统的运行时主入口是工具,不是自动隐式注入。 -
manager 是缓存复用的服务对象,不是一次性查询函数。 -
backend 路由层允许 builtin / qmd 并存,并支持 fallback。 -
embedding provider 失败不一定导致记忆系统失效,因为还能退到 FTS-only。 -
memory_search -> memory_get 是一条有明确预算意识的两阶段召回链。
夜雨聆风