Skip to content
Go back

OpenClaw 是如何记住东西的?

实现记忆系统的挑战

LLM 本质上是 stateless 的。你给它一段 prompt,它返回一段 completion,然后什么都忘了。下一次请求进来,模型不知道你是谁,不知道上次聊了什么,更不知道三天前你让它记住的那个 API key 格式。

Context window 是唯一的「短期记忆」,但它有硬性的 token 上限。Claude 是 200K,GPT-4o 是 128K,听起来很大,但实际用起来填得很快——Agent 调一次浏览器工具返回的 DOM 就可能吃掉几万 token。

所以核心问题是:怎么让一个没有记忆的东西,表现得好像有记忆?

展开来看,这个问题至少包含这几层:

  1. 短期记忆的容量管理。对话越长 context 越满,满了就得压缩或截断,但压缩会丢信息。怎么在「记住重要的」和「腾出空间」之间取舍?
  2. 长期记忆的持久化。跨会话的信息——用户偏好、项目约定、历史决策——必须存到某个地方。存到哪?怎么存?存完了怎么高效找回来?
  3. 检索的准确性。存了一万条记忆,用户问了一个问题,你得从里面捞出最相关的几条注入 context。捞错了比不捞更糟——占了 token 不说,还可能误导模型。
  4. 写入的时机。靠模型自己「意识到」该存东西然后主动调工具?还是系统自动在后台捕获?前者依赖模型能力,后者容易存一堆垃圾。

这几个子问题互相纠缠,没有银弹。接下来看看目前常见的方案都是怎么处理的。


常见的 Memory 实现方案

目前 AI Agent 领域的记忆方案,大致可以分成这么几类:

纯 Context Window 流

最简单的做法——把历史对话全塞进 context。不做任何持久化,依赖 context window 本身当记忆。ChatGPT 最早就是这么干的。

优点是实现简单,不需要额外基础设施。缺点也很明显:context 满了就得截断,早期信息直接丢失;跨会话记忆完全不存在。

向量数据库 RAG 流

主流方案。把对话或文档切成 chunk,通过 embedding 模型转成向量存到 Pinecone、Weaviate、Milvus 之类的向量数据库里。每次用户提问时,先检索 top-k 最相关的 chunk 注入 context,再让模型回答。

这个方案的问题在于:向量检索是模糊匹配,精确查找弱(比如「我三月五号定的那个规则」这种查询向量很难命中);chunk 切分策略直接影响效果,切太细丢上下文,切太粗检索不准;而且向量数据库是个黑盒,用户没法直接看到或编辑里面存了什么。

Mem0 / MemGPT 流

更结构化的方案。Mem0 在对话过程中自动抽取结构化的 memory entity(偏好、事实、决定),带类别标签和时间戳,独立于对话历史存储。MemGPT 则模拟操作系统的虚拟内存,搞了个 main context / archival storage 的两级架构,由模型自己决定什么时候把信息从「内存」搬到「磁盘」。

这类方案比纯 RAG 更智能,但代价是复杂度高,依赖额外的基础设施,而且抽取和检索的质量强依赖模型能力。

本地文件流

这就是 OpenClaw 走的路——用本地文件系统当 source of truth。不依赖云端向量数据库,所有记忆以明文 Markdown 存在本地磁盘上。

为什么 OpenClaw 选了一条看起来最「土」的路?这跟它的产品定位有关。


OpenClaw 爆火背后的原因

OpenClaw 最早叫 Clawdbot,是 Claude 模型的一个本地封装,跑在 WhatsApp 上当聊天机器人。后来因为商标问题改名 Moltbot,最终定名 OpenClaw。

名字换了两轮,架构也跟着彻底解耦——从绑定 Claude 的 chatbot 变成了一个模型无关的 Agent 网关,能桥接 WhatsApp、Telegram、Discord、Slack、iMessage,还给 Agent 开了文件读写、Shell 执行、浏览器、日历等系统级权限。

OpenClaw 爆火的核心原因,我觉得是两点:

第一,它是 local-first 的。 所有数据——对话记录、记忆文件、配置——全在你本地机器上。不经过任何第三方服务器(除了 LLM API 本身)。在大家越来越在意 data sovereignty 的今天,这一点杀伤力很大。

第二,它足够透明。 你用文本编辑器就能打开 Agent 的「大脑」看它记住了什么。不需要数据库客户端,不需要 API 调用,cat ~/.openclaw/workspace/MEMORY.md 就行了。甚至可以直接手动编辑来纠正 Agent 的记忆。

这个「透明」和「本地」的产品哲学,直接决定了它的记忆系统为什么用 Markdown 文件而不是向量数据库。接下来具体看它是怎么实现的。


OpenClaw 记忆实现机制

