4902 字
25 分钟

OpenClaw memory-core 源码精读

主题:从 query 进入 memory_search 开始,一路追到 recall result,再看结果如何回到模型可见的工具结果里。本文默认你已经看过第一篇《OpenClaw 长期记忆与 RAG 深入笔记》,这一篇专注源码链路


1. 这篇的目标是什么#

第一篇主要解决的是:

  • OpenClaw 的长期记忆是什么;
  • 默认 RAG 的思想是什么;
  • memory-corememory-lancedb 有什么区别。

这一篇不再讲那么多概念,而是回答一个更“工程”的问题:

当模型想调用 memory_search 时,代码到底怎么一层一层走到最终结果?

你可以把它理解成一条源码追踪路线:

  1. tool 是怎么创建出来的
  2. tool 执行时怎么知道自己属于哪个 agent
  3. 怎么拿到 memory manager
  4. manager 怎么决定用 builtin 还是 QMD
  5. builtin manager 怎么做 embedding
  6. 怎么做 vector search / keyword search / hybrid merge
  7. 怎么给结果加 citation
  8. memory_get 又怎么沿着另一条分支把具体行读出来

2. 先给你一张“大地图”#

如果先不看细节,默认 memory-core 的主链路可以简化成这张图:

memory_search tool
-> tools.ts
-> tools.shared.ts
-> tools.runtime.ts
-> memory/index.ts
-> memory/search-manager.ts
-> MemoryIndexManager.get()
-> MemoryIndexManager.search()
-> embedQueryWithTimeout()
-> searchKeyword() + searchVector()
-> mergeHybridResults()
-> decorateCitations()
-> jsonResult(...)

如果用户或模型接着要读具体内容:

memory_get tool
-> tools.ts
-> tools.runtime.ts
-> readAgentMemoryFile() 或 manager.readFile()
-> 返回 { path, text }

看起来很多层,但其实每一层职责都挺清楚。下面我们按真实代码顺序走。


3. 第一站:memory_search 这个 tool 是在哪里定义的#

最直接的入口在:

  • extensions/memory-core/src/tools.ts

其中最关键的是:

  • createMemorySearchTool(...)
  • createMemoryGetTool(...)

先看 memory_search

3.1 createMemorySearchTool() 做了什么#

它不是立刻开始搜索,而是先做 3 件事:

  1. 定义 tool 的名字、描述、参数 schema;
  2. 通过共享工具层创建一个真正可执行的 tool;
  3. 把执行逻辑包装成一个 async execute 函数。

对应位置:

  • extensions/memory-core/src/tools.ts:24

你可以把它理解成:

这一层不是“搜索引擎本体”,而是“把搜索引擎包装成 agent 能调用的工具接口”。

这在 agent 系统里很常见。


4. 为什么工具层和检索层要分开#

很多零基础读者看到这一步会问:

为什么不在 tools.ts 里直接把搜索全写完?

因为 tool 层和 engine 层关心的问题不同。

4.1 tool 层关心什么#

  • tool 叫什么
  • 参数怎么校验
  • agent 当前能不能用这个工具
  • 返回结果怎么包装成 JSON
  • 是否给 snippet 加 citation

4.2 engine 层关心什么#

  • memory store 在哪
  • embedding provider 怎么初始化
  • query 怎么 embed
  • vector table / fts table 怎么查
  • 结果怎么 merge

所以 OpenClaw 把这两层拆开,是一个很正常、也很值得学习的工程设计。


5. 第二站:tools.shared.ts 是真正的“入口门卫”#

接下来会进入:

  • extensions/memory-core/src/tools.shared.ts

这是一个很重要的文件,因为它负责解决一个本质问题:

一个 tool 被调用时,怎么知道该连到哪个 agent、哪个 config、哪个 memory manager?

5.1 resolveMemoryToolContext()#

这个函数做了几件很关键的事情:

  1. 拿到 config;
  2. 根据 session key 解析 agentId;
  3. 检查该 agent 是否启用了 memory search;
  4. 如果没启用,直接返回 null。

对应位置:

  • extensions/memory-core/src/tools.shared.ts:33

这说明:

  • 不是所有 agent 都一定有 memory_search;
  • tool 创建阶段就已经带有 agent 作用域判断。

5.2 createMemoryTool()#

这个函数就是一个通用工厂:

  • 先 resolve context;
  • 如果没有合法上下文,就不创建 tool;
  • 如果有,就返回带 execute 的 tool 定义。

对应位置:

  • extensions/memory-core/src/tools.shared.ts:86

