
很多长期使用 AI 编程助手的人都会有一种别扭的体验上一轮对话里刚刚交代过的项目背景、命名偏好、技术约束换个会话窗口之后模型忘得一干二净。你不得不把同样的话反复粘贴它却依然像第一次见面那样跟你客套。我研究并实际搭建过这类「记忆扩展」方案也就是标题里这个 claude-mem 所代表的思路给 Claude 类模型加一层外部持久记忆让它在不同会话之间能真正“想起”你之前说过的话。这篇文章把我在设计、实现和踩坑过程中的所得整理出来不吹不黑只讲实操。适合正在用 Claude Code、构建 Agent 工作流或者单纯对“大模型如何拥有长期记忆”感兴趣的开发者参考读过之后你可以自己复刻一套最小可用版本。1. 项目全貌与设计定位1.1 claude-mem 到底解决什么问题先说痛点。大模型本身不是没有“记忆”它的记忆就藏在上下文窗口里。但上下文窗口有两个硬伤一是容量有限哪怕窗口很大塞满之后最早的内容也会被丢掉二是会话一旦关闭这堆内容就烟消云散下次重新打开客户端状态清零。这种设计对写代码、做问答来说问题不大但对需要长期维护同一套系统的人来说体验很割裂。claude-mem 这类项目做的事情本质上是在模型外部加一个记忆仓库。它不等同于模型参数里的知识也不等同于上下文窗口里的临时内容而是专门服务于“跨会话持续使用”的存储。具体到实际场景你告诉过 Claude 你的项目使用 pnpm 而不是 npm、测试命令是pnpm test --runInBand、代码风格里禁止使用默认导出这些信息如果只是放在某一次会话里下一次还得重新教一遍。而有了记忆系统新会话启动时它会自动把相关回忆注入给模型模型一开口就知道你的规则。我用一个类比来理解它普通对话模式相当于现场临时办公桌上堆满文件散会就清理带记忆的 claude-mem 相当于给每个长期项目配了一个档案柜下次开工时自动把对应项目的档案抽出来放在桌面上。档案不全没关系至少关键的约定不会丢。1.2 核心场景与需求拆解我把实际使用中的需求拆成三类对应不同的记忆粒度。第一类是个人偏好类。比如“你习惯用双引号而不是单引号”“错误信息要给我中文解释”“你希望提交信息用 conventional commits 格式”。这类记忆更新频率低但每次会话都适用适合长期保存、全局注入。第二类是项目上下文类。比如某个仓库的目录结构、依赖管理方式、部署流程、最近一次讨论的技术决策。这类记忆跟具体项目强绑定适合按项目隔离存储。没人希望在 A 项目里检索到 B 项目的历史决策。第三类是短期进度类。比如“上一次会话我们改到哪个函数了”“某个 bug 排查到一半结论是什么”。这类记忆时效性强可以短期保存过期自动清理不然积攒太多会成为噪音。把三类记忆分开处理是我实际搭建时最受益的一个设计决定。很多人做记忆系统失败不是因为技术做不到而是把所有记忆一股脑堆在一起检索时什么都匹配、什么都注入结果模型被泛滥的旧信息干扰还不如没有记忆。1.3 适用对象与边界这个方案适合几类人大量使用 Claude Code 进行日常开发的工程师在多个项目间切换、需要保持语境连续性的独立开发者以及研究 Agent 架构、想验证“外部记忆 RAG”这个技术组合的人。它尤其适合那些经常发生“上次聊过这周继续”的异步工作模式比如周五晚上写了一段代码下周一接着调。反过来如果只是偶尔用一次 Claude 问两个问题或者每次会话任务之间毫无关联那加记忆系统属于过度设计。额外的存储、额外的调用次数、额外的维护成本换来的是用不上的上下文。我自己判断是否该上记忆系统的标准很简单你是否每周都向同一个工具重复说明同样的三件事如果是那说明它缺记忆值得改造如果没有就别折腾。2. 核心架构与实现机制2.1 数据流转全景整个 claude-mem 系统按我自己的实现分成五个阶段捕获、萃取、存储、检索、注入。捕获是最容易忽略但最关键的一环。记忆不是凭空生成它来自你和模型之间的真实对话。我在实现里监听会话的结束事件拿到完整的消息列表而不是在模型推理过程中实时截取。这样做的好处是接口入侵小、性能开销低而且能拿到一整个回合的完整上下文萃取时质量明显更高。萃取阶段把原始对话转录成结构化记忆条目。这一步必须调用一次大型语言模型让它在对话里识别出值得记住的事实、偏好和决策。注意这不是简单地把消息文本存进去而是先做信息压缩。比如原始消息里有一句“上次那个 API 超时问题我记得是网关层没配超时策略”萃取后得到的记忆条目是“API 超时问题原因网关层缺少超时策略配置”更干净、更利于后续检索。存储阶段把萃取出的记忆文本向量化也就是计算成一组浮点数让它落在某个向量空间里。检索阶段通过相似度匹配找出和当前问题最相关的记忆条目。注入阶段把这些条目拼进新的提示词里。整条链路看起来不算复杂但每一个环节的选择都会影响最终效果。2.2 存储层选型从小而美到可扩展记忆存储有两种主流选择嵌入式向量库和外部向量数据库。我最初图省事用了某个纯 Python 的内存向量索引后来数据量一多就不行了——不是怕存不下而是每次读取时索引加载慢、占用内存高。换成 SQLite 加向量搜索插件后整体稳定性提升了一个档次。选择 SQLite 作为存储底座的考量是这样的单文件、无服务进程、备份方便非常适合个人开发者或小团队的使用场景。数据量在几万条以内时向量检索的时延完全可接受。我实测过一万条记忆、每条记忆用一个 768 维向量查询一次相似度大约十几毫秒到几十毫秒这个量级在对话型工具里完全感知不到延迟。等记忆规模超过几十万条再考虑迁移到外部专用向量数据库不迟。过早引入重型组件会给部署和运维带来额外负担收益却未必提升。我自己有一个基本判断先跑起来再考虑规模化不要在第一天就把架构堆满。2.3 检索策略与记忆唤起逻辑记忆系统光存进去不够关键是“怎么想起来”。很多人理解的 RAG 就是“我把问题向量化检索出最相近的若干条拼进提示词”实际做下来会发现有细节问题相近不等于有用。我在检索策略里加了三个过滤器。第一是相似度阈值低于某个分数的记忆干脆不注入宁缺毋滥。第二是时效过滤器比如短期进度类记忆只取最近 7 天到 30 天的避免翻出几个月前早就过时的决策。第三是类型优先级个人偏好类记忆通常全局注入项目上下文类只在对应该项目时注入。注入方式也值得设计。我不建议把所有检索到的记忆一次性塞给模型那样上下文会被冲淡。更合适的做法是把记忆分成两段一段是“该会话基本规则”数量少但优先级高放在提示词前部另一段是“相关背景资料”数量稍多作为辅助参考放在尾部。两段各取所需模型理解负担小。2.4 与 Claude 工具的集成方式我实现的 claude-mem 走的是模型上下文协议也就是常说的 MCP这条路。简单理解MCP 为外部工具提供了一种标准化接口让模型在对话过程中可以按需调用外部能力而不是把所有内容都硬塞进提示词。claude-mem 在这里注册了两个核心工具一个用于检索记忆一个用于写入记忆。检索工具的上游逻辑是当模型感觉当前对话涉及某个历史背景时主动发起查询获得相关记忆条目。写入工具则是当对话中出现了值得记住的信息时模型调用它把记忆沉淀下来。通过这种方式记忆系统不再是被动地在启动时注入一遍而是融入了对话流程按需唤起。这种集成方式的好处是记忆不会喧宾夺主但坏处是工具的调用时机完全取决于模型自己偶发性会强一些。实际跑下来模型在遇到“续写之前的内容”“项目的某个约定是什么”这类表达时调用检索工具的意愿很高整体效果能接受。3. 实操构建与调优要点3.1 数据表设计别拍脑袋建表如果不用现成的记忆项目而是想自己搭一个最小版本建议数据模型设计成下面这样。我实际使用的表结构分为三个层次原始事件表、萃取事实表、记忆向量表。-- 原始会话事件表 CREATE TABLE events ( id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, project TEXT NOT NULL, happened_at TEXT NOT NULL, raw_message TEXT NOT NULL ); -- 萃取后的事实表 CREATE TABLE facts ( id INTEGER PRIMARY KEY, event_id INTEGER NOT NULL, project TEXT NOT NULL, category TEXT NOT NULL, -- preference / project / progress content TEXT NOT NULL, importance INTEGER DEFAULT 5, -- 1-10, 后续调优用 created_at TEXT NOT NULL, expires_at TEXT -- 可空, 用于短期记忆 ); -- 向量索引表: 用 SQLite 的向量扩展 CREATE TABLE fact_vectors ( fact_id INTEGER PRIMARY KEY, embedding BLOB NOT NULL, -- 存储序列化后的向量 FOREIGN KEY (fact_id) REFERENCES facts(id) );三个表各司其职events 保留原始数据用于审计和追溯facts 存放精炼后的记忆内容fact_vectors 存放在向量检索中使用的嵌入数据。没有原始事件表萃取错了就无法溯源没有向量表检索就退化成全量文本匹配效率直线下降。事实表中的 importance 字段是我后加的。系统刚跑起来时我发现无论什么记忆都被同等对待导致重要约定被淹没。后来给每条记忆打重要性分检索排序时按重要性和相似度加权情况大为改观。这里有一个实用技巧重要性分不要靠规则引擎猜而是让萃取模型在生成记忆时顺便输出一个 1 到 10 的评分效果更自然。3.2 触发时机的选择不要每一步都记录刚开始我犯过一个错误让记忆萃取逻辑走得太频繁。每轮对话都去调用大型语言模型萃取记忆结果对话稍长记忆条目就爆炸式增长噪音远大于价值还烧了大量调用预算。后来我把触发时机优化成三个节点会话结束时批量萃取一次对话中出现明确建议、选择或决策时由模型判断主动写入一条用户手动执行“记住这条”时无条件写入。实际效果看会话结束时的批量萃取覆盖了绝大多数有效信息模型主动写入捕捉到了那些容易在批量萃取时被忽略的细节。手动写入则是最保底的一层比如你明确说“以后遇到这类问题直接按这个方案做”它就会一字不差地保存下来。这里建议不要为了省预算而省掉模型主动写入这个环节。批量萃取偏向“事后总结”主动写入偏向“即时记录”两者行为差距明显。我测试过只保留批量萃取的模式流失的记忆大约在三四成主要都是那些在对话里随口一提、事后连自己也忘了的重要约束。3.3 关键参数与配置推荐记忆系统的性能很大程度上由几个参数决定我给一组基于自己经验调出来的参考值不同场景可以微调。参数推荐值说明嵌入维度768 或 1024维度太低丢精度太高浪费空间相似度阈值0.72 到 0.80低于阈值的基本无关不注入单次检索条数5 到 8 条太多会稀释注意力重要性加权importance * 0.3 similarity让重要记忆优先出现短期记忆有效期7 到 30 天过期自动清理批量萃取调用频率每会话一次不要每一步都调相似度阈值是我调试最多的参数。阈值调低了检索出来的记忆跟话题八竿子打不着阈值调高了明明相关的记忆又匹配不上。以我的数据量来看0.75 左右是较均衡的点。判断方法很简单把几十条真实对话记录喂进去逐个检查检索结果的命中率命中率低于一半就往下调阈值噪音比例超过两成就往上调。3.4 多项目隔离与命名空间多个项目同一套 claude-mem 存储最大的风险是记忆串味。我把 project 字段当作命名空间每次检索时强制限定在对应项目里查。这个设计看似简单却避免了大量逻辑混乱。具体实现里我要求每个项目在配置文件中声明两个信息项目名和记忆目录。不匹配当前工作目录的项目记忆不会出现在检索结果里。还要注意项目名不要随便改动一旦改了老记忆全部失联。我在迁移过一次项目目录后得出一个经验项目名最好用仓库根目录的相对路径来生成而不是依赖用户自定义名称比如observability/alert-service这种结构稳定又自然。4. 踩坑记录与问题排查实录4.1 记忆重复系统反复记住同一条约定第一个大坑是记忆重复。会话结束批量萃取时模型可能对同一个事实稍微换个说法生成两条记忆比如“项目使用 pnpm 作为包管理器”和“这个项目应该用 pnpm 安装依赖”。这两条语义几乎一致检索时会同时命中白白占掉上下文位置还削弱了其他有效记忆的权重。我的排查思路和解决办法分两步。第一步是去重写入前对候选记忆做一次与已有记忆的相似度比对相似度超过 0.9 就丢弃新记忆。第二步是合并不去重老条目而是把新条目里出现的新信息追加到已有条目的内容里。试了一个月合并方案的效果明显更好因为它不会丢失信息也不会因为阈值不准而误删重要记忆。4.2 记忆污染旧记忆干扰新判断第二个大坑恰恰是记忆功能带来的记忆越多模型反而越容易被带偏。典型场景是某个项目的技术栈已经从小程序改成了 Web 应用但记忆里还残留着 1.0 版本时期的结论检索时被当成背景注入模型基于过时信息给出建议让整个会话都偏离方向。解决办法是“记忆降权”加“主动遗忘”。降权指的是调低历史久远条目的权重检索排序时乘以一个时间衰减系数时间越久越往后排。主动遗忘则是提供一个清理入口当我在对话中明确说“这条约定作废”时系统会把相关条目标记为 invalid不再参与检索。后者需要模型配合调用一个遗忘工具在实现上跟写入工具对称写起来并不复杂。4.3 频发故障速查表症状可能原因解决办法检索结果完全无关相似度阈值过低调高阈值到 0.75 以上重新测试相关记忆一条都查不到记忆没有写入成功检查萃取调用是否抛错看 events 表有没有新数据注入记忆后回答变差一次性注入条数太多减少到 5 条以内拆分全局规则和参考背景记忆串项目project 字段未填或匹配逻辑错误检查配置文件强制按目录归属判定存储体积增长过快短期记忆没有设置 expires_at设置过期字段定期跑清理任务启动会话明显变慢向量索引全量加载换用 SQLite 向量扩展或改用外部向量库4.4 成本与延迟控制记忆系统本身会消耗额外的大型模型调用这一点必须心里有数。批量萃取的每次调用成本按我自己使用的模型价格估算一轮会话大约是几分钱到几毛钱一个月下来也就几块钱到几十块钱完全可以接受。真正需要注意的不是单次成本而是失控的调用频次。延迟方面最容易被忽视的是启动注入那一环。如果每次启动会话都要等记忆检索返回才能继续对话那这个等待时间是实实在在的体感卡顿。解决办法是把记忆检索做成“后台预取 会话内按需再查”的两级模式。会话启动时先异步拉取最强的几条全局记忆用户已经可以开始输入对话进行中如果触发到检索工具再同步查询具体细节。这个改动把首屏等待减少了约一半。提示如果你发现记忆系统每个月花费明显超过预期第一件事不是换更便宜的模型而是检查日志里萃取调用和主动写入的频率分布。绝大多数情况下问题出在调用次数失控而不是单次价格。4.5 隐私与删除机制外部记忆相当于把对话内容持久化落盘隐私问题必须提前设计。我用的策略有三层路径白名单、内容过滤、遗忘接口。路径白名单保证只有明确指定目录下的会话内容会被萃取其他对话一概不管。内容过滤会在萃取前扫描原始消息命中 API 密钥、内部域名、手机号等敏感模式时直接跳过不写入。遗忘接口则参考真实产品的“清除记忆”需求可以按会话、按项目、按关键词三种粒度删除记忆。这里有一个容易忽略的细节删除记忆不只是删事实表里那行记录向量表里的对应向量也必须同步删。如果只删其中一个检索时要么查到无头信息要么向量索引出现孤儿数据影响后续准确度。我在实现遗忘功能时用事务包裹两个删除操作保证要么都删掉要么都不删。这个看起来低级的问题实际确实出现过当时排查了很久最后定位到就是孤儿向量在捣乱。我的几点实操体会折腾 claude-mem 这套方案的过程中我最大的一点体会是记忆系统真正的价值不在“记住”而在“想得起”。存东西很容易但要在恰当的时机把恰当的记忆送到模型面前让模型觉得这些背景像是自己本来就知道的才算是合格的设计。这个度的把握没有银弹只能靠真实对话样本反复测试调整检索逻辑和注入方式。另外分享一个后来一直用着的小技巧给记忆加一个“来源会话编号”字段。平时用不到但一旦哪条记忆被模型反复错误引用你可以立刻回溯到产生这条记忆的原始对话判断萃取过程是否扭曲了原意。这个溯源字段成了我调试记忆系统最重要的抓手加的时候只是顺手用起来才明白它的分量。做类似项目时建议你也从一开始就保留这个信息成本极低收益长期存在。