你有没有经历过这种时刻:上午刚和 Claude 把项目架构、技术选型、代码规范聊得明明白白,下午新建一个会话让它继续写代码,结果它连你用 React 还是 Vue 都记不起来了。又或者,你花了十分钟把业务背景、约束条件、个人偏好重新讲了一遍,聊到一半它又忘了你刚刚强调过"不要动数据库表结构"。这种"金鱼记忆"式的体验,是重度使用 Claude 的人最常吐槽的地方。
claude-mem 这个名字,就是冲着这个问题来的——一个专门给 Claude 加持久记忆的开源记忆工具。它的思路很直接:把每次会话里的关键信息自动抽出来存到本地,下次对话开始时再自动取出来塞回上下文,让同一个 Claude 表现得像一个真正"认识你"的助手。这篇文章我会结合自己实际接入和使用的经历,把这个工具背后的记忆链路、接入方法、以及那些不踩不知道的坑,一次讲清楚。如果你正在用 Claude API 做工具开发,或者被"多会话失忆"折磨到崩溃,这篇文章应该对你有用。
1. AI 对话的"金鱼记忆"问题:claude-mem 想解决什么
1.1 一个让我决定接上记忆的真实场景
我最近在给团队做一个基于 Claude 的代码审查助手,用官方 API 封装了一层内部服务。每天上午最忙的时候,我会在终端里跟它讨论某个模块怎么拆、接口命名用什么风格、哪些文件不要让 AI 碰。问题来了:下午我继续开一个会话让它按上午的结论写代码时,它完全想不起来我们上午聊了什么,甚至会给我一个和上午结论冲突的方案。
一开始我以为是 prompt 写得不够好,后来发现根本不是我的问题——所有基于大模型的 API 本质上都是"一次性的"。每次请求发出去,服务器那边就是一次独立计算,输入里不带历史,模型就不知道历史。客户端能做的最多是把之前几轮对话一字不差地再发一遍,但换个会话、隔几个小时,这些前提就全丢了。
还有一类更隐蔽的痛,是个人偏好。我希望 Claude 回中文、代码里注释用中文、变量命名保持英文,这类规则我几乎每次接入新工具都要重新说一遍。如果工具里有一个记忆层,说过一次就能长期生效,体验会完全不一样。claude-mem 解决的就是这两类痛点:项目级的关键决策,和用户级的长期偏好。
1.2 失忆的根源:无状态 API 与"硬塞上下文"的老办法
要理解 claude-mem 为什么存在,得先理解为什么大模型"记不住"。Claude 这样的对话模型,本身有很强的上下文理解能力,200K 级别的上下文窗口也足够装下一本书,但窗口一关,它就什么都不留。模型服务端不维护任何跨请求的用户状态,这是 API 层面刻意做的设计——无状态才能水平扩展、才能稳定提供千万级调用。
所以"让 Claude 记住你",本质上就是在每次调用前,把该记住的东西重新放进输入里。常见的老办法是"把历史对话全部塞回去":客户端把所有历史消息打包再发一次。这个办法在单次会话内有效,跨会话就抓瞎了,而且随着对话增长,成本和延迟都会线性上升。假设一个会话积累了 120K token 的历史,每次继续对话都要把这 120K 重新计费,哪怕你只问一句"接着写吧",对用户来说都是巨大的浪费。
更麻烦的是,硬塞上下文不区分什么该记、什么不该记。寒暄、临时讨论、中间过程的错误答案,全都混在一起。模型要在一大堆噪音里找出真正有用的线索,精度反而下降。这也是为什么 claude-mem 的思路不是"记录全部",而是"先抽取再回注"。它把每次对话交给一个专门的提取步骤,挑出值得长期保留的事实和决策,存进本地库;下次对话时只注入相关的部分。这样既不用带着全量历史跑,又不会丢掉关键上下文。
2. claude-mem 的记忆机制拆解:从捕获到回注的完整链路
claude-mem 这种记忆工具,本质是在"用户的业务代码"和"Claude 的 API"之间加了一层会思考的缓存。整个链路可以拆成四步:捕获、提取、存储检索、回注。每一步都有各自的设计取舍,下面分开说。
2.1 记忆捕获:做一个"旁路监听的记录员"
捕获层要回答的问题只有一个:对话内容从哪来?
我见过三种主流实现方式,claude-mem 类的工具通常会在其中选一种或组合使用:
- 中间层拦截:把 Claude API 的 base_url 指到本地服务,所有请求和响应先经过它,再转发到官方端点。这种方式对客户端代码入侵最小,无论你是用官方 SDK、curl、还是自己封装的库,只要改一个 base_url 就能接入。它还能同时看到"用户问了什么"和"Claude 答了什么",信息来源最全。
- MCP 服务器:Anthropic 现在把 MCP 当作标准扩展协议,Claude Code、Claude Desktop 都支持加载 MCP server。记忆工具可以注册成一个 MCP 服务,在会话中被模型主动调用"save_memory"或"query_memory"。这种方式最规范,等于模型自己决定什么时候去翻记忆,缺点是需要宿主客户端支持 MCP。
- 日志解析:如果你的对话记录本身就有落盘(比如 Claude Code 的会话日志、终端历史),可以直接定时解析这些文件。最省事,但格式一旦变化就容易挂。
我的建议是优先用中间层方案。原因很实际:它能捕获到真实的完整上下轮对话,而不是只有工具调用片段;同时它在网络层做拦截,和具体的客户端解耦,以后换前端工具不影响记忆层。
2.2 记忆提取:用"第二遍阅读"找出值得留下的信息
拿到原始对话后,下一步不是存起来,而是提取。claude-mem 常见的做法是:在对话结束后,把整段对话再交给模型读一遍,让它总结出值得长期记住的信息。这个过程也叫二次读取抽取,因为相当于用"第二遍阅读"的视角去筛选第一遍对话里的重点。
我最初犯过一个错误:不加筛选地把所有消息都存入记忆库。结果就是记忆库膨胀飞快,检索的时候总被无关条目干扰。后来我把提取 prompt 改成了下面这个模式,记忆质量明显提升:
请仔细阅读以下用户与 AI 的对话记录,只提取值得在下次会话中保留的信息: 1. 用户明确表达的身份、偏好、习惯,例如语言、技术栈、沟通方式 2. 正在进行的项目的关键决策、约束条件、命名约定、禁止事项 3. 用户提供的事实性信息:术语定义、账号/角色、任务目标 4. 必须排除:寒暄、临时指令、一次性问题、情绪化表达 输出格式为 JSON 数组,每条记忆必须包含 text 和 reason 字段。 如果没有值得记住的内容,输出 []。这个 prompt 里有几个关键点。第一,明确要求"只提取",宁可少记也不要滥记;第二,要求输出 reason 字段,相当于给每条记忆一个"为什么当时要记它"的依据,后面做合并和淘汰时非常有用;第三,明确排除临时指令,避免把"帮我查一下天气"这种一次性请求也当成长期记忆。
2.3 存储与检索:SQLite 打底,向量检索进阶
提取出来的记忆需要落地存储。这个层级的选型看起来简单,实际差别很大。
| 维度 | SQLite + 全文检索(FTS5) | 向量数据库 + Embedding |
|---|---|---|
| 检索逻辑 | 关键词匹配、字面命中 | 语义相似度,换个说法也能召回 |
| 部署成本 | 零依赖,一个文件搞定 | 需要额外引入 embedding 服务和向量库 |
| 召回质量 | 依赖用户用词和记忆用词一致 | 对"今天那个服务"这种模糊指代效果好 |
| 适合场景 | 个人使用、几百条到几千条记忆 | 团队共享、跨项目、上万条记忆 |
| 维护成本 | 极低,备份就是拷贝文件 | 需要维护 embedding 版本、重建索引 |
我的实际选择是先上 SQLite。个人场景下,你问的是"那个电商项目的登录逻辑",记忆里存的是"订单系统的认证设计",关键词匹配往往是够用的,因为项目命名通常稳定。SQLite 的 FTS5 全文索引不需要额外服务,备份直接拷贝文件,对个人工具来说省心很多。等到使用者超过三个人、大家描述同一件事的用词差异变大时,再引入向量检索也不迟。这类工具大多也预留了存储层接口,换起来不至于重写。
2.4 记忆回注:把记忆"装回"大脑的时间点
存储的最终目的是在合适的时机"想起来"。claude-mem 通常会在新会话构造第一条请求时,把检索到的记忆拼进系统提示词中。这里的细节决定成败。
第一是注入位置。记忆应该放在 system prompt 的末尾、业务指令的后面,而不是最前面。这样模型先理解当前任务的规则,再接住历史记忆,不会主次颠倒。第二是排序。相关度高的记忆优先,用户明确说过的偏好置顶,其他记忆按时间倒序排。第三是预算。我自己的经验是,记忆注入的部分最好控制在当前模型上下文窗口的 15% 以内,超过这个比例,模型的注意力会被大量历史条目稀释,回答质量会肉眼可见地下降。
一个可参考的回注结构:
[业务系统提示词...] 以下是该用户过往会话中留下的记忆,属于事实性信息,回答时请优先遵守: 1. [相关度高的记忆条目] 2. [次相关的记忆条目] ... 如果以上记忆与当前对话上下文冲突,以当前对话内容为准,但请明确告知用户冲突点。最后那句"以当前对话为准"很重要。它既防止了记忆对当前任务的过度绑架,又保留了发现冲突的机会。实际用下来,这个兜底条款帮我避免了很多次"AI 因为旧记忆而拒不执行新指令"的尴尬。
3. 接入实践:把 claude-mem 用在日常编码与对话工作流里
原理拆完,接下来是最容易上手操作的部分。我以本地部署为例,讲一下从零接入的完整路径。
3.1 环境准备与全局配置
claude-mem 这类工具通常是命令行工具,跑在本地,需要 Node.js 或 Python 运行时。用包管理器安装后,第一件事是初始化记忆库。命令大同小异,核心就三个动作:初始化目录、指定当前项目、检查配置。
# 初始化记忆库,会在当前用户目录下创建记忆数据文件 claude-mem init # 开启中间层服务,监听本机端口,准备拦截 API 请求 claude-mem serve --port 8790 # 设置当前项目名,后续所有记忆都会打上这个命名空间 claude-mem use --project my-api-service配置上有两个环境变量值得留意:一个是指向官方 API 的转发地址,一个是记忆库的数据目录。前者保证中间层能正常转发请求到官方服务,后者决定数据落在哪个路径。第一次跑的时候建议把日志级别开到 debug,因为你能直接看到有没有记忆被捕获、注入了几条,这对验证效果极其重要。
3.2 与 Claude API 和 Claude Code 的配合方式
接入官方 SDK 时,只需要把 base_url 指到 claude-mem 的本地端口。以 Python SDK 为例:
from anthropic import Anthropic client = Anthropic( api_key="your-api-key", base_url="http://127.0.0.1:8790" # 先经过 claude-mem 中间层 ) resp = client.messages.create( model="claude-sonnet-4-5", max_tokens=4096, system="你是我的代码助手。", messages=[{"role": "user", "content": "继续昨天的任务"}] )改这一行 base_url,等于给所有请求都加上了"先被记忆层看一眼"的环节。对已有的业务代码来说,侵入量小到可以忽略。
如果你的开发环境是 Claude Code,更推荐走 MCP 注册。给客户端配置添加一个 MCP server,指向 claude-mem 提供的能力:
{ "mcpServers": { "claude-mem": { "command": "claude-mem", "args": ["mcp"], "env": { "CLAUDE_MEM_PROJECT": "my-api-service" } } } }MCP 模式下,模型会在合适的时候主动调用记忆查询或保存接口,交互更自然,但要注意它不像中间层那样能无感捕获所有对话。如果你不想让模型自己决定"什么时候该记",中间层方案仍然是更可控的选择。
3.3 怎么确认记忆真的生效
很多人接完就以为万事大吉,结果第二天发现模型还是什么都想不起来,最后发现是记忆注入根本没触发。我建议按下面三步做验证:
- 看日志。启动时打开 debug 日志,确认出现了类似
memory injected: 5 entries, 780 tokens的记录,这说明注入步骤跑通了。 - 看数据文件。初始化目录下的记忆库文件会随着对话增加而增长,翻看里面的记录,确认提取出来的条目是有信息量的"事实",而不是"用户在聊天"这种废话。
- 做一次探测对话。新开会话,直接问"你还记得我们之前约定的接口前缀吗?"如果模型能准确回答,并且你没有在本次会话里重新提过这件事,那基本可以断定记忆链路正常工作。如果它答得模棱两可,就去查日志里这次请求到底注入了哪几条记忆。
有一个特别容易误导人的情况:当前会话里你刚好又说了一遍关键信息,模型记住是因为当前上下文,跟记忆注入无关。所以探测时一定要避免"换会话后还把信息再讲一遍"这种操作,否则你永远测不出真实效果。
4. 实测中翻过车的四个典型场景与规避方案
记忆工具用起来之后,真正的坑才开始暴露。下面这几个场景都是我在实际使用中踩过的,按严重程度排个序。
4.1 记忆串味:把 A 项目的约束灌进了 B 项目
最让我崩溃的一次翻车,是 B 项目的对话里突然出现 A 项目的模块命名规范。当时我想让 Claude 给 B 项目写一个新的支付回调模块,结果它把 A 项目里"禁止修改订单状态字段"的约束带了过来,差点让我上线一套错误逻辑。
问题根源很简单:记忆库是全局的,没有按项目做隔离。检索的时候用当前对话的关键词去匹配,只要两个项目都涉及"支付""订单""回调"这些词,A 项目的记忆就会被误当成 B 项目的上下文。
解决方案是在记忆模型里增加项目维度。每条记忆都带上 project 字段,写入时打标签,检索时强制过滤project = 当前项目。更进一步,还可以做一个"全局偏好区"和"项目专属区"的划分:技术栈、语言偏好这类 user 级记忆全局共享;接口约定、架构决策这类 project 级记忆必须在指定项目下才能被召回。这样既不会串味,又保留了跨项目有用的个人偏好。
4.2 Token 预算失控:记忆太多把上下文撑爆
第二个坑是记忆无节制增长。假设每条记忆平均 220 token,存上 300 条就是 66K token。Claude 3.5 Sonnet 的上下文窗口是 200K,看似还有余量,但你别忘了,业务系统提示词、对话历史、模型输出都要占空间。记忆如果占掉三分之一窗口,对话历史稍微一长,请求直接被截断,模型会忘记你当前正在聊的事情。
我做过一次粗略统计:注入记忆超过 50 条时,回答质量开始下降;超过 100 条时,模型频繁出现"记了但用不上"的现象,答非所问的概率明显增加。这不是玄学,而是注意力被稀释的必然结果。
所以现在我对记忆注入设了三条硬规则:单条记忆不超过 120 个 token,多出来的在提取时就压缩;注入前按相关度只取前 20 条;低相关度的旧记忆先做摘要合并。宁可把 20 条高质量记忆放进去,也绝不让 100 条垃圾记忆把上下文灌满。
4.3 隐私底线:把敏感信息当成了长期记忆
这个坑我建议所有人都提前设防。对话里只要出现了 API 密钥、服务器地址、本地绝对路径,而提取 prompt 又没有做敏感类别限制,很容易被当成"用户提供的事实性信息"存进 SQLite。我就在一次调试中看到记忆库里出现了一个完整的数据库连接字符串,当场惊出一身冷汗。
要解决它,光靠"提取时小心"是不够的,得从两层同时下手。第一层是过滤器:在捕获阶段用正则把疑似密钥、密码、令牌的内容直接打码,不进提取环节。第二层是提取约束:在抽取 prompt 里明确加一条规则——"如果信息属于 API 密钥、密码、IP 地址、绝对路径等敏感类别,只输出'用户已提供某类敏感信息',不要输出具体值"。这两层配合,基本能保证敏感原文不会落到记忆库。
另外,记忆库默认存储在本地用户目录,如果存放的机器有盘符加密(比如 macOS 的 FileVault),建议把数据目录放到加密盘里。如果是团队共享的记忆服务,还得考虑加密存储和访问控制,最好不要裸存。
4.4 "它根本没记住"的排查链路
比"记错"更让人困惑的是"它好像根本没记住"。用户明明昨天交代过的事情,模型今天表现得一无所知。我围绕这个问题拆过很多次,总结出下面这条排查链路,遇到类似情况按顺序检查基本都能定位:
- 注入层:打开 debug 日志,确认这次请求确实走了记忆注入,并且注入了记忆条目。如果这里就没有,问题在中间层配置或检索逻辑。
- 召回层:拿当前用户的提问,手动跑一遍记忆检索,看看 top 3 的命中条目是什么。如果召回结果跟当前问题完全不沾边,说明关键词匹配失败,或者记忆条目本身太抽象、没有落到具体项目或实体上。
- 排序层:召回本来命中了很多条,但真正有用的一条排在了第 12 位,而注入只取前 10 条,它就"被淹没"了。解决方式是调高相关度阈值,或者减少单次注入条数,保证精华能进。
- 使用层:召回正确、注入正确,模型却视而不见。这多半是 system prompt 里的引导语太弱。比如只说"以下是历史记忆",模型会把它当参考资料,而不是需要遵守的事实。改成"以下是该用户过往会话中留下的事实信息,回答时务必遵守,如有冲突请明确指出",效果立竿见影。
- 最后检查记忆条目本身的质量。如果存的是"用户在进行项目开发"这种没有实体的废话,那怎么检索都白搭。
这条链路我建议贴在项目 Wiki 里,团队里有人问"记忆到底有没有用"的时候,照着查一遍比自己瞎猜快得多。
5. 记忆质量调优:让 claude-mem 记住该记的、漏掉不该记的
接入稳定之后,剩下的工作就是持续打磨记忆质量。记忆系统是个典型的"垃圾进、垃圾出"的系统,提取阶段放过一条垃圾,后面检索阶段就会多一份噪音。
5.1 用"记忆等级"控制写入策略
我给记忆定义了三个优先级,写入时由提取 prompt 自动给每条记录打上等级:
- P0:用户明确要求记住的信息,或安全边界、个人偏好、身份类事实。这类永远保留,回注时置顶。
- P1:项目决策、接口约定、技术选型、当前任务的关键约束。这类在整个项目周期内有效,按相关度排序注入。
- P2:临时状态,比如"昨天调试时发现某个模块有一段可疑逻辑"。这类设 24 小时或 72 小时过期,到期自动清理。
实现起来就是在提取结果的 JSON 里加一个 priority 字段,存储时直接写入对应栏目。回注时按 P0 优先、P1 次之、P2 除非特殊情况不注入。这套机制加上之后,我的记忆库从"什么都存"变成了"有主次地存",注入后的回答质量提升非常明显。
5.2 自定义提取 prompt:让工具适配你的工作流
默认提取 prompt 适合通用场景,但真正好用的记忆工具一定要能适配你的工作流。比如我团队的项目里有一堆内部术语和模块代号,我就把术语表挂进提取 prompt,要求模型认出这些词并优先记录。下面是一个比默认更严格的生产级模板,你可以直接拿去改:
你是记忆抽取引擎。输入是一段用户与 AI 的对话记录。 规则: 1. 每条记忆必须能在未来会话中独立提供价值,否则丢弃 2. 输出 JSON 数组,每项包含: - text: 记忆内容,不超过 60 个字 - priority: p0 / p1 / p2,保持严格标准 - reason: 这条记忆未来可能被用到的场景 3. 单次对话最多输出 5 条,宁可少不可滥 4. 敏感信息(密钥、密码、IP、路径)不输出原文 5. 如果没有值得记忆的内容,输出空数组 [] 示例输出: [ {"text": "用户的后端技术栈是 Python FastAPI,不熟悉 Node.js", "priority": "p0", "reason": "后续代码建议会贴合其技术栈"}, {"text": "项目约定 API 前缀统一为 /api/v1,禁止出现 /v2 路径", "priority": "p1", "reason": "生成接口代码时需要遵守"} ]加了"reason 场景"这个字段之后,我排查"这条记忆为什么会被记下来"方便了很多,有时候还能发现提取模型把上下文理解偏了,及时修正 prompt。
5.3 定期清理与合并:记忆也需要"断舍离"
记忆系统不做维护,一定会慢慢腐烂。我的维护节奏是每周一次,具体做三件事:
- 查看本周新增记忆。用工具自带的管理命令列出新条目,快速扫一眼有没有明显误记或垃圾条目,有就手动删掉。
- 合并重复内容。比如记忆里同时存在"用户喜欢 Python"和"用户只用 Python 写后端",合并成一条更完整的描述。相似条目在文本层面就做一次聚类,避免同一个事实占用多条记忆的名额。
- 项目收尾后归档。当一个项目结束,把它的 P1 项目级记忆从活跃区移到归档区。这样下次检索时不会因为旧项目词汇相似而串味,也减轻了注入压力。归档区不是丢弃,万一项目重启还能恢复。
我还养成了一个习惯:每次新会话开始前,如果时间允许,先翻一眼最近几天的记忆条目。这让我对"模型即将看到什么背景"有数,比出了问题再查日志要省力得多。
说实话,claude-mem 这类工具让我重新理解了"给 AI 装记忆"这件事的价值。它不只是一个缓存或脚本,而是把模型从"用完即弃的状态函数"变成了"持续积累的工作伙伴"。我踩过几次坑之后,现在最深的体会是:记忆层的核心矛盾永远是"质量"和"成本",而解决矛盾的手段不是堆更多的记忆,而是更精准地筛选。最后再分享一个实用小技巧:在记忆注入的 system prompt 里给所有记忆条目加一个统一前缀标记,比如"(历史记忆)"。这样模型引用记忆时,日志里会留下明确痕迹,你一眼就能分辨它说的内容是来自记忆库还是来自当前对话,排查问题的时候会省掉大量猜测。下一步我打算把这个工具接进本地模型,让 Claude 和本地模型共用一套记忆库,再把扁平的记忆条目升级成结构化的知识关系图,让记忆真正连成网——那到时候再写一篇来分享。