也就是说:

memory_search 不是“始终存在的全局命令”,而是“对当前 agent 合法时才装上的工具”。


6. 第三站:tool 执行时并不会直接碰到数据库#

memory_search 真正执行时,会先调:

  • loadMemoryToolRuntime()

对应:

  • extensions/memory-core/src/tools.shared.ts:16

这个函数做的事很简单,但很关键:

  • 懒加载 ./tools.runtime.js

为什么要这样做?

因为运行时桥接通常不想在模块一加载就把整套 memory runtime 全拉起来。

这是一个典型的 lazy-load 设计:

  • 首次真正需要 memory 时再加载 runtime;
  • 避免不必要的初始化开销。

7. 第四站:tools.runtime.ts 很薄,但地位很关键#

文件:

  • extensions/memory-core/src/tools.runtime.ts

内容非常短:

  • 导出 readAgentMemoryFile
  • 导出 resolveMemoryBackendConfig
  • 导出 getMemorySearchManager

对应位置:

  • extensions/memory-core/src/tools.runtime.ts:1

这意味着它本质上是一个:

tool 层与底层 memory runtime / manager 之间的桥接文件

它本身逻辑不多,但把来源拆清楚了:

  • 读文件能力来自 host runtime files
  • search manager 来自 memory/index.ts

8. 第五站:memory/index.ts 是 memory 子系统的总出口#

文件:

  • extensions/memory-core/src/memory/index.ts

它导出了:

  • MemoryIndexManager
  • getMemorySearchManager
  • closeAllMemorySearchManagers

对应位置:

  • extensions/memory-core/src/memory/index.ts:1

这一层很像一个 barrel file,它的作用是:

  • 对外隐藏内部文件结构;
  • 让外部只依赖一个稳定出口。

这对源码阅读很重要:

你看到 getMemorySearchManager() 时,第一反应就该去 memory/search-manager.ts


9. 第六站:真正的 manager 选择发生在 search-manager.ts#

文件:

  • extensions/memory-core/src/memory/search-manager.ts

这里最关键的函数就是:

  • getMemorySearchManager(...)

对应位置:

  • extensions/memory-core/src/memory/search-manager.ts:43

9.1 它先做什么#

第一步不是直接 new MemoryIndexManager,而是:

  • 先调用 resolveMemoryBackendConfig(params)

这一步的意义是:

先搞清楚这次到底要用哪个 memory backend。

9.2 builtin vs QMD#

这个函数里最值得注意的一点是:

  • 如果配置的是 QMD,就优先走 QMD manager;
  • 如果 QMD 失败,还可以 fallback 到 builtin;
  • 默认情况下,还是走 builtin MemoryIndexManager

也就是说:

默认链路并不是“写死 builtin”,而是“先看 backend 配置,再选择默认 builtin 或特定 backend”。

9.3 这层为什么重要#

因为它把“外部 API”变成了“统一 manager 接口”。

tool 层不需要知道:

  • 你用的是 builtin 还是 QMD
  • 后面是不是 fallback
  • manager 是缓存的还是新建的

tool 层只需要知道:

  • 我拿到一个能 search() / readFile() 的 manager 就行。

这就是一个很典型的多后端抽象层。


10. 第七站:默认情况下落到 MemoryIndexManager.get()#

如果不是 QMD,或者 QMD 不可用,搜索管理器会进入 builtin 路线:

  • MemoryIndexManager.get(...)

位置:

  • extensions/memory-core/src/memory/manager.ts:159

10.1 这个 get() 不只是 new 一个对象#

它做的事情比“new manager”多得多:

  1. 解析 resolveMemorySearchConfig
  2. 判断 memorySearch 是否启用;
  3. 计算 workspaceDir;
  4. 生成 cache key;
  5. 如果已有缓存 manager,直接复用;
  6. 如果没有,再创建新的 MemoryIndexManager

10.2 为什么它要缓存#

因为 memory manager 不是一个“无状态函数对象”,而是维护着:

  • sqlite 连接
  • provider 状态
  • watcher
  • session listener
  • dirty 标记
  • sync 队列

这种对象如果每次 tool 调用都重新造,成本会很高,也会丢失持续状态。

所以缓存是很自然的设计。


11. 第八站:MemoryIndexManager 构造函数到底干了什么#

看构造函数部分:

  • extensions/memory-core/src/memory/manager.ts:217

