1. 这个工具到底解决什么问题
1.1 先讲一个我自己的翻车现场
上个月我接手一个中型 Next.js 项目,改动涉及数据模型、权限校验和一组历史遗留的 SQL 查询。头两天我把所有来龙去脉都交代给了 Claude Code,它帮我把重构方案、命名约定、哪些函数不能动全都理得清清楚楚。第三天早上我重新打开终端,新开一个会话准备继续改权限模块,结果它问我:“这个项目是做什么的?你的数据模型大概是什么样?”我当时就愣住了——我上个会话花了半小时喂给它的项目背景、约束条件、技术债清单,一夜之间全没了,它就像失忆了一样。
这不是 Claude 蠢,而是它的工作方式决定的。每个会话都是独立的,上下文窗口一关,模型不会记得上一个会话说过什么。就算你硬把旧对话贴回去,很快也会撞上 token 上限,而且越贴越贵。这个痛点太常见了,所以我开始研究怎么给 AI 助手“装长期记忆”,也就是今天想聊的 claude-mem。
1.2 claude-mem 的定位与设计思想
先说清楚:claude-mem 不是一个聊天机器人增强插件,而是一个给 Claude Code 这类终端 AI 编程助手增加跨会话记忆的工具。它做的事情可以概括成四步:把会话里产生的关键信息固化下来,转成语义向量存进数据库,下次会话开始时按需找回相关内容,再注入到模型的上下文里。
这点很重要——它做的不是“把所有历史对话都原样存下来”,因为那既浪费存储又会在检索时引入大量噪声。它做的是“提炼要点 + 语义检索”。一段 5000 字的调试过程,真正值得长期记住的可能就只有三句话:表结构变了、某个接口返回字段改名了、遗留问题在哪个模块。claude-mem 的设计目标就是把这三句话捞出来,而把情绪化的吐槽、无关的试错过程全部丢掉。
我会把它理解成给 AI 装了一个“第二大脑”,而不是“日记本”。日记本翻起来太慢,第二大脑是有人问“你之前怎么解决数据库锁超时的?”时,立刻把当时的结论递到你手上那种感觉。它解决的真正问题是:AI 聊天工具的上下文隔离,导致长周期项目的连续性断裂。
1.3 适合谁用,不适合谁用
我用下来的体会是,这个工具最适合三类人。第一类是像我一样整天跟 Claude Code 打交道、同时维护多个项目的开发者,尤其当你每天要开十几个新会话,每个会话都要重新交代一遍背景时,记忆工具的收益极大。第二类是做技术写作或代码考古的人,你经常要问“这个项目当初为什么这么设计”,记忆库能把历史决策带回来。第三类是在团队里推广 AI 辅助开发的人,你可以把记忆库当做一个项目知识沉淀层,新成员上手时直接查历史结论。
不太适合的场景也有。如果你只是偶尔用 Claude 写个一次性脚本、问答两句,那装记忆完全是负担,配置成本比收益还高。另外,如果你的项目高度敏感、代码不允许出本地环境,那你得先确认记忆数据的存储和检索是否全本地化,这个我后面会专门讲。记忆工具的本质是把你的开发过程变成可检索资产,但它不是免费的——它要占磁盘、占 API 额度、需要你偶尔维护。想清楚自己有没有这个需求再上,别盲目跟风。
2. 记忆是怎么存进去又取出来的
2.1 先从“工作记忆”和“长期记忆”说起
认知科学里有个成熟的分层模型:人脑分工作记忆和长期记忆。工作记忆是当前正在处理的信息,容量小、易丢失;长期记忆是沉淀下来的知识,容量大、按语义关联存取。大模型其实只有工作记忆——上下文窗口里有什么它就只能看到什么。窗口之外,它什么都不记得。
claude-mem 干的事,就是在模型旁边加一个长期记忆层。日常会话中产生的有用信息定期被“压缩”成记忆条目,写进本地数据库;等新会话需要时,再根据用户当前的问题或项目状态,把最相关的记忆条目拉回来,放到上下文窗口里。这个“存进去再取出来”的过程,本质上就是模仿人脑从海马体写入记忆、再由前额叶检索记忆的机制。
为什么要用语义检索而不是普通的关键词匹配?因为自然语言太灵活了。你昨天记的是“订单超时重试了三次才成功”,今天你问的可能是“为什么下单偶尔会卡住”,两者字面上几乎没有重叠词,但语义高度相关。关键词搜索会扑空,向量检索却能把它们关联起来。这就是 claude-mem 选择以向量数据库为核心存储的原因。
2.2 核心链路:捕获 → 提取 → 嵌入 → 检索 → 注入
整个记忆系统可以拆成五个环节,理解清楚每个环节,你就知道问题可能出在哪。
捕获最吃功夫。claude-mem 不是把整个终端日志都吃进去,那里面 90% 都是噪音。它一般挂在 Claude Code 的会话事件流上,监听助手消息、工具调用结果、用户的提问方式,从这些事件里筛选出值得记忆的片段。比如工具调用返回的报错信息和随后成功的修正动作,这就是一条高价值的“解决方案型记忆”。而那种“好的,我来看看”之类的寒暄,直接扔掉就好。
提取负责把原始文本压缩成结构化记忆。常见做法是让模型自己总结,把一段长对话压缩成 2、3 句结论,并打上标签,比如“决策”“BUG 修复”“架构约定”“命令备忘”。这个过程可以设置触发条件,比如对话轮次超过 N 轮,或者检测到报错关键字后再执行,避免每说一句话就调动一次模型,成本扛不住。
嵌入是把文本变成向量。这一步需要调用嵌入模型,把一段文字映射成几百维的数字数组。注意这里不需要用最强的模型,普通规模的嵌入模型足够用。向量生成后写入向量数据库,同时把原始文本也存一份,因为检索结果始终要展示给人或模型看的,不能只给一串数字。
检索发生在每次新会话启动时,或者当用户提问触发了某个关键词时。系统拿当前上下文或问题去查向量库,找出语义最接近的几条记忆,按相似度排序返回。这里有两个关键参数要调:返回条数 top_k 和相似度阈值。返回条数决定注入多少记忆,阈值决定哪些结果会被当作噪声过滤掉。参数调不好,检索回来一堆不相关内容,反而污染模型的判断。
注入是最后一步。取回的压缩记忆会被拼装成一段结构化的上下文文本,比如“以下是与当前任务相关的历史记忆,请参考这些信息继续工作”,然后通过系统提示或自定义指令塞给模型。这一步做得好不好,直接影响模型是“自然地使用记忆”还是“生硬地读一段话”。我的经验是,与其把一堆记忆原文堆给模型,不如先让模型读一遍再归纳成几条约束,效果更稳定。
2.3 检索参数到底怎么定
很多人第一次配 claude-mem 时会纠结 top_k 和相似度阈值,我直接说结论:top_k 设 5 到 8 之间比较稳,相似度阈值设在 0.72 到 0.78 之间需要看你用的嵌入模型。阈值低于 0.65 会混入大量弱相关记忆,高于 0.85 则可能什么都召不回来,导致记忆系统形同虚设。
这里有个平衡。top_k 太大,注入的上下文太长,既费 token 又容易让模型“花眼”,分不清哪些记忆更重要。top_k 太小,又容易漏掉关键信息。我的建议是先把阈值调严一点,比如 0.80,把 precision 拉高,观察一段时间再逐步放宽到 0.75。毕竟记忆工具的第一原则是“宁缺毋滥”——一旦把不相关的记忆注入到上下文,模型的回答质量可能比没有记忆时更差。
3. 从零配置一个记忆系统
3.1 安装前置条件与完整步骤
提醒一句:claude-mem 是社区驱动的开源方案,不同分支配置方式有差异。下面这套是我自己试过、走通的通用流程,你可以照着操作,遇到版本差异就查对应文档。
前置条件就三个:装好了 Claude Code 并能正常调用模型;本机有 Node.js 18 以上版本;准备一个本地或远程的向量数据库实例。我就用 Chroma 做演示,因为它在本地跑起来最省事,一条命令就能启动。
安装核心组件可以按下面几步来:
# 1. 安装 claude-mem 本体 npm install -g claude-mem # 2. 启动本地向量数据库(以 Chroma 为例) docker run -d --rm \ -p 8000:8000 \ -v chroma-data:/data \ chromadb/chroma # 3. 初始化配置目录 claude-mem init初始化完成后,需要把嵌入模型的 API 地址和向量数据库地址填进配置文件。这是最容易被忽略的一步。claude-mem 默认会找环境变量里声明的地址,你最好在 shell 配置里显式写清楚:
export CLAUDE_MEM_DB_URL="http://localhost:8000" export CLAUDE_MEM_EMBED_API_URL="https://你的嵌入服务地址/v1/embeddings" export CLAUDE_MEM_EMBED_API_KEY="sk-xxxx"这里有个容易踩的坑:嵌入 API 和对话模型 API 未必要用同一个服务商。对话模型贵但聪明,嵌入模型便宜且够用,可以混用。我目前就是对话用 Claude,嵌入用轻量模型,成本能省不少,检索质量也没有明显下降。
3.2 存储结构设计
配置完成后,claude-mem 通常会在用户目录下创建一个隐藏的数据目录,比如~/.claude-mem/。里面一般有两类数据:一类是 SQLite 文件,存结构化数据,比如记忆条目本身、会话 ID、时间戳、项目名;另一类是向量索引目录,存嵌入向量和索引元数据。
这个设计是有讲究的。向量数据库擅长做相似度检索,但在“按项目过滤”“按时间删除”“统计记忆数量”这类业务查询上性能一般。SQLite 正好补齐这个短板。所以实际查询往往是两步:先用 SQL 按条件过滤出候选记忆 ID,再拿这些 ID 去向量库做精确匹配,或者反过来。理解这个双层结构后,你就知道为什么记忆工具的数据清理不能只删一边,两边都要清,否则会出现“有记录但查不到向量”这种幽灵数据。
3.3 接入 Claude Code 的两种主流方式
把记忆系统接进 Claude Code,目前实践中有两种主流方式,各有利弊,我建议你先用第二种。
第一种是 hook 方式。Claude Code 支持在事件发生时执行外部命令。你可以在配置里声明一个 hook,在每次会话启动时执行claude-mem recall,把返回的记忆文本作为前缀注入初始上下文中。这种方式的优点是全自动、零干预,缺点是每条记忆都会无差别注入,即使当前任务用不上,占用的 token 也不会少。
第二种是命令方式。在 Claude Code 的项目指令文件(比如CLAUDE.md)里写清楚:每当用户提问涉及历史决策、重构背景、已有约定时,先运行claude-mem search "关键词"来检索相关记忆,再基于检索结果作答。这种方式的优点是记忆按需加载、精准性强,成本低;缺点是 Claude 不一定每次都记得去调命令,需要你在指令里写得足够明确。
我个人的选择是混合式:会话启动时只注入少量“项目全局约定”,比如“本项目常见的坑”“必须遵守的代码风格”;涉及具体问题时再按需搜索。用一条整体兜底 + 多条精细检索,效果比单纯全自动要好很多。
4. 核心代码实现(可以直接抄)
4.1 记忆写入:把一条会话固化成记忆
下面这段代码是你搭建一个最小记忆系统的核心。它做的事情很简单:接收一段文本,生成语义向量,连同元数据一起写入向量数据库。我用 TypeScript 写,方便你直接集成到 Node.js 服务里。
import { ChromaClient } from "chromadb"; const client = new ChromaClient({ path: "http://localhost:8000" }); async function embed(text: string): Promise<number[]> { const resp = await fetch(process.env.EMBED_API_URL!, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.EMBED_API_KEY}`, }, body: JSON.stringify({ input: text }), }); const data = await resp.json(); return data.embeddings[0]; } async function saveMemory(content: string, meta: Record<string, unknown>) { const collection = await client.getOrCreateCollection({ name: "claude-mem", }); const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; const vector = await embed(content); await collection.add({ ids: [id], embeddings: [vector], documents: [content], metadatas: [{ createdAt: new Date().toISOString(), ...meta }], }); return id; }注意几个细节。id一定要唯一,否则重复写入会互相覆盖。metadatas里务必带上创建时间和项目名,这是后面做过滤清理的基础。embed函数要处理失败重试,因为嵌入服务偶尔会超时,建议加一个简单的指数退避,比如失败后等 1 秒、2 秒、4 秒再重试。我在实际使用中试过,不加重试时一个月大概会丢 2 到 3 条关键记忆,加了三行重试代码后就再没丢过。
4.2 记忆检索:让相关记忆回到上下文
写入只是存储,真正影响体验的是检索。下面这段演示了如何根据当前问题找到相关记忆,并把结果整理成模型友好的文本块。
async function recall(query: string, project: string, topK = 5) { const collection = await client.getOrCreateCollection({ name: "claude-mem", }); const queryVector = await embed(query); const result = await collection.query({ queryEmbeddings: [queryVector], nResults: topK, where: { project: project }, }); const docs = result.documents[0] ?? []; const distances = result.distances?.[0] ?? []; return docs .map((doc, i) => ({ text: doc, score: 1 - distances[i] })) .filter((item) => item.score >= 0.75) .map((item) => item.text) .join("\n---\n"); }这里我用where条件把检索范围限制在当前项目,避免把 A 项目的记忆混进 B 项目。1 - distances是把距离换算成相似度分数,用于过滤阈值判断。实际使用中,我还会把检索到的记忆拼成一段带标记的文本,再交给 Claude。一个简单有效的模板是这样的:
以下是与当前任务相关的历史记忆。这些信息来自过往会话,可能已过期,如果与当前代码实际情况冲突,以当前代码为准。 <memory> 订单超时重试问题曾在 3 月修过,当时是 DB 连接池配置过小导致。 数据库表 orders 的 status 字段值含义:0=待支付,1=已支付,2=已取消。 </memory>那段“可能已过期,以当前代码为准”的声明特别重要。AI 是拿来即用的,它不会主动质疑记忆的真实性,你必须在提示里给它留一个纠偏出口,否则它会拿过期记忆去回答新问题,而且语气还很肯定。
4.3 自动触发与后台整理策略
手动调用记忆检索不够聪明,更好的做法是让系统在合适的时机自动触发。我维护的这套系统里挂了两个定时任务:懒写入和周期整理。
懒写入的意思是,不实时记录每一句话,而是攒够一个对话片段后再统一压缩。每次 Claude 回答完一轮,我会把那轮的用户问题、助手回答、工具调用结果拼成一小段,检查一下是否包含“修复”“决策”“约定”“注意”等高价值词。如果命中,就触发一次提取;如果没命中,就跳过。这样一天的对话里可能触发几十次,但真正入库存的只有几次,噪声大大减少。
周期整理是每周末跑一次的清理任务。逻辑很简单:删除 30 天前创建的、且相似度最高的重复记忆;把超过 90 天未命中检索的记忆标记为“待归档”,移出主存储。这么做主要是控制向量库的体积,否则跑半年后记忆库膨胀到几个 GB,每次检索的延迟会明显上升。我实测过,加了归档机制后,检索延迟从平均 1.2 秒降回 400 毫秒以内,体感差距非常大。
5. 常见问题与排查实录
5.1 检索结果不相关:八成是嵌入粒度问题
我遇到最多的问题是:查“用户登录失败排查”时,返回来的却是“支付接口超时处理”。乍一看检索系统坏了,其实不是。真正原因是记忆条目过于碎片化。如果你把一句话就存成一条记忆,它的向量只代表那一句话的语义,跟查询词的语义重合度自然低。
解决办法是:调整记忆提取的粒度,让每个记忆条目包含“问题背景 + 解决方案 + 结论”三个部分。可以试试在提取提示词里加上一条硬约束:“当遇到问题时,请描述完整上下文,而不要只记录答案。”另外,还可以把嵌入模型换成维度更高、语义理解更强的新版本,维度从 768 升到 1536 对召回率的提升非常明显,但成本也会增加,需要取舍。
5.2 记忆污染和过期:一定要设计纠偏机制
记忆污染是比检索不到更可怕的问题。有一次我的记忆库里存了一条错误结论:“数据库连接串里的密码不需要加密,直接明文写在配置里。”这明显是一条误导性记忆,但模型不去质疑它,反而反复引用。遇到这种情况,我建议大家做三件事。
第一,所有检索结果在注入时都要加“以当前代码为准”的声明,给模型纠偏依据。第二,给记忆条目标加“信任级别”,比如人工确认过的标记为高信任,自动提取的默认低信任,低信任记忆要经过两次命中才会被提升为高信任。第三,定期做记忆审计——query 一些你已知正确答案的问题,看记忆系统会不会给你返回错误信息,发现就立刻删除或修正对应条目。
5.3 API 成本越来越高:从源头压缩
成本暴涨通常有两个原因:一是嵌入调用太频繁,二是注入的记忆文本太长。前者可以通过降低写入频率解决,把“每轮对话都提取”改成“每 5 轮或出现关键词时才提取”,调用量能减少 70%。后者可以限制单条记忆的长度,比如超过 200 字就强制二次压缩,只保留最核心的结论。
另外,嵌入模型不一定选最贵的。很多场景下,一个 1024 维的轻量嵌入模型已经够用,成本只有旗舰模型的十分之一。选择时可以自己做一个简单的 recall 测试:准备 20 条查询,人工标注正确记忆,比较不同嵌入模型的召回率。对我来说,只要召回率在 80% 以上,价格越低越好。
5.4 多项目隔离与数据安全
如果你同时维护多个项目,一定要在元数据里打上项目名,并在每次检索时添加过滤条件。有人偷懒不做过滤,结果我在做 A 项目时,模型突然提到了 B 项目的内部接口命名,这种串味在代码审查时相当尴尬。更严重的是,如果 B 项目涉及敏感信息,这种串味就成了数据信任事故。
如果项目对数据安全要求高,有两个建议。第一,优先考虑纯本地部署的嵌入模型和向量库,所有计算都在本机完成,隐私数据不出内网。第二,对注入上下文前的内容做脱敏处理,比如把真实表名替换成代号,让模型只基于结构不基于真实数据做推理。这不是 claude-mem 的强制功能,但你可以通过自定义提取提示词来实现。
5.5 一条最重要的实战习惯
踩过这么多坑之后,我最想分享的一条经验是:记忆工具不是“存了就完事”,它是一个需要维护的系统。就像你不会把文件随便丢在桌面然后指望永远找得到,你也得定期整理记忆库。我给自己定了个规矩:每周五下午花十分钟,用一条命令导出本周新增记忆,扫一眼有没有错误或者过期的信息,顺手清理掉。这十分钟看起来不起眼,但正是这两个月来我的记忆系统能保持高可用性的关键。
另外,不要贪心。记忆库不是越大越好,也不是记得越细越好。一个好的记忆条目,应该是你在三个月后重新看到时,还能立刻想起来“当时为什么做这个决定”的那种。用它来判断该存什么、不存什么,才是这个工具真正的用法。