短期记忆

OpenClaw 的短期记忆有两个层面。

第一层是 context window 本身。 和所有 LLM 应用一样,当前会话的对话历史就在 context 里。这没什么特别的。

第二层是按日轮转的工作记忆文件 memory/YYYY-MM-DD.md 这是个 append-only 的日志文件,Agent 在对话中产生的即时决策、工具输出、日常活动都会追加到里面。系统启动时自动加载「今天」和「昨天」的文件进 context——相当于给 Agent 一个「昨天和今天发生了什么」的速览。

按日轮转是个很务实的设计:防止单文件无限膨胀,同时提供了一个天然的时间窗口。前天以前的工作记忆不会自动加载,需要时通过检索找回。

但关键问题在于:context window 快满的时候怎么办?

OpenClaw 在这里做了一个我觉得很巧妙的东西:Pre-Compaction Memory Flush(预压缩内存刷新)。

当 context 的 token 数逼近上限时,系统必须执行 compaction——用小模型把早期对话压缩成摘要来腾空间。但如果 Agent 刚在思维链中做了个关键决策,还没来得及写入磁盘,压缩一来这些信息就永久丢了。

所以 OpenClaw 在压缩之前,会强制插入一个不可见的「静默轮次」(Silent Agentic Turn)。系统向 Agent 注入一段紧急指令:「会话即将压缩,立即把所有有价值的决策和事实写入 memory/YYYY-MM-DD.md。没什么要保存的就回复 NO_REPLY。」

NO_REPLY 会被网关拦截吞掉,用户完全无感——只觉得 Agent 某次回复多想了几秒。

触发的阈值是动态计算的:

阈值 = 模型 context 上限 - 安全预留(默认 20,000 token) - 软性缓冲(默认 4,000 token)

这个机制理念上很漂亮,但实际有几个已知的 bug。比如触发判断用的是上一轮的 token 快照,如果当前轮次工具返回暴涨几万 token,会直接跳过刷新撞上 API 上限;还有个计数器 bug 导致刷新「隔次生效」——执行、跳过、执行、跳过,可靠性减半。这些问题在 GitHub issues 里都有记录,社区还在修。

长期记忆

长期记忆的核心设计思想就是一句话:Markdown 就是数据库。

模型只有在把信息物理写入磁盘后,才算「真正记住了」。RAM 里的都是瞬态 context,随时可能被压缩掉。

工作区(~/.openclaw/workspace)里有几个关键文件:

文件用途
MEMORY.md经过模型提炼的稳定知识——用户偏好、项目约定、长期目标。动态覆写,只在私有会话中加载
USER.md / SOUL.md身份和规则基线。定义 Agent 的人格、行为底线。每次启动作为最高优先级注入 system prompt
sessions/YYYY-MM-DD-<slug>.jsonl对话原始记录。JSONL 格式,用于回放和追溯,不直接进 context

OpenClaw 通过 tool calling 给模型暴露了两个记忆工具来操作这些文件:

  • memory_search:语义检索。返回截断到约 700 字符的摘要片段,附文件路径、行号、相关性得分。不返回完整文件,防止撑爆 context。
  • memory_get:读取指定路径的文件或行号范围。如果文件不存在,不抛异常,返回 { text: "", path }——把「文件不存在」转化为「尚无记录」,LLM 可以直接进入创建流程。

但纯文本没法高效检索。所以 OpenClaw 在 Markdown 之上建了一层 SQLite 索引作为加速器。每个 Agent 有独立的 .sqlite 文件,里面有五张表:

  • files:记录文件路径和内容 hash,做增量同步——hash 没变就跳过
  • chunks:Markdown 被切成约 400 token 一段(80 token 重叠)的文本块
  • embedding_cache:基于 SHA-256 对 chunk 去重,避免重复调 embedding API
  • chunks_fts:SQLite FTS5 虚拟表,倒排索引,支撑 BM25 词法检索
  • vec_chunks:sqlite-vec 虚拟表,存 embedding 向量,支持原生 SQL 做余弦相似度

检索时,向量搜索和 BM25 并发执行,用 RRF(Reciprocal Rank Fusion) 按排名融合。然后过两道后处理:MMR 做去重(防止大量语义重复的 chunk 占满 token 配额),时间衰减保证最新的信息优先(类似记忆遗忘曲线,越旧的记忆得分衰减越大)。

向量和关键词两条路互补:向量擅长模糊语义匹配,BM25 擅长精确命中函数名、配置键值、错误码这类东西。

嵌入模型方面,系统有一条自动回退链:本地 GGUF 模型 → OpenAI / Gemini / Voyage / Mistral 远端 API → 纯 BM25 降级。所有向量通道都挂了也不报错,直接回退到纯词法搜索。

其它