你会发现它一上来就做很多“基础设施”初始化:

  • 打开数据库
  • 计算 provider key
  • 初始化 cache 配置
  • 初始化 fts / vector 配置
  • ensureSchema()
  • 读 meta
  • 非 status 模式下启动 watcher / session listener / interval sync

11.1 这说明默认 memory-core 是“常驻型组件”#

不是说:

  • 搜索时才临时看一眼文件;

而是:

  • 启动后维护一个持续存在的本地索引管理器。

这点你一定要建立直觉,否则很容易误以为它每次 query 都要完整重扫文件。


12. 第九站:search() 是真正的 recall 主体#

核心函数:

  • extensions/memory-core/src/memory/manager.ts:315

这一段几乎就是默认 memory-core 的 recall 主体。

我们一步一步拆。

12.1 第一步:清洗 query#

const cleaned = query.trim();
if (!cleaned) return [];

这一步很普通,但告诉你一个事实:

  • manager 层还是尽量守住输入安全的。

12.2 第二步:warmSession()#

void this.warmSession(opts?.sessionKey);

这一步的作用是:

  • 若配置了 sync.onSessionStart
  • 会在 session 刚开始时推动一次 memory sync

也就是说,查询不只是“读”,还可能顺手触发记忆索引变新。

12.3 第三步:如果 dirty,就尝试 async sync#

if (this.settings.sync.onSearch && (this.dirty || this.sessionsDirty)) {
void this.sync({ reason: "search" })
}

这一步非常值得研究。

它说明:

  • 搜索发生时,系统可能意识到索引过期了;
  • 然后异步地刷新索引;
  • 但不会硬阻塞搜索。

这个设计非常“工程化”:

  • 优先保证响应;
  • 容忍 recall 结果可能略微滞后;
  • 让 freshness 和 latency 做平衡。

12.4 第四步:检查是否真的有 indexed content#

函数:

  • hasIndexedContent()

如果根本没有 chunk,自然直接返回空结果。

这一步避免了后面无意义地 embed query。

12.5 第五步:初始化 embedding provider#

await this.ensureProviderInitialized();

这一步很关键。

因为前面 manager 虽然已经构造好了,但 provider 通常还是 lazy init 的。

也就是说:

  • manager 可以先存在;
  • provider 到真正 search 时再初始化。

13. 第十站:为什么 provider 也是懒加载的#

初始化 provider 的逻辑在:

  • extensions/memory-core/src/memory/manager.ts:274
  • extensions/memory-core/src/memory/manager.ts:142

它最终会调用:

  • createEmbeddingProvider(...)

位于:

  • extensions/memory-core/src/memory/embeddings.ts

13.1 为什么不在构造函数里直接初始化 provider#

因为 provider 可能意味着:

  • 本地模型加载
  • 远程 API 配置检查
  • fallback 决策
  • batch 能力探测

这些事情都不一定每次都用得上。

lazy init 的好处:

  • 启动轻一点
  • 真有 recall 需求时再付代价
  • 某些 agent 即使挂了 memory tool 也未必每轮都搜索

14. 第十一站:query 是怎么变成 embedding 的#

这部分在:

  • extensions/memory-core/src/memory/manager-embedding-ops.ts

最关键的函数:

  • embedQueryWithTimeout(text)

位置:

  • extensions/memory-core/src/memory/manager-embedding-ops.ts:401

14.1 它做了什么#

  1. 确认当前不是 FTS-only 模式;
  2. 根据 provider 是否 local 选不同 timeout;
  3. 调用 this.provider.embedQuery(text)
  4. withTimeout() 包一层超时保护。

这说明 query embedding 并不是“裸调 provider”,而是有运行时保护的。

14.2 为什么超时设计很重要#

因为 memory search 是回答链路的一部分。

如果 embedding provider 卡死,整个聊天就会卡死。

所以它必须有:

  • timeout
  • retry(batch 场景)
  • fallback

这就是一个真正可运行系统和 demo 之间的差别。


15. 第十二站:检索不是只有一种,而是 3 种模式#

回到 search() 函数,你会看到后面开始分流。

15.1 模式一:FTS-only#

如果没有 provider:

if (!this.provider) { ... }

对应:

  • extensions/memory-core/src/memory/manager.ts:346

这时它会:

  • 检查 FTS 是否可用;
  • 提取关键词;
  • 对每个关键词做 searchKeyword()
  • 合并、去重、按分数排序。

这意味着:

没有 embedding provider 时,memory search 并不会彻底瘫痪,只要 FTS 可用还能工作。

15.2 模式二:vector-only#

