1. 长会话跑着跑着就“失忆”,问题出在哪
如果你用 OpenClaw 跑过超过几十轮的长会话,大概率遇到过这种场景:前面聊过的技术选型、约定好的文件路径、甚至上一轮刚确认过的接口参数,到了后面模型突然“不记得”了。你以为是模型上下文不够大,换了 200k 窗口的模型,结果还是会在某个节点开始丢信息。
我试过把会话拉到 100 多轮,观察 OpenClaw 的记忆流转日志,才把这件事理清楚。OpenClaw 的记忆系统不是单一缓存,而是Session、Memory、向量库三层介质在配合:Session 是磁盘上的 JSONL 会话记录,Memory 是 workspace 下的 Markdown 持久记忆,向量库是 LanceDB 做的混合检索层。三者通过 hook 在压缩前后异步流转,任何一环配置不对,长会话就会“断片”。
这篇聚焦一个具体问题:长会话场景下,向量库如何写入与召回、Memory 如何跨 Session 持久化、Session 生命周期如何触发记忆更新。我会给出可复制的配置片段、Memory 读写接口调用示例,以及通过日志和检索结果确认记忆流转是否生效的检查步骤。适合已经在用 OpenClaw 做长任务、或者准备把 OpenClaw 接入自己 Agent 工作流的开发者。读完你能自己判断:记忆到底写没写进去、召回有没有命中、Session 压缩时该用 async 还是 await。
核心检索词先摆出来:OpenClaw 记忆系统、向量库写入与召回、Memory 跨 Session 持久化、Session 生命周期触发记忆更新。这四个词贯穿全文,后面每个环节都会落到可验证的动作上。
先说结论性的机制,方便你带着预期往下看。Session 的 transcript 是同步 append到磁盘的,位置在~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl,运行时通过before_prompt_buildhook 从文件系统加载历史。Memory 的写入是异步的,由before_compaction和after_compaction两个 hook 触发 flush,写入memory/YYYY-MM-DD.md和MEMORY.md。向量库同步也是异步,由postCompactionForce控制是off、async还是await。这三层的同步/异步属性不一样,正是长会话丢信息的根源——你以为写进去了,其实异步任务还没跑完,下一轮检索自然召回不到。
下面按“原问题场景 → 前置准备 → 可复制配置 → 验证请求 → 常见错排查 → 接入入口”的顺序展开。每一节都尽量给到能直接粘贴的命令或配置,而不是停留在概念解释。
2. 前置准备:TaoToken 接入与 OpenClaw 记忆目录确认
在动记忆系统之前,得先把模型调用链路打通,否则你连压缩摘要都生成不了,更别提 Memory flush。OpenClaw 本身是 Pi 框架上的智能体应用,模型侧我用的是 TaoToken 的兼容接口,Base URL 指向https://taotoken.net/api,Key 在控制台生成。这一步不是注册教程,重点是把三个件配齐:Base URL、API Key、Model ID,缺一个都会在压缩阶段报 401。
先确认你的 OpenClaw 工作目录结构。记忆相关的路径有三个,建议先ls一遍,心里有数:
# 会话 transcript 目录(按 agentId 分) ls -la ~/.openclaw/agents/<agentId>/sessions/ # 向量库文件(LanceDB,SQLite 底座) ls -la ~/.openclaw/memory/ # workspace 下的 Memory 文件 ls -la <workspace>/memory/ ls -la <workspace>/MEMORY.md如果~/.openclaw/memory/下没有<agentId>.sqlite,说明向量库还没初始化,第一次写入 Memory 后才会生成。memory/目录下如果只有空的MEMORY.md,也正常,flush 触发后才会出现YYYY-MM-DD.md。
模型侧配置我放在 OpenClaw 的 agent 配置里,关键字段是baseUrl、apiKey、model。TaoToken 的接入文档里有完整的字段说明,我按它的格式填:
{ "agents": { "defaults": { "model": { "provider": "openai-compatible", "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的Key", "model": "claude-sonnet-4-5" } } } }这里有个容易踩的点:baseUrl结尾不要带/v1,TaoToken 的兼容层会自动补路径。我第一次填了/api/v1,结果压缩摘要请求一直 404,日志里能看到POST /api/v1/v1/chat/completions这种重复路径。改成https://taotoken.net/api就正常了。
Key 的生成入口在控制台的 API Keys 页面,模型对话可以在线验证模型是否通。这两个入口我放在文末 CTA 里,这里先不展开。前置准备的核心就一句话:模型链路通了,记忆系统的异步任务才有意义,否则 flush 阶段生成摘要会直接失败,Memory 文件永远是空的。
另外确认一下 OpenClaw 版本,记忆系统的 hook 名称在不同版本有差异。before_compaction/after_compaction是当前稳定版的命名,老版本可能叫on_compact。用openclaw --version看一眼,低于 0.9 的建议先升级,否则下面的配置片段对不上。
3. 可复制配置:向量库连接参数与 Memory 读写接口
这一节是全文最实操的部分,给三块配置:向量库连接参数、Memory flush 的 hook 配置、以及 Memory 读写接口的调用示例。每一块都能直接粘贴,路径和字段名跟 OpenClaw 源码保持一致。
3.1 向量库连接参数(LanceDB)
向量库的配置在agents.defaults.memory下,核心是vectorStore段。LanceDB 的路径默认是~/.openclaw/memory/<agentId>.sqlite,检索模式是 BM25 + 向量的混合搜索。下面这段 JSON 可以直接放进 agent 配置:
{ "agents": { "defaults": { "memory": { "vectorStore": { "type": "lancedb", "path": "~/.openclaw/memory/${agentId}.sqlite", "embeddingModel": "text-embedding-3-small", "hybridSearch": { "enabled": true, "bm25Weight": 0.3, "vectorWeight": 0.7 }, "topK": 8, "minScore": 0.35 }, "postCompactionForce": "async" } } } }postCompactionForce这个字段值得单独说。它控制压缩后向量库同步的模式,三个取值:off禁用同步、async不等待、await等待完成。长会话场景我建议先用async,压缩延迟低;如果你发现召回经常漏掉刚写入的记忆,再改成await,代价是每轮压缩会多等几百毫秒到一两秒。
hybridSearch的权重也影响召回质量。BM25 权重高,偏向关键词精确匹配,适合代码、路径、ID 这类不透明标识符;向量权重高,偏向语义相似,适合自然语言描述的任务背景。我实测下来 0.3/0.7 是个比较稳的起点,代码类任务可以把 BM25 提到 0.5。
3.2 Memory flush 的 hook 配置
Memory 的写入由压缩 hook 触发,配置在agents.defaults.compaction下。这里同时涉及压缩模式和自定义提示词:
{ "agents": { "defaults": { "compaction": { "mode": "safeguard", "customInstructions": "用中文摘要,保留所有代码细节、文件路径和接口参数。", "identifierPolicy": "strict", "recentTurnsPreserve": 3, "memoryFlush": { "enabled": true, "targetFile": "memory/YYYY-MM-DD.md", "appendOnly": true, "readOnlyFiles": ["MEMORY.md", "SOUL.md", "TOOLS.md", "AGENTS.md"] } } } } }mode: safeguard会启用compaction-safeguard扩展,拦截session_before_compact事件,把customInstructions注入到摘要生成流程。identifierPolicy: strict对应源码里的标识符保留策略,确保 UUID、hash、API Key、文件路径这些不被摘要“重构”掉——这点对长会话特别重要,模型一旦把路径改写了,后续工具调用就会失败。
memoryFlush段对应源码里的DEFAULT_MEMORY_FLUSH_PROMPT,appendOnly: true保证只追加不覆盖,readOnlyFiles列出 flush 期间视为只读的引导文件。这几个字段跟源码里的MEMORY_FLUSH_APPEND_ONLY_HINT和MEMORY_FLUSH_READ_ONLY_HINT一一对应。
3.3 Memory 读写接口调用示例
除了 hook 自动 flush,你也可以通过 memory-plugin API 主动读写。下面是一个 Node 侧的调用示例,展示写入一条带 importance 和 category 的记忆,然后立刻检索:
// memory-write-read.mjs import { MemoryPlugin } from "@openclaw/memory-plugin"; const memory = new MemoryPlugin({ agentId: "default", vectorStorePath: "~/.openclaw/memory/default.sqlite", }); // 写入一条持久记忆 const entry = await memory.store({ text: "项目使用 pnpm workspace,构建命令是 pnpm -r build,产物在 packages/*/dist", importance: 0.9, category: "project-config", }); console.log("written:", entry.id, entry.createdAt); // 立刻检索,验证写入是否可召回 const results = await memory.search({ query: "构建命令是什么", topK: 3, minScore: 0.3, }); for (const r of results) { console.log(r.score.toFixed(3), r.text.slice(0, 60)); }store返回的entry里包含id、text、vector、importance、category、createdAt。注意源码里 LanceDB 存的是完整 MemoryEntry,不是只存向量索引,所以text字段是原文,检索命中后可以直接注入 prompt,不需要回查 Markdown 文件。
search走的是混合搜索,返回结果按 score 排序。如果results为空,先别怀疑代码,去看~/.openclaw/memory/default.sqlite文件大小有没有变化,以及日志里有没有memory sync skipped的警告。
4. 验证请求:用日志与检索结果确认记忆流转生效
配置写完不算完,得验证。这一节给三个验证动作:看 Session transcript 是否同步写入、看 Memory flush 是否触发、看向量库召回是否命中。每个动作都有具体的命令和预期输出。
4.1 验证 Session transcript 同步写入
Session 的 transcript 是同步 append 的,所以发一条消息后立刻看文件,应该马上能看到新行。用tail -f跟一下:
tail -f ~/.openclaw/agents/default/sessions/<sessionId>.jsonl然后在 OpenClaw 里发一条消息,比如“记住:部署环境是 staging,端口 8080”。预期是文件里立刻多出一行 JSON,包含role、content、timestamp。如果等了几秒还没写入,说明before_message_writehook 有问题,检查插件是否加载。
这一步验证的是“当下对话”这一层。Session 写不进去,后面 Memory 和向量库都无从谈起。
4.2 验证 Memory flush 是否触发
Memory flush 由 token 阈值触发,公式是:
totalTokens >= contextWindowTokens - reserveTokensFloor - softThresholdTokens以 128k 上下文、默认reserveTokensFloor=20000、softThresholdTokens=4000计算,阈值是128000 - 20000 - 4000 = 104000tokens。也就是说会话用到 10.4 万 token 左右时,flush 才会触发。你不可能每次都手动聊到 10 万 token 去验证,所以有两个办法。
第一个办法是临时调低阈值,在配置里把reserveTokensFloor和softThresholdTokens改小:
{ "agents": { "defaults": { "compaction": { "reserveTokensFloor": 2000, "softThresholdTokens": 500 } } } }这样阈值降到128000 - 2000 - 500 = 125500,还是偏高。更直接的是换个小窗口模型,比如 8k 上下文的,阈值立刻降到几千 token,聊几轮就能触发。
第二个办法是看日志。flush 触发时日志里会有memory flush相关记录,写入成功后memory/YYYY-MM-DD.md会出现新内容:
ls -la <workspace>/memory/ cat <workspace>/memory/2025-*.md预期看到类似这样的追加内容:
## 2025-01-15 - 部署环境:staging,端口 8080 - 构建命令:pnpm -r build如果文件存在但内容为空,检查customInstructions是否让模型返回了NO_REPLY——源码里SILENT_REPLY_TOKEN就是NO_REPLY,模型判断“没有需要存储的内容”时会静默,这是正常行为,不是 bug。
4.3 验证向量库召回是否命中
向量库召回验证最直接的方式是用 3.3 的memory.search主动查。但更贴近真实场景的是看 OpenClaw 运行时有没有把检索结果注入 prompt。日志里搜memory search或retrieved关键词:
grep -i "memory search\|retrieved\|inject" ~/.openclaw/logs/*.log | tail -20预期看到类似memory search returned 3 results, top score 0.72的记录。如果 top score 长期低于minScore(默认 0.35),说明要么记忆没写进去,要么 embedding 模型不匹配。检查embeddingModel字段是否和写入时一致——换过 embedding 模型的话,旧向量和新查询向量不在同一空间,召回会全线失效。
还有一个隐蔽的坑:postCompactionForce: "async"时,压缩刚结束就去检索,向量库可能还没同步完。这时候你会看到“明明刚 flush 了,却搜不到”。解决办法是改成await,或者在检索前加一个短延迟。我一般长任务用await,交互式对话用async,按场景取舍。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
记忆系统的报错往往不在记忆本身,而在模型调用链路。下面四个是我踩过的、也帮别人排查过的典型错误,对照日志逐条看。
401 Unauthorized。压缩阶段生成摘要时最常见。日志里会看到POST https://taotoken.net/api/chat/completions 401。原因通常是 Key 没配、Key 过期、或者baseUrl和 Key 所属环境不匹配。检查三件套:Base URL 是不是https://taotoken.net/api、Key 是不是控制台新生成的、Model ID 是不是当前账号有权限的。三个都对还 401,去控制台看 Key 的额度是否耗尽。
local proxy failed。这个报错说明请求根本没出本机,卡在本地代理层。OpenClaw 某些版本会读环境变量里的代理设置,如果你本机有残留的HTTP_PROXY/HTTPS_PROXY,请求会被导向一个不存在的本地端口。检查:
env | grep -i proxy有输出就unset HTTP_PROXY HTTPS_PROXY ALL_PROXY,然后重启 OpenClaw。注意这里说的是清理本机环境变量,不是让你去配什么网络工具,纯粹是避免残留配置干扰。
reading 'choices'。典型报错是Cannot read properties of undefined (reading 'choices')。这说明请求发出去了,但响应体结构不对,代码在取response.choices[0]时拿到 undefined。常见原因是baseUrl多写了/v1,导致实际请求路径变成/api/v1/v1/chat/completions,服务端返回的是错误页而不是标准响应。把baseUrl改成https://taotoken.net/api即可。另一个原因是 Model ID 拼错,服务端返回 404 页面,同样解析不出choices。
OAuth 相关报错。如果你用的是 Claude Code 或 Codex 这类带 OAuth 流程的客户端,接入自定义 Base URL 时可能报OAuth token exchange failed或invalid_grant。这类客户端默认走官方 OAuth,切到兼容接口需要在配置里显式指定authType: "api-key",并把 Key 填到对应字段。以 Codex 的auth.json为例:
{ "authType": "api-key", "apiKey": "sk-你的Key", "baseUrl": "https://taotoken.net/api", "model": "claude-sonnet-4-5" }Claude Code 的 settings 里则是ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY两个字段。CC Switch 这类切换工具也是同样的三件套逻辑:Base URL、Key、Model ID,缺一不可。OAuth 报错九成是authType没改,客户端还在尝试走 OAuth 换 token。
排查顺序建议固定下来:先看 401(认证),再看 local proxy failed(网络层),再看 reading choices(响应结构),最后看 OAuth(客户端认证模式)。按这个顺序走,基本不会绕弯路。
6. 把记忆流转跑通之后,下一步接什么
记忆系统跑通的标志很简单:Session transcript 实时写入、Memory 文件在压缩后出现追加内容、向量库检索能命中刚写入的记忆。这三件事都能用第 4 节的命令验证。跑通之后,你会发现 OpenClaw 的长会话稳定性上了一个台阶——模型不再“失忆”,跨 Session 的任务背景也能延续。
如果你还没配模型链路,先去 API Keys 页面生成 Key,再对照接入文档把 Base URL、Key、Model ID 三件套填进配置。模型是否通,可以在模型对话里先发一条测试消息确认。长期跑编码任务或 Agent 工作流的,建议直接上 Coding Plan,省得每次手动配额度。
记忆系统的调优是个持续过程。hybridSearch的权重、postCompactionForce的模式、minScore的阈值,都要根据你的任务类型反复试。代码类任务把 BM25 权重调高、identifierPolicy设 strict;自然语言任务把向量权重调高、recentTurnsPreserve设大一点保留更多近期上下文。这些参数没有万能值,跑几轮长会话,看日志里的召回分数,慢慢就找到手感了。