除了上面的核心机制,还有几个值得一提的东西:

插件化。 OpenClaw 把记忆机制抽象成了一个排他性的插件插槽(plugins.slots.memory)。默认的 memory-core 坚持「被动调用」——所有写入必须由模型主动思考后显式调工具。社区还有 memory-lancedb 系列,走完全相反的路:auto-capture(后台自动抽取结构化记忆)+ auto-recall(回复前自动检索注入)。有企业压测显示后者让大跨度任务完成率涨了 43%,token 成本降了 91%——不过透明度和可控性就没了。

QMD 后端。 对于知识库特别大的用户(比如几千篇 Obsidian 笔记),内置的 SQLite 检索可能不够用。QMD 是一个实验性的独立搜索引擎,作为子进程运行,自带查询扩展和 Cross-Encoder 重排。但默认配置有个坑——搜索模式被锁定在了纯 BM25,而 LLM 倾向于生成自然语言长句查询,BM25 对这类输入基本全 miss。得手动改成 searchMode: "query" 才能真正用上混合语义检索。

安全隐患。 MEMORY.mdSOUL.md 每次启动时无条件注入 system prompt。这意味着本地 Markdown 文件实质上就是「可执行的内存代码」。如果攻击者能诱导 Agent 在预压缩刷新时把恶意指令写入这些文件,这个指令就能跨重启永久驻留——经典的 Prompt Injection 持久化攻击。社区为此有 SecureClaw 等审计插件做防线。


如果让我实现一个记忆系统我会怎么做?

研究完 OpenClaw 之后,我回过头来想了想:如果从零开始做一个 Agent 记忆系统,我会怎么设计?

存储层我还是会选文件。 OpenClaw 用 Markdown 这个决定看起来「土」,但仔细想想真的很务实。文件系统是最通用的持久化方案,不需要额外的数据库服务,git 天然支持版本控制,用户可以直接审查和编辑。对于个人和小团队场景,这些好处远超向量数据库的检索优势。

但我会更早引入结构化。 OpenClaw 的 Markdown 文件基本是自由格式的,模型写什么就是什么。我更倾向于在写入时就做结构化——类似 Mem0 的 entity 抽取,给每条记忆打上类别(偏好、事实、决定、待办)和时间戳。这样检索时不纯靠语义匹配,可以做 structured query。

写入时机我会混合。 纯靠模型主动写太不靠谱了——长对话中模型经常忘记调工具。纯自动捕获又容易存一堆噪声。我的想法是:关键事件(用户显式说「记住这个」、Agent 做了重大决策)走自动捕获,其他的靠模型主动调工具。预压缩刷新是个好 idea,我会保留,但触发逻辑需要更稳——OpenClaw 那几个竞态 bug 说明基于 token 估算的阈值机制太脆弱了。

检索我会做分层。 短期用 context window + 近两天的日志文件(这个 OpenClaw 已经做了)。中期用全文搜索。长期用向量 + 结构化查询。三层之间按时间和相关性动态混合。

事务和一致性必须从第一天做。 OpenClaw 的 SQLite 层没有事务保护,进程崩溃会导致数据撕裂。这种基础设施层面的问题越晚修成本越高。哪怕用 SQLite,BEGIN TRANSACTION 不花多少力气。

总的来说,OpenClaw 的大方向我很认同——本地优先、文件优先、逐层加速。具体的工程实现细节有不少可以改进的空间,但这正好说明:做一个靠谱的记忆系统,真的很难。 每一个看起来简单的设计决策背后都有一堆 tradeoff。


Key Takeaway

  1. LLM 的记忆问题本质是「无状态 + 有限 context」的矛盾。 所有方案都在围绕这个矛盾做文章。
  2. OpenClaw 选择了「文件优先」的路线。 所有长期记忆以 Markdown 明文存储在本地,不依赖云端向量数据库。好处是透明、可审计、用户有完全的 data sovereignty。
  3. 短期记忆靠 context window + 按日轮转的工作记忆文件。 预压缩刷新机制在 context 快满时自动把重要信息落盘,是整个系统最精妙的设计。
  4. 长期记忆靠 SQLite 加速的混合检索。 向量搜索和 BM25 并行,RRF 融合,MMR 去重,时间衰减。嵌入模型有完整的回退链,离线也能用。
  5. 工程实现上有不少已知问题。 事务缺失、缓存泄漏、竞态 bug。但这些问题恰好说明记忆系统的实现难度——每一层都有坑。
  6. 没有银弹。 被动写入(靠模型主动调工具)和主动捕获(后台自动抽取)各有利弊。OpenClaw 默认走被动路线,插件生态提供了主动路线的选项。两条路的 tradeoff 是透明度和可控性 vs. 自动化和命中率。