如果 embedding provider 可用,但 FTS 不可用或 hybrid 关闭:

  • 只做向量检索。

15.3 模式三:hybrid#

这是默认最重要的模式。

它会:

  1. searchKeyword(...)
  2. embedQueryWithTimeout(...)
  3. searchVector(...)
  4. 最后 mergeHybridResults(...)

也就是说:

默认 recall 不是“先关键词后向量”的二选一,而是两个都跑,再融合。


16. 第十三站:searchVector() 具体怎么查#

文件:

  • extensions/memory-core/src/memory/manager-search.ts

关键函数:

  • searchVector(...)

位置:

  • extensions/memory-core/src/memory/manager-search.ts:23

16.1 如果 sqlite-vec 可用#

它会走数据库里的向量检索:

  • vec_distance_cosine(v.embedding, ?)

这意味着:

  • queryVec 会转成 blob
  • 在向量表里做 cosine distance
  • 再 join 回 chunks 表

最后把距离转成分数:

score: 1 - row.dist

16.2 如果 sqlite-vec 不可用#

它就退化成:

  • 把所有 chunk embedding 拉出来;
  • 在 JS 里算 cosineSimilarity()
  • 再排序取 top-k。

也就是说:

vector search 有数据库加速路径,也有纯 JS fallback 路径。

这是 OpenClaw 很典型的“可退化工程设计”。


17. 第十四站:searchKeyword() 具体怎么查#

同一个文件里:

  • extensions/memory-core/src/memory/manager-search.ts:139

关键点:

  • 它先 buildFtsQuery(raw)
  • 再对 FTS 表执行 MATCH
  • 使用 bm25(...) AS rank
  • 再把 rank 转成 0~1 左右的 textScore

注意这里不是直接拿 BM25 原始值做最终分数,而是经过:

  • bm25RankToScore(row.rank)

然后统一进入后面的融合步骤。

这一步说明:

  • OpenClaw 不是“把两个结果简单拼一起”;
  • 它有一个明确的 score normalization / merge 思路。

18. 第十五站:hybrid merge 发生在哪里#

回到 manager.ts,关键函数:

  • mergeHybridResults(...)

位置:

  • extensions/memory-core/src/memory/manager.ts:500

这一层会把:

  • vector results
  • keyword results

都转换成统一结构,再带着:

  • vectorWeight
  • textWeight
  • mmr
  • temporalDecay

交给真正的 merge 实现。

这里你要建立一个很重要的直觉:

recall 结果不是“搜到了就直接返回”,而是经过一个排名重建过程后再返回。

所以 RAG 质量不只是看 embedding,好不好很多时候看的是:

  • merge 策略
  • cut-off 策略
  • MMR
  • temporal decay

19. 第十六站:结果是怎么变成最终 tool payload 的#

回到 tools.ts

createMemorySearchTool 里,真正拿到 raw results 后会做这些事:

  1. 解析 citations mode
  2. 判断当前 session 是否应显示 citation
  3. 调用 memory.manager.search(...)
  4. 读取 memory.manager.status()
  5. 对 raw results 做 decorateCitations(...)
  6. 如果是 QMD,按 injected chars 做裁剪
  7. 返回 jsonResult(...)

对应位置:

  • extensions/memory-core/src/tools.ts:41
  • extensions/memory-core/src/tools.ts:52
  • extensions/memory-core/src/tools.ts:57
  • extensions/memory-core/src/tools.ts:58

也就是说:

manager 负责“搜到什么”,tool 层负责“怎么把搜到的东西包装给模型看”。


20. 第十七站:citation 是什么时候加上的#

文件:

  • extensions/memory-core/src/tools.citations.ts

核心函数:

  • resolveMemoryCitationsMode()
  • shouldIncludeCitations()
  • decorateCitations()

20.1 decorateCitations() 做了什么#

它会给每个 result:

  • 生成 citation 字符串
  • 格式像 path#Lx-Ly
  • 再把它 append 到 snippet 后面

对应位置:

  • extensions/memory-core/src/tools.citations.ts:16

具体格式逻辑在:

  • extensions/memory-core/src/tools.citations.ts:30

20.2 为什么 citation 不在 manager 层做#

因为 citation 更像“面向展示和 prompt 的格式化”,不是纯检索引擎逻辑。

manager 层应该尽量只负责:

  • 结果是什么
  • path / line range / snippet / score 是什么

tool 层再决定要不要展示 citation。

这也是分层合理的一个体现。


21. 第十八站:结果到底怎么进入模型“可见范围”#

