1. 为什么本地编码代理需要一个「文件即记忆」的骨架
OpenClaw 的 Agent Memory 是一套把长期记忆落在本地 Markdown 文件、再用 SQLite 做索引的架构,它解决的是本地 AI 编码代理「记不住项目上下文、跨会话就失忆」的问题。适合谁?适合那些把编码代理跑在自己机器上、又不想把代码片段和项目笔记上传到云端向量库的开发者。它最核心的一句话是:记忆的真相在文件里,数据库只是可重建的索引。
我在本地跑编码代理时踩过最典型的坑,就是每次新开会话都要重新解释一遍项目结构、命名习惯、哪些目录不能动。代理本身很聪明,但它没有「昨天我们聊过什么」的概念。市面上多数方案要么把记忆托管到云端,要么依赖一个必须常驻的向量数据库服务,对个人开发者来说部署负担不小。OpenClaw 走的是中间路线:完全本地化、生产可用、工程成熟度高。
它的独特之处可以拆成四点。第一,没有外部依赖,不需要 Redis、Pinecone 或任何外部向量数据库,一个 SQLite 文件搞定索引。第二,人机同构,记忆以 Markdown 文件形式存在,人类和 AI 都能直接读写,你打开编辑器就能看到代理记住了什么。第三,渐进增强,即使没有配置任何 Embedding API,也能通过纯 FTS 全文检索工作,配了 Embedding 再升级到语义检索。第四,多语言支持,内置中、英、日、韩、阿、葡、西等语言的查询优化。
这套设计哲学深受 Unix「一切皆文件」的影响。代理的记忆不是锁在某个黑盒数据库里,而是以人类可读的 Markdown 形式存在工作区中。带来的好处很直接:透明性,你随时能打开文件查看 AI 记了什么;可编辑性,记错了直接改;版本控制,可以用 Git 追踪记忆变化历史;可移植性,换一个 Agent 框架,记忆文件照样能用。
理解这套架构的取舍,比记住它的 API 更重要。因为一旦你接受了「文件是主、索引是辅」这个前提,后面所有的配置项、目录结构、检索策略都会变得顺理成章。这篇会交付可复制的目录结构、记忆读写配置片段,以及三步验证动作:启动日志、记忆命中、跨会话召回。你跟着做,就能搭出一个能跑起来的骨架。
2. TaoToken 前置:给记忆检索接上可用的模型通道
OpenClaw 的记忆系统本身不绑定任何模型服务,但它的 Embedding 层和查询扩展层需要一个稳定的模型通道。这里我用 TaoToken 来做统一接入,原因是它把模型对话、编码计划、API Key 管理放在同一个控制台里,配置一次就能被多个本地工具复用。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数。
先说清楚它在整条链路里的位置。OpenClaw 的记忆生命周期分四段:写入、索引、检索、呈现。TaoToken 主要作用在索引段的 Embedding 计算和检索段的查询扩展上。也就是说,即使 TaoToken 暂时不可用,你的记忆文件依然在,FTS 全文检索依然能跑,只是语义检索会降级。这个降级能力是 OpenClaw 设计里很关键的一环,后面第五节会专门讲。
你需要准备三样东西:一个可用的 API Key、一个 Base URL、一个 Model ID。这三件套在 TaoToken 控制台里都能拿到。API Key 在 https://taotoken.net/api-keys 生成,模型列表和对话测试在 https://taotoken.net/models 验证。我建议你先在模型对话页面发一条测试消息,确认通道通了,再去配 OpenClaw,这样能把「通道问题」和「配置问题」分开排查。
关于模型选择,Embedding 和对话模型是两类。OpenClaw 的 Embedding 层支持 OpenAI、Gemini、Voyage、Mistral、Ollama 以及本地 GGUF 模型。如果你走远程 Embedding,就在配置里填对应的 provider 和 model;如果你想完全离线,可以用 Ollama 拉一个 embedding 模型,或者用 node-llama-cpp 加载本地 GGUF。查询扩展和记忆摘要这类需要生成能力的环节,才用到对话模型。
这里有个工程上的取舍值得说。远程 Embedding 精度通常更好,但每次索引都要发网络请求,大批量文件首次索引会慢;本地 Embedding 快且免费,但精度和维度选择受限。我的做法是:日常小项目用本地 Ollama 做 Embedding,保证零延迟;需要高精度语义召回时切到远程。OpenClaw 的 auto 模式会自动选择可用 provider,还支持 fallback,主 provider 挂了自动切备选。
配置前还要确认一件事:你的工作区目录。OpenClaw 默认把记忆放在 workspace 下,索引数据库放在 ~/.openclaw/state/memory/ 目录。这两个路径要分清,前者是你的记忆真相,后者是可重建的索引。备份的时候只需要备份 workspace,索引丢了重新索引即可。理解这一点,后面看到数据库文件损坏就不会慌。
如果你打算长期跑编码代理、让它积累项目记忆,建议顺手了解一下 Coding Plan,它更适合持续性的编码和 Agent 场景,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,配置项对不上时以文档为准。
3. 可复制配置:目录结构、settings 片段与三件套
这一节是整篇最需要动手的部分。我先把目录结构摆出来,再给可复制的配置片段,最后说明每个字段的作用。你照着建目录、贴配置,就能得到一个可运行的记忆骨架。
先建工作区目录结构。记忆的真相全部在 workspace 下:
workspace/ ├── MEMORY.md # 长期记忆(精炼的核心知识) ├── USER.md # 用户画像 └── memory/ ├── 2026-03-23.md # 每日笔记 ├── 2026-03-22.md └── ...MEMORY.md 放那些跨会话都要记住的东西,比如项目约定、技术栈偏好、常用命令。USER.md 放你的个人画像,比如你习惯用 TypeScript、讨厌过度注释、提交信息用中文。memory/ 下的每日笔记按日期命名,代理在对话中产生的临时记录写这里。这种分层的好处是:长期记忆保持精炼,每日笔记可以随便写,检索时按权重和时效一起算。
接下来是记忆检索的配置片段。OpenClaw 的配置结构叫 ResolvedMemorySearchConfig,我用 JSON 形式给你一份可直接改的版本,路径和字段名与源码保持一致:
{ "memory": { "search": { "enabled": true, "sources": ["memory", "sessions"], "extraPaths": [], "provider": "auto", "store": { "driver": "sqlite", "path": "~/.openclaw/state/memory/index.sqlite", "vector": { "enabled": true, "extensionPath": "" } }, "chunking": { "tokens": 400, "overlap": 80 }, "sync": { "onSessionStart": true, "onSearch": true, "watch": true, "watchDebounceMs": 1500, "intervalMinutes": 30 }, "query": { "maxResults": 6, "minScore": 0.35, "hybrid": { "enabled": true, "vectorWeight": 0.7, "textWeight": 0.3, "candidateMultiplier": 4, "mmr": { "enabled": true, "lambda": 0.7 }, "temporalDecay": { "enabled": true, "halfLifeDays": 30 } } }, "cache": { "enabled": true, "maxEntries": 2000 } } } }如果你用的是 TOML 风格的配置,等价片段如下,字段含义一致:
[memory.search] enabled = true sources = ["memory", "sessions"] provider = "auto" [memory.search.store] driver = "sqlite" path = "~/.openclaw/state/memory/index.sqlite" [memory.search.store.vector] enabled = true [memory.search.chunking] tokens = 400 overlap = 80 [memory.search.sync] onSessionStart = true onSearch = true watch = true watchDebounceMs = 1500 intervalMinutes = 30 [memory.search.query] maxResults = 6 minScore = 0.35 [memory.search.query.hybrid] enabled = true vectorWeight = 0.7 textWeight = 0.3 candidateMultiplier = 4 [memory.search.query.hybrid.mmr] enabled = true lambda = 0.7 [memory.search.query.hybrid.temporalDecay] enabled = true halfLifeDays = 30现在把三件套填进去。无论你走远程 Embedding 还是对话模型,Base URL、API Key、Model ID 都要明确。以 TaoToken 为例:
{ "memory": { "search": { "provider": "openai", "embedding": { "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的Key", "model": "text-embedding-3-small" } } } }这里 Base URL 填 https://taotoken.net/api ,不要带 UTM 参数。API Key 从控制台生成,Model ID 填你实际要用的 Embedding 模型名。如果你用 Ollama 本地 Embedding,provider 改成 ollama,baseUrl 填 http://localhost:11434,model 填 nomic-embed-text,apiKey 可以留空。
几个关键配置项的作用我列个表,方便你对照调参:
| 配置项 | 默认值 | 说明 |
|---|---|---|
| chunking.tokens | 400 | 每个 chunk 的 token 数 |
| chunking.overlap | 80 | chunk 之间的重叠 token 数 |
| query.maxResults | 6 | 每次检索最多返回的结果数 |
| query.minScore | 0.35 | 最低相关性分数阈值 |
| query.hybrid.vectorWeight | 0.7 | 向量检索权重 |
| query.hybrid.textWeight | 0.3 | 全文检索权重 |
| query.hybrid.mmr.lambda | 0.7 | MMR 多样性参数,0 纯多样,1 纯相关 |
| query.hybrid.temporalDecay.halfLifeDays | 30 | 时效衰减半衰期(天) |
| sync.watchDebounceMs | 1500 | 文件变化后等待多久再同步 |
调参经验:chunking.tokens 太小会切碎语义,太大召回不精准,400 是通用起点。overlap 保证跨 chunk 的语义连贯,80 够用。minScore 调高召回更准但可能漏,调低召回更全但噪声多,0.35 是平衡点。vectorWeight 和 textWeight 加起来建议等于 1,代码类项目可以适当提高 textWeight,因为变量名、函数名这类精确匹配很重要。
注意:配置里的 apiKey 不要提交到 Git。建议用环境变量注入,或者把配置文件加进 .gitignore。记忆文件本身可以进版本控制,但密钥不行。
配置写完后,先别急着启动。检查三件事:workspace 目录存在且可写、SQLite 索引目录的父目录存在、Base URL 和 Key 能通过模型对话页面验证。这三件事确认了,再进下一节做启动验证。
4. 三步验证:启动日志、记忆命中、跨会话召回
配置贴完不代表能跑。这一节给你三个可执行的验证动作,每一步都有明确的成功标志。我按顺序来,前一步不过就别做下一步,否则排查起来会混在一起。
第一步,看启动日志。启动 OpenClaw 后,观察日志里记忆模块的初始化输出。正常情况下你会看到类似这样的信息:记忆管理器初始化、SQLite 索引打开、向量扩展加载、文件监听器启动。如果向量扩展加载失败,日志会提示 sqlite-vec 相关错误,这时候先确认 extensionPath 是否正确,或者把 vector.enabled 临时设为 false,退回 FTS-only 模式先跑起来。
# 启动后过滤记忆相关日志 openclaw start 2>&1 | grep -i "memory\|sqlite\|vector\|watcher"成功标志:看到 watcher 启动、索引数据库打开、没有 fatal 级别错误。如果看到「FTS-only mode」字样,说明 Embedding provider 没配好,但系统已经优雅降级,记忆检索仍可用,只是语义能力受限。
第二步,验证记忆命中。在 workspace/MEMORY.md 里写一条明确的记忆,比如「本项目使用 pnpm 而不是 npm」。等 watcher 的 debounce 时间(默认 1500ms)过去,然后在代理对话里问一个相关的问题,比如「这个项目用什么包管理器」。观察代理的回答是否引用了你写的那条记忆。
<!-- workspace/MEMORY.md --> ## 项目约定 - 包管理器:pnpm - 提交信息语言:中文 - 禁止修改目录:vendor/成功标志:代理回答里出现 pnpm,并且能给出引用来源,格式类似 Source: MEMORY.md#L1-L3。如果代理没命中,先检查文件是否被 watcher 捕获,再检查 minScore 是否设得太高把结果过滤掉了。可以临时把 minScore 调到 0.1 试试。
第三步,验证跨会话召回。这一步最关键,因为它验证的是长期记忆是否真的跨会话生效。关掉当前会话,重新开一个全新的会话,再问一个需要用到之前记忆的问题。比如你之前让代理记住「API 错误统一用 AppError 包装」,新会话里问「新增一个接口时错误怎么处理」,看它是否召回这条约定。
# 查看索引里实际存了哪些 chunk sqlite3 ~/.openclaw/state/memory/index.sqlite \ "SELECT path, start_line, end_line FROM chunks ORDER BY path LIMIT 10;"成功标志:新会话里代理能引用旧会话写入的记忆,且引用路径正确。如果跨会话召回失败但单会话命中正常,通常是索引没持久化或会话隔离配置有问题,检查 store.path 是否指向了固定路径,而不是临时目录。
三步都过了,说明你的记忆骨架已经跑通。这时候可以开始往 MEMORY.md 里积累真正有用的项目知识了。我建议的节奏是:每天结束时让代理把当天的重要结论提炼进 MEMORY.md,每日笔记保留原始记录,长期记忆只留精炼结论。这样检索时信噪比最高。
提示:验证阶段可以把 sync.intervalMinutes 调小,比如 5 分钟,让索引更新更频繁,方便观察。稳定后再调回 30 分钟,减少后台开销。
如果你在验证模型通道时遇到问题,可以回到模型对话页面单独测一条消息,确认 Base URL、Key、Model ID 三件套无误,再回来查 OpenClaw 的配置。把通道问题和配置问题分开,排查效率会高很多。
5. 本篇常见错排查:401、local proxy failed 与 reading choices
这一节把我在配置过程中真实遇到过的报错整理出来,每个都给定位思路和修复动作。你大概率会碰到其中一两个,对照着查能省不少时间。
第一个高频错误是 401 Unauthorized。表现是索引阶段或检索阶段报鉴权失败,日志里能看到 401 状态码。原因通常是 API Key 无效、过期,或者 Base URL 和 Key 不匹配。排查顺序:先在模型对话页面用同一个 Key 发一条消息,确认 Key 本身可用;再检查配置里的 baseUrl 是否写成了 https://taotoken.net/api ,注意不要漏掉 /api,也不要多加斜杠;最后确认 Key 没有多余空格或换行。
{ "embedding": { "baseUrl": "https://taotoken.net/api", "apiKey": "sk-xxxxxxxx", "model": "text-embedding-3-small" } }第二个常见错误是 local proxy failed。这个报错通常出现在你配置了本地代理或本地模型服务,但服务没起来的时候。比如你配了 Ollama 做 Embedding,但 Ollama 进程没启动,或者端口不是默认的 11434。修复动作:确认本地服务在跑,用 curl 测一下端口通不通。
# 测试本地 Ollama 是否可用 curl http://localhost:11434/api/tags如果本地服务确实起不来,最快的恢复方式是把 provider 切回远程,或者临时把 vector.enabled 设为 false 退回 FTS-only。OpenClaw 的降级设计在这里就体现出价值了:记忆检索不会因为 Embedding 不可用而完全瘫痪。
第三个错误是 reading choices 相关报错。这个通常出现在对话模型返回结构不符合预期时,比如你用的模型返回格式和 OpenAI 兼容格式有差异,导致解析 choices 字段失败。排查思路:确认 Model ID 填的是对话模型而不是 Embedding 模型;确认该模型在模型对话页面能正常返回;如果用的是非标准模型,检查是否需要额外的兼容参数。
Error: reading choices: unexpected response shape at parseCompletion (provider/openai.ts:88)遇到这个错,先换一个标准模型测试,比如先用一个通用的对话模型确认链路通,再换回你要用的模型。如果只有特定模型报错,那就是模型兼容性问题,不是配置问题。
第四个是 OAuth 相关报错。如果你用的是需要 OAuth 授权的 provider,token 过期后会报 OAuth 错误。修复动作是重新走一遍授权流程,或者改用 API Key 方式接入。对于本地编码代理场景,API Key 方式通常更简单,不需要处理 token 刷新。
第五个是索引不更新。表现是你改了 MEMORY.md,但检索结果还是旧的。原因可能是 watcher 没捕获到变化,或者 debounce 时间没到,或者文件 hash 没变(比如只改了空格)。排查:确认 sync.watch 为 true,等够 watchDebounceMs 时间,或者手动触发一次同步。
# 手动触发一次记忆同步 openclaw memory sync --agent default第六个是检索结果为空。minScore 设太高、查询词和记忆用词差异太大、或者索引根本没建,都会导致空结果。排查顺序:先查数据库里有没有 chunk,再把 minScore 临时调低,最后检查查询扩展是否生效。如果是纯 FTS 模式,查询词要尽量用记忆里出现过的关键词。
注意:排查时优先看日志里的模块名。memory 相关的问题看 manager 日志,embedding 相关看 embedding-ops 日志,检索相关看 search 日志。定位到模块,范围就小了一半。
把这几类错误过一遍,你基本能覆盖配置阶段 90% 的坑。剩下的就是具体模型和具体环境的差异了,遇到再针对性查。
6. 从骨架到长期记忆:把配置沉淀成可复用资产
走到这里,你已经有了一个能跑的记忆骨架:目录结构清晰、配置可复制、三步验证通过、常见错有排查路径。接下来要做的不是继续加功能,而是把这套骨架沉淀成可复用的资产,让它真正服务于你的日常编码。
第一件事是把配置模板化。把上面那份 JSON 配置存成一个模板文件,新项目直接复制,只改 workspace 路径和 provider 相关字段。这样每开一个新项目,记忆系统的搭建时间从半小时压缩到两分钟。模板里把 apiKey 留空,用环境变量注入,避免密钥泄露。
第二件事是建立记忆维护习惯。记忆系统最大的敌人不是技术问题,是没人维护导致信噪比下降。我的做法是:每日笔记随便写,长期记忆每周整理一次,把过时的约定删掉,把重复的合并。OpenClaw 的时效衰减机制会自动降低旧记忆的权重,但人工整理仍然不可替代。
第三件事是理解架构取舍背后的边界。SQLite + sqlite-vec 适合个人 Agent 的记忆规模,通常几千到几万条 chunk 完全够用。如果你要处理百万级向量,那确实该上专业向量数据库。Markdown 文件适合人机同构的场景,如果你需要复杂的结构化查询,纯文件方案会吃力。FTS-only 降级适合离线或低成本场景,如果你对语义精度要求极高,还是得配远程 Embedding。
把这套骨架跑通之后,你可以进一步探索的方向包括:多模态记忆(图片等非文本记录)、会话记忆提取(从对话历史自动提炼记忆)、多 Embedding provider 的 fallback 策略。这些在 OpenClaw 的源码里都有对应模块,理解了本篇的架构,再看那些模块会顺很多。
最后给一个实用建议:把 workspace 纳入 Git 管理,但把索引目录排除。这样你的记忆有版本历史,索引随时可重建。每次重要决策后让代理更新 MEMORY.md,提交一次,时间长了你就有了一个可追溯的项目记忆库。这比任何云端记忆服务都更可控,也更符合本地编码代理的初衷。
如果你在接入模型通道时需要更细的配置说明,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API Key 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。先把骨架跑起来,再逐步调优,比一上来就追求完美配置要快得多。