资讯详情 OpenClaw 记忆系统深度拆解:Markdown 即真相,Agent 永不失忆
📅 2026/10/12 1:13:12
1. 为什么 Agent 一关会话就“失忆”从 OpenClaw 记忆系统说起如果你用过一段时间的 Agent 工具大概率遇到过这种尴尬昨天刚跟它聊完项目架构今天开个新会话它一脸茫然地问你“请问你想做什么”。这不是模型笨而是大多数 Agent 的记忆只活在当前会话的上下文窗口里窗口一关记忆归零。OpenClaw 的记忆系统想解决的就是这个问题它的核心设计哲学只有一句话Markdown 即真相。所有值得记住的东西最终都落在磁盘上的 Markdown 文件里人类可读、git 可追踪、随时能打开看而 SQLite 只是从这些 Markdown 派生出来的加速索引哪怕整个数据库删掉也能从 Markdown 完整重建。这个思路对做 Agent 记忆系统的人来说非常值得抄作业——它把“真相源”和“检索层”彻底解耦了。这套系统适合谁如果你正在做需要跨会话记忆的 Agent 应用或者想给自己的编码助手加一层可审计的长期记忆又或者你只是好奇“Agent 到底怎么记住我说过的话”这篇拆解都能给你可跟做的配置和验证步骤。我会从记忆目录结构讲起一路走到 SQLite 索引、混合检索、压缩前刷新最后用 TaoToken 统一 Key 通道把模型调用接上让整条记忆链路可观测、可复现。先给个整体印象OpenClaw 的记忆分几层。长期记忆是工作区根目录的MEMORY.md存的是精心整理的持久事实和用户偏好它永远不参与时间衰减日志记忆是memory/YYYY-MM-DD.md按天追加流水账会参与时间衰减再往外还有extraPaths扩展目录和实验性的会话转录记忆。检索时Agent 通过memory_search工具做语义搜索命中后再用memory_get按行范围精读避免把整个文件灌进上下文。理解了这个分层后面的配置和排障就顺了。下面我按“目录结构 → 索引机制 → 可复制配置 → 验证 → 排障 → 接入”的顺序展开每一步都给能直接跑的命令。2. OpenClaw 记忆目录结构与 SQLite 索引机制拆解2.1 记忆目录长什么样OpenClaw 默认把工作区放在~/.openclaw/workspace记忆相关的文件结构大致是这样~/.openclaw/workspace/ ├── MEMORY.md # 长期记忆持久事实、偏好、关键决策 ├── memory/ │ ├── 2026-03-10.md # 日志记忆按天追加 │ ├── 2026-03-11.md │ └── projects.md # 无日期文件不参与时间衰减 └── (extraPaths 配置的额外目录)MEMORY.md是“常青文件”只在主私有会话一对一直接对话里加载群组上下文不加载它。写入规则很明确决策、偏好、持久事实写这里用户说“记住这件事”就写下来别只放在内存里。memory/YYYY-MM-DD.md是每日流水账会话启动时读取今天加昨天两天。它的文件名带日期所以会参与时间衰减——越久远的日志检索得分越低。extraPaths允许你把工作区外的目录也纳入索引支持绝对路径或工作区相对路径目录会被递归扫描.md文件符号链接会被忽略。2.2 SQLite 索引的四张核心表索引数据库默认在~/.openclaw/memory/agentId.sqlite可以通过store.path自定义支持{agentId}占位符。核心表有四张表名主键核心字段用途metakeyvalue存 schema 版本、provider/model 指纹filespathhash, mtime, size, source文件变更检测chunksidpath, start_line, end_line, hash, model, text, embedding分块文本 向量embedding_cache(provider, model, provider_key, hash)embedding, dims向量缓存避免重复调 API另外两张辅助表是可选的chunks_fts是 FTS5 虚拟表支持 BM25 关键词搜索vec0是 sqlite-vec 扩展表加速余弦相似度计算。如果平台不支持 FTS5 或 sqlite-vec系统会降级——FTS5 缺失时关键词搜索降级sqlite-vec 不可用时改用 JS 层面的余弦相似度。2.3 分块与向量化分块默认参数在src/agents/memory-search.ts里DEFAULT_CHUNK_TOKENS 400每块目标约 400 tokenDEFAULT_CHUNK_OVERLAP 80相邻块重叠 80 token防止边界信息丢失。这个粒度保证检索结果足够小不会让上下文膨胀同时保留必要的语义。向量化的 provider 在auto模式下按优先级自动选择local配置了local.modelPath且文件存在→ openai → gemini → voyage → mistral都没有则记忆检索保持禁用。默认模型分别是 OpenAI 的text-embedding-3-small、Gemini 的gemini-embedding-001、Voyage 的voyage-4-large、Mistral 的mistral-embed本地则是embeddinggemma-300m的 GGUF 量化版约 0.6GB首次使用自动下载。embedding_cache表默认开启避免对未变更文本重复调 API。Reindex 的触发机制很关键索引里存了 provider/model、endpoint fingerprint 和 chunking 参数指纹任何一项变更自动清空并重新索引全部文件。所以你换了 embedding 模型第一次搜索会慢那是在重建。2.4 混合检索与后处理检索走的是混合管道默认开启向量候选池取maxResults × candidateMultiplierBM25 候选池同样然后按 chunk id 合并加权融合finalScore vectorWeight × vectorScore textWeight × textScore默认0.7 × 向量 0.3 × BM25。BM25 得分转换公式是textScore 1 / (1 max(0, bm25Rank))。关键默认值DEFAULT_MAX_RESULTS 6DEFAULT_MIN_SCORE 0.35DEFAULT_HYBRID_CANDIDATE_MULTIPLIER 4。后处理有两个可选阶段。时间衰减默认关闭启用后decayedScore score × e^(-λ × ageInDays)λ ln(2) / halfLifeDays默认半衰期 30 天——今天 100%7 天约 84%30 天 50%90 天 12.5%。MEMORY.md和memory/下无日期格式的文件豁免衰减。MMR 去重也默认关闭用 Jaccard 文本相似度做迭代选择λ 默认 0.7平衡相关性和多样性。2.5 压缩前记忆刷新这是我觉得最巧妙的一环。当 session 接近自动 compaction 阈值时OpenClaw 在下一轮对话前先触发一个静默的 agentic turn提示 Agent 把重要信息写进记忆文件。触发条件是当前 token 数 ≥contextWindow - reserveTokensFloor - softThresholdTokens且本 compaction 周期内没触发过且工作区可写。默认参数softThresholdTokens 4000reserveTokensFloor 20000forceFlushTranscriptBytes 2MB。默认提示词会强制追加NO_REPLY提示确保正常情况不产生用户可见回复。每个 compaction 周期只触发一次由memoryFlushCompactionCount记录。这个设计保证了跨 compaction 的记忆连续性——不是等丢了再补而是提前落盘。3. 可复制的 OpenClaw 记忆配置与 TaoToken 接入3.1 记忆配置全景下面这份配置可以直接改改就用路径和字段名与 OpenClaw 源码一致{ memory: { backend: builtin, citations: auto, qmd: { command: qmd, searchMode: search, includeDefaultMemory: true, paths: [{ name: docs, path: ~/notes, pattern: **/*.md }], update: { interval: 5m, debounceMs: 15000, onBoot: true }, limits: { maxResults: 6, maxSnippetChars: 700, maxInjectedChars: 8000, timeoutMs: 4000 } } }, agents: { defaults: { memorySearch: { enabled: true, provider: auto, model: text-embedding-3-small, fallback: none, sources: [memory], extraPaths: [], store: { path: ~/.openclaw/memory/{agentId}.sqlite, vector: { enabled: true } }, chunking: { tokens: 400, overlap: 80 }, sync: { onSessionStart: true, onSearch: true, watch: true, watchDebounceMs: 1500, intervalMinutes: 0, sessions: { deltaBytes: 100000, deltaMessages: 50 } }, query: { maxResults: 6, minScore: 0.35, hybrid: { enabled: true, vectorWeight: 0.7, textWeight: 0.3, candidateMultiplier: 4, mmr: { enabled: false, lambda: 0.7 }, temporalDecay: { enabled: false, halfLifeDays: 30 } } }, cache: { enabled: true, maxEntries: 50000 }, experimental: { sessionMemory: false } }, compaction: { reserveTokensFloor: 20000, memoryFlush: { enabled: true, softThresholdTokens: 4000, forceFlushTranscriptBytes: 2mb } } } } }几个容易踩的点store.path里的{agentId}占位符会被替换成实际 agent 名sync.intervalMinutes默认 0 表示禁用定时同步靠会话启动和搜索前同步就够了experimental.sessionMemory打开后sources要加sessions但注意memory_get不支持会话来源只有memory_search能用。3.2 用 TaoToken 统一 Key 通道接入模型调用记忆检索本身要调 embedding APIAgent 对话要调 chat API如果每个 provider 都单独配 key管理起来很乱。TaoToken 提供统一 Key 通道把模型调用收敛到一个入口记忆链路也更好观测。先拿 Key访问https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentmemory_systemutm_campaignrewrite创建 API Key。然后在 OpenClaw 的 provider 配置里把 base URL 指向 TaoToken{ providers: { openai: { apiKey: sk-你的TaoToken密钥, baseUrl: https://taotoken.net/api } } }如果你用的是 Claude Code 或 Codex 这类工具配置方式类似把 Base URL 换成https://taotoken.net/apiKey 填 TaoToken 的 KeyModel ID 按你实际用的填。三件套缺一不可Base URL、Key、Model ID。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentmemory_systemutm_campaignrewrite里面有各工具的详细配置。想先验证模型通不通可以用模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmemory_systemutm_campaignrewrite发一条测试消息。3.3 记忆目录初始化配置好之后手动建一下记忆目录结构mkdir -p ~/.openclaw/workspace/memory touch ~/.openclaw/workspace/MEMORY.md echo # 长期记忆 ~/.openclaw/workspace/MEMORY.md echo ## 用户偏好 ~/.openclaw/workspace/MEMORY.md echo - 喜欢简洁的代码示例 ~/.openclaw/workspace/MEMORY.md然后写一条今天的日志TODAY$(date %Y-%m-%d) cat ~/.openclaw/workspace/memory/$TODAY.md EOF # 今日记录 ## 项目进展 - 完成记忆系统配置 - 验证 SQLite 索引生成 EOF这样真相源就有了接下来让索引跑起来。4. 验证记忆读写从 CLI 到 Agent 工具调用4.1 查看索引状态配置完成后第一件事是确认索引状态openclaw memory status正常输出会显示 backend 类型、索引文件路径、已索引文件数、chunk 数、embedding provider 和模型。如果显示 provider 为none或检索禁用说明 embedding provider 没配好回去检查memorySearch.provider和对应 key。深度探测 embedding provider 是否真的可用openclaw memory status --deep这个命令会实际调一次 embedding API能暴露 key 无效、网络不通、模型名写错等问题。如果同时想重建索引openclaw memory status --deep --index4.2 重建索引改了分块参数或换了 embedding 模型后需要重建openclaw memory index强制全量重建openclaw memory index --force想看详细日志加--verbose会打印 provider、model 和进度openclaw memory index --verbose实测下来几百个 Markdown 文件的全量索引在几十秒内能跑完主要耗时在 embedding API 调用上。embedding_cache开启后重复重建会快很多因为未变更文本直接命中缓存。4.3 命令行搜索验证索引建好后直接用 CLI 搜索openclaw memory search release checklist指定返回条数和最低得分openclaw memory search --query release checklist --max-results 10 --min-score 0.4要 JSON 输出方便脚本处理openclaw memory search --json指定 agentopenclaw memory status --agent main搜索结果里每条包含path、startLine、endLine、score、snippet和source。score是综合得分范围 0 到 1低于minScore的会被过滤掉。snippet上限约 700 字符citation格式类似memory/2026-03-10.md#L12-L18。4.4 Agent 工具调用验证CLI 验证的是索引层真正要确认的是 Agent 会不会主动检索。OpenClaw 的memory_search工具描述里写得很强硬Mandatory recall step: semantically search MEMORY.md memory/*.md ... before answering questions about prior work, decisions, dates, people, preferences, or todos也就是说Agent 在回答关于过往工作、决策、日期、人物、偏好或待办的问题前被要求必须先做语义搜索。你可以这样测先在MEMORY.md里写一条“用户偏好用 TypeScript 而不是 JavaScript”然后开新会话问 Agent“我之前说过偏好什么语言”看它是否调用memory_search并命中这条。memory_get工具用于精读。当memory_search定位到某文件某行后Agent 用memory_get按行范围拉取具体片段避免全量文件注入。安全限制上它只允许读MEMORY.md和memory/路径越界访问会被拒绝。容错设计也不错文件不存在时不抛异常返回{ text: , path }让 Agent 优雅处理。Citations 模式由memory.citations控制auto在直接对话中显示来源群组不显示on始终显示off始终隐藏但 Agent 内部仍能拿到路径。4.5 验证记忆刷新想验证压缩前刷新可以手动把softThresholdTokens调小比如设成 100然后进行一段较长对话观察memory/YYYY-MM-DD.md是否被自动追加内容。默认提示词会强制NO_REPLY所以你不会在对话里看到刷新的痕迹但文件会变。5. 常见报错排查401、local proxy failed、reading choices、OAuth5.1 401 Unauthorized这是最常见的。记忆检索报 401通常是 embedding provider 的 key 无效或没配。检查顺序先看memorySearch.provider是不是auto但实际没有任何 key再看remote.apiKey是否填对如果用 TaoToken确认baseUrl是https://taotoken.net/api且 key 以sk-开头。Agent 对话报 401检查 provider 配置里的apiKey和baseUrl。三件套要齐全Base URL、Key、Model ID。少一个都可能 401 或 404。5.2 local proxy failed这个报错通常出现在本地 embedding 模型加载失败时。如果你配了local.modelPath指向 GGUF 文件但文件不存在或下载不完整就会报这个。解决方式要么删掉local.modelPath让 provider 回退到远程要么重新触发下载。本地模型约 0.6GB首次使用自动下载网络不稳时容易断。另一个可能是modelCacheDir指向的目录没有写权限。检查~/.cache/openclaw是否可写。5.3 reading choices 相关报错这类报错一般出现在 chat API 返回结构不符合预期时比如 base URL 配错导致返回了 HTML 错误页而不是 JSON。检查baseUrl是否漏了/api或多了斜杠。TaoToken 的 API 地址是https://taotoken.net/api不要加 UTM 参数到 API 地址上。如果用的是兼容 OpenAI 格式的第三方端点确认它返回的choices字段结构标准。有些端点会在流式响应里返回非标准 chunk导致解析失败。5.4 OAuth 相关报错Claude Code 或 Codex 这类工具用 OAuth 登录时如果 token 过期或刷新失败会报 OAuth 错误。解决方式是重新走一遍登录流程或者改用 API Key 方式接入 TaoToken。用 API Key 的好处是不依赖 OAuth 刷新长期跑 Agent 更稳。5.5 索引不更新如果改了 Markdown 但搜索不到新内容检查sync.watch是否为 truewatchDebounceMs默认 1500ms防抖期间不会触发。也可以手动openclaw memory index强制同步。另外确认文件在extraPaths或工作区内符号链接会被忽略。5.6 搜索得分普遍偏低如果所有结果得分都低于minScore可能是 embedding 模型和查询语言不匹配。比如用英文模型搜中文内容得分会偏低。换一个多语言 embedding 模型或者调低minScore。也可能是分块太大导致语义稀释试试把chunking.tokens从 400 调到 256。6. 把记忆链路接上 TaoToken统一 Key 通道的长期价值走到这里记忆系统的读写验证应该都通了。最后说下为什么建议用 TaoToken 统一 Key 通道。Agent 的记忆链路涉及两类模型调用embedding 和 chat。如果 embedding 用 OpenAI、chat 用另一个 provider你得管两套 key、两套额度、两套报错。TaoToken 把这两类调用收敛到一个 Key 和一个 base URL排查问题时只需要看一个入口。记忆检索报错时先确认 TaoToken 通道通不通再往下查索引层排障路径短很多。长期跑编码 Agent 的话Coding Plan 更适合https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentmemory_systemutm_campaignrewrite有专门的额度方案。控制台在https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentmemory_systemutm_campaignrewrite可以看调用量和余额。配置上把 OpenClaw 的 provider base URL 指向https://taotoken.net/apiKey 填 TaoToken 的 KeyModel ID 按实际用的填。这样 embedding 和 chat 都走同一条通道记忆链路的可观测性就上来了——哪一步慢、哪一步报错在控制台一目了然。最后给个实用技巧定期把~/.openclaw/workspace用 git 管起来。因为 Markdown 即真相你的记忆就是一堆文本文件git diff 能清楚看到 Agent 每天往记忆里写了什么。哪天觉得它记错了直接改 Markdown重建索引即可。SQLite 删了都不心疼真相永远在 Markdown 里。