这点特别容易误解。

很多人会问:

memory_search 返回之后,是不是系统又偷偷把结果写进某个隐藏 prompt 里?

默认最直接的理解应该是:

memory_search 的返回值本身就是 tool result,而 tool result 本来就是模型当前上下文的一部分。

也就是说:

  • 模型调用 tool
  • tool 返回 JSON payload
  • 这个 payload 出现在当前运行的工具结果里
  • 模型随后基于这个结果继续推理或调用 memory_get

所以不一定要有一个专门叫“inject memory into prompt”的函数。

在 OpenClaw 的 agent 体系里:

  • tool result 本身就是 prompt 生态的一部分

21.1 那 chat-transcript-inject.ts 是干什么的#

文件:

  • src/gateway/server-methods/chat-transcript-inject.ts

这个文件的作用更偏向:

  • 向 transcript 里追加一条注入型 assistant message;
  • 保证 parentId chain 不断。

它不是默认 memory_search 主链路的必经步骤,但能帮助你理解:

  • OpenClaw 对“注入型消息”是怎么安全写进 transcript 的。

22. 第十九站:memory_get 为什么是另一条重要支线#

很多新手以为 memory_search 找到结果后,系统就一定把整段大文本塞给模型。

实际上 OpenClaw 给了一个更细的后续动作:

  • memory_get

定义在:

  • extensions/memory-core/src/tools.ts:81

22.1 memory_get 做什么#

它接收:

  • path
  • from
  • lines

然后根据 backend 分两种情况:

builtin backend#

  • 调用 readAgentMemoryFile(...)

非 builtin / status 情况#

  • 通过 manager 的 readFile(...)

这说明:

memory_get 是 recall 之后的“精准读原文”步骤。

它能让模型避免把整个文件无脑读进上下文,而是只读需要的部分。


23. 第二十站:readFile() 最后落到哪里#

在 builtin manager 里,对应:

  • extensions/memory-core/src/memory/manager.ts:673

MemoryIndexManager.readFile(...) 最终调用:

  • readMemoryFile(...)

它会根据:

  • workspaceDir
  • extraPaths
  • 相对路径
  • 起始行
  • 行数

去返回:

  • { text, path }

这一步非常重要,因为它告诉你:

  • recall 的“搜索结果”只是线索;
  • 真正要精读文件,还得走 read path。

这和搜索引擎很像:

  • 搜索结果页 ≠ 原文页。

24. 第二十一站:索引是怎么被建立的#

虽然这篇主线是 query → recall,但你最好顺便知道索引生成逻辑在哪。

关键文件:

  • extensions/memory-core/src/memory/manager-embedding-ops.ts

24.1 indexFile(...)#

位置:

  • extensions/memory-core/src/memory/manager-embedding-ops.ts:589

它会做这些事:

  1. 把文件内容 chunk 化;
  2. 对 chunk 做 embedding;
  3. 生成 chunk id;
  4. upsert 到 chunks 表;
  5. 如果向量表可用,再写 chunks_vec
  6. 如果 FTS 可用,再写 chunks_fts
  7. 更新 files 表记录。

这说明整个 memory-core 默认索引存储其实是一个小型本地检索引擎。

24.2 它还带 embedding cache#

你会看到:

  • embedding_cache
  • loadEmbeddingCache()
  • upsertEmbeddingCache()

这说明 OpenClaw 也考虑到了:

  • 文件稍改一点就全量重 embed 很浪费;
  • 相同 chunk 可复用 embedding。

这对长期使用非常重要。


25. 第二十二站:为什么这条链路值得学#

这套源码链路很适合拿来学习一个真正可运行的 RAG/记忆系统,因为它同时包含:

25.1 工具层#

  • tool schema
  • lazy runtime load
  • agent scope resolve

25.2 后端抽象层#

  • builtin vs QMD
  • fallback manager
  • borrowed manager

25.3 检索层#

  • query embedding
  • vector search
  • keyword search
  • hybrid merge

25.4 结果格式层#

  • citation formatting
  • result shaping
  • json payload

25.5 读取层#

  • search-first
  • read-later

这几层拼起来,基本就是一个现代 agent memory/RAG 体系的缩影。


26. 你读源码时最值得盯住的数据结构#

如果你接下来要真正逐行看代码,我建议重点关注这些“数据形状”:

26.1 tool input#

MemorySearchSchema

  • query
  • maxResults
  • minScore

位置:

  • extensions/memory-core/src/tools.shared.ts:21

26.2 search result#

