乐于分享
好东西不私藏

OpenClaw 记忆系统探索,memory-core 插件的FTS文本检索

OpenClaw 记忆系统探索,memory-core 插件的FTS文本检索

大家好,继续和大家分享对 OpenClaw 的记忆系统的学习探索。

之前文章中和大家聊过了 MEMORY.md 这个记忆系统中最直观的一个机制。如果需要了解,可以先看看这篇内容:

为什么你的 OpenClaw 总是记不住? 记忆系统探索之 MEMORY.md

这次我们来聊记忆系统的另一个模块, memory-core 插件。它实现记忆功能也分了好几个子系统,我们这次先聊 FTS 全文检索这套流程。

memory 目录

我们前面聊过,workspace 中,会有一个 MEMORY.md 文件, 用于存放可以直接注入到系统提示词的记忆内容。

除了这个文件之外, 如果你用 OpenClaw 一段时间之后, 应该会发现在 workspace 中,还有一个 memory 目录。这里面存放了很多日期命名的 md 文件, 为了更好理解,带上了完整的路径,以及 ~/.openclaw/workspace/MEMORY.md 作为位置参考:

~/.openclaw/workspace/MEMORY.md~/.openclaw/workspace/memory/2026-06-18.md~/.openclaw/workspace/memory/2026-06-19.md~/.openclaw/workspace/memory/2026-06-23.md

workspace/memory 目录中的文件是 OpenClaw 会自动写入的另一组长期记忆,至于这些文件怎么自动写入的,我们以后再详细展开,我们这次只说 memory-core 怎么去用它们。但是如果你的 OpenClaw 刚配置不久,可能 memory 目录的中文件并不存在。

另外, memory 目录中的这些 md 文件,大多数是不会注入系统提示词的。 这点和 MEMORY.md 不同。

memory_search 和 memory_get

那么我们继续往下聊。 既然不注入提示词,怎么让模型知道这些记忆呢。 这就需要通过 memory-core 提供的两个 Tool Call: memory_search 和 memory_get

首先要确保你的 OpenClaw 开启了 memory-core 插件。 如果开启状态下,用这个 slash 命令是可以看到这两个 tool 的:

/context list

关于这个 slash 命令,前面有专门的文章和大家聊过:OpenClaw 用 context 命令快速了解 AGENTS.md 状态

这两个 Tool Call 是否会被使用,是由你的语言模型来决定的。

memory_search 是给定查询关键词或者语句,去找到相关的记忆内容。 memory_get 是给出明确的文件位置,来获取记忆内容

大多数情况下,模型会使用 memory_search。

它的代码定义按照当前 OpenClaw 版本, 是在 extensions/memory-core/src/tools.ts 文件中 createMemorySearchTool 这个函数, 大家感兴趣的话,可以自己去看代码,这里就不给大家贴出来了。

只把这个 tool 的描述部分给大家看一下, 这个是给模型的使用指导:

Mandatory recall step: semantically search MEMORY.md + memory/*.md (and optional session transcripts) before answering questions about prior work, decisions, dates, people, preferences, or todos. Optional corpus=wiki or corpus=all also searches registered compiled-wiki supplements. corpus=memory restricts hits to indexed memory files (excludes session transcript chunks from ranking). corpus=sessions restricts hits to indexed session transcripts (same visibility rules as session history tools). If response has disabled=true, memory retrieval is unavailable; you must tell the user and include the warning/action guidance.

下面是几个参数定义:

const query = readStringParam(rawParams, "query", { required: true });const maxResults = readPositiveIntegerParam(rawParams, "maxResults");const minScore = readFiniteNumberParam(rawParams, "minScore");const requestedCorpus = readStringParam(rawParams, "corpus"as| "memory"| "wiki"| "all"| "sessions"| undefined;

我就不过多给大家解释了,感兴趣可以让你自己的 AI 模型帮你解释。 这里面唯一必须的参数就是 query, 也就是查询关键词, 或者也可以说是查询语句。

SQLite 数据库

我们知道了是通过 memory_search 这个 Tool Call 来查找记忆内容。 但是有一点要注意,它并不是直接去搜索那几个 memory 目录中的文件,而是需要先把这些原始的 md 记忆内容,索引到一个本地的 SQLite 数据库中。默认情况下,memory_search 会同时使用向量语义检索和 FTS 全文检索,再把两边的结果合并后返回。

如果你的 OpenClaw 有用过一段时间后,并且 memory-core 这个插件是开启的,那么你应该能在这个位置看到这个 SQLite 数据库文件,还是为了大家方便理解,我也给了前面几个路径作为参照:

~/.openclaw/workspace/MEMORY.md~/.openclaw/workspace/memory/2026-06-18.md~/.openclaw/memory/main.sqlite

大家注意第三行就行了, 前两个是作为参照。 就是和我们 workspace 目录同级,还有一个 memory 文件夹, 这里面就是存放 SQLite 数据库的地方。 这个数据库里面放的是原始的 md 文件经过索引后的内容,而 memory_search 工具查找信息, 是从这里查找的。

这个索引机制是用 SQLite 的 FTS 扩展,是一种全文检索的机制,这个就不展开讲了,大家有兴趣可以去看 SQLite 官方的介绍:https://sqlite.org/fts5.html

简单来说,用 FTS 建立全文索引后,可以明显提高关键词检索的效率。至于最终的搜索效果,还会受到分词、查询内容、向量检索和结果排序等因素影响。

这里不展开讲,不过有几个小细节可以和大家提一下。 OpenClaw 会把 md 文档按照行数或者预估的 token 数量,切成若干个 chunk, 就是说一篇文档,在数据库中不是以文档为单位存储的,而是 chunk,一个 chunk 实际上是一个文档中一部分内容。当然,这个 chunk 机制是 OpenClaw 自己的行为,和 FTS 无关,不过在这里确实是这个流程的一个环节。

另外,SQLite FTS 默认的分词器是 unicode61, 这个分词器对非英文,或者说非空格划分词汇的语言支持的不算好。 当然也有一些简单的办法去解决,这个以后也会和大家聊。

索引数据库如何建立

现在我们知道了, 记忆系统真正能够实现搜索的地方是这个 SQLite 索引数据库, 并不是我们原始的 md 文件。那么我们可以继续聊一下,我们 memory 目录的原始 md 文件, 是怎么放到索引数据库的。是什么时候发生的。

这里的索引创建和更新跟大家简单先说一下,就是读取 md 文件,把文件的内容切 chunk 分段。 然后把每个分段写进 SQLite 的 FTS 数据表中。这是口语化的表述,实际情况当然细节还会更多,但是为了大家好理解,这样说应该更合适。

主要的一个机制是文件监听。指定监听的文件和目录发生改变的时候,对改变的文件重新进行上述的索引操作。

把一些关键代码跟大家贴一下。(注意是当前 OpenClaw 版本的,不保证以后不会变。)

extensions/memory-core/src/memory/manager-sync-ops.ts 文件中的 ensureWatcher 方法,这里边初始化了三个监听路径:

const fileWatchPaths = new Set<string>([path.join(this.workspaceDir, "MEMORY.md")]);const dirWatchPaths = new Set<string>([path.join(this.workspaceDir, "memory")]);const additionalPaths = normalizeExtraMemoryPaths(this.workspaceDir, this.settings.extraPaths);

前两个路径大家应该知道了, 一个是 MEMORY.md,另一个是 memory 目录。 还有一个是 extraPaths, 这是我们可以设置的额外路径,比如你自己的外部知识库路径。

然后这里边注册了一些 watcher 事件:

this.watcher.on("add", markDirty);this.watcher.on("change", markDirty);this.watcher.on("unlink", markDirty);this.watcher.on("unlinkDir", markDirty);

以上几个文件操作发生的时候,都会触发。 触发之后,不会马上进行重新索引,写入数据库。而是等待一个 1.5秒的防抖机制。常量定义在这里:

src/agents/memory-search.ts

const DEFAULT_WATCH_DEBOUNCE_MS = 1500;

简单描述就是,我们文件修改完之后, 不会马上执行重新索引操作,要等待 1.5 秒后才执行,如果这 1.5 秒内, 监听的几个目录中又有内容被修改了,就会重新计时。 直到 1.5 秒内,没有任何更改了,才会把前面所有的修改,提交一次重新索引。

这里可以得出一个观察,就是我们自己手动去编辑 memory 目录中的 md 文件,也会触发重新索引这套机制。

我们可以验证一下。 打开 debug 日志输出,然后修改 md 文件,就会看到类似这样的日志, 说明自动索引在起作用:

memory sync: indexing memory files

有一点要注意,就是这个文件监听 watcher 子程序,不是随着 OpenClaw gateway 一起启动的,它是要在我们使用一次 memory-core 相关能力后才启动的,比如执行一次 memory_search Tool Call。

就是说,如果你已经开启了 debug 日志显示,修改文件后,还是看不到这条日志内容,很可能就是你的 watcher 子程序没启动, 你可以试着运行一次 memory_search tool,或者用一些其他的命令行触发一下它的启动, 比如:

/tool memory_search query="xxx"

上面这个不是标准的 slash 命令,但是这样写,模型会理解到我们就是要做一次 tool call。 这样 watcher 子程序也会一起启动,我们随后再修改 md 文件,就能触发重新索引了。

总结

这次和大家把 memory-core 插件关于文件索引这块的流程分享了一下。 很多细节并没有展开太多,不过应该也是把这套子系统的关键流程说清楚了。 即使这样,篇幅也不算太小了。 更多的细节大家可以自己去探索,也欢迎大家互动。

memory-core 的记忆功能不只是 FTS 文件索引这一点东西,还有很多其他的机制,后面还会跟大家继续讨论。 希望对你有帮助。

提醒:文章内容都是基于当时的日期的运行环境,软件版本等,作为思考和学习过程的分享,给大家提供思路。但由于行业技术的更新速度非常快,如果你是距离文章发布比较长的时间看到的,很可能你当前的各种运行环境和文章中提到的会有差异,有些情况下差异还可能很大。所以请大家保持验证信息的习惯,以你当前实际环境的运行结果和官方文档为准。