结果里最核心的字段通常是:

  • path
  • startLine
  • endLine
  • score
  • snippet
  • source

这些字段是你理解 recall payload 的关键。

26.3 tool output#

memory_search 最后会返回:

  • results
  • provider
  • model
  • fallback
  • citations
  • mode

位置:

  • extensions/memory-core/src/tools.ts:65

26.4 read result#

memory_get 最终是:

  • { path, text }

这是 search → read 两阶段设计的第二阶段数据形状。


27. 你可以自己做一个“手工追踪练习”#

如果你真的想把这套源码吃透,我建议你亲手做这条练习:

练习题#

假设模型发起:

{
"query": "我们之前关于 memory flush 的讨论是什么?",
"maxResults": 3,
"minScore": 0.35
}

你就按下面顺序追:

  1. createMemorySearchTool() 收到参数后在哪里读 query
  2. getMemoryManagerContext() 如何拿到 manager
  3. getMemorySearchManager() 为什么默认落到 builtin
  4. MemoryIndexManager.search() 是怎么决定 hybrid 路线的
  5. embedQueryWithTimeout() 在什么时候被调用
  6. searchKeyword()searchVector() 分别返回什么
  7. decorateCitations() 如何修改 snippet
  8. jsonResult(...) 最终包出来是什么样子

只要你能完整回答这 8 步,你就真的掌握默认 memory-core recall 主线了。


28. 一个非常重要的工程理解:谁负责“事实”,谁负责“展示”#

这套源码最值得你学习的一个点,是职责分离做得很清楚。

28.1 manager 负责“事实”#

manager 层关心:

  • 有没有结果
  • score 是多少
  • path / line range 是多少
  • 哪个 provider
  • 哪种 backend

28.2 tools 层负责“展示给模型看”#

tools 层关心:

  • citation 要不要加
  • snippet 怎么包装
  • JSON payload 长什么样
  • 出错时 warning / action 怎么提示

28.3 这为什么重要#

因为以后你自己设计聊天 AI 时,也会反复碰到这类问题:

  • retrieval engine 不该掺太多 UI / prompt 格式逻辑;
  • tool/result formatting 也不该污染底层检索逻辑。

OpenClaw 在这点上是个不错的参考。


29. 再给你一个最终压缩版链路#

如果以后你忘了,可以只记这 12 步:

  1. memory_searchtools.ts 被定义
  2. tools.shared.ts 解析 agent/context
  3. loadMemoryToolRuntime() 懒加载 runtime
  4. tools.runtime.ts 导出 manager / file runtime 能力
  5. memory/index.ts 暴露 getMemorySearchManager
  6. search-manager.ts 决定 builtin 还是 QMD
  7. 默认落到 MemoryIndexManager.get()
  8. MemoryIndexManager.search() 进入 recall 主流程
  9. embedQueryWithTimeout() 得到 query vector
  10. searchKeyword() + searchVector() 得到候选
  11. mergeHybridResults() 重排融合
  12. decorateCitations() + jsonResult() 返回给模型

接着如果模型要精读:

  1. memory_get
  2. readAgentMemoryFile()manager.readFile()
  3. 返回 { path, text }

这就是默认 memory-core 从 query 到 recall result 的完整主线。


30. 最后总结#

如果第一篇解决的是:

  • “OpenClaw 的长期记忆是什么?”

那这一篇解决的就是:

  • “OpenClaw 的默认 memory-core 在代码里到底怎么跑?”

你现在应该已经能建立这样一个理解:

memory_search 不是一个孤零零的函数,而是一条跨越 tool 层、runtime 桥接层、backend 选择层、manager 层、embedding 层、检索层、结果格式层的完整调用链。

这个理解一旦建立起来,你之后再看:

  • QMD
  • session memory
  • memory flush
  • memory-lancedb

就会轻松很多,因为你已经抓住默认主干了。


31. 建议你下一篇继续看什么#

如果你还想继续,我最推荐的第三篇题目是:

《OpenClaw memory flush 源码精读:它为什么不是简单的“刷盘”,而是上下文压缩前的记忆沉淀机制》

这一篇会专门讲:

  • compaction 和 memory flush 的关系
  • token threshold 怎么算
  • silent turn 是怎么触发的
  • 为什么这个设计对长期 agent 很关键
OpenClaw memory-core 源码精读
https://blog.mengh04.top/posts/openclaw-memory-core-source-walkthrough/
作者
mengh04
发布于
2026-03-28
许可协议
CC BY-NC-SA 4.0