1. 跨会话失忆:Claude落地Agent时的第一道坎
如果你跟我一样,把Claude Code当成日常开发的主力助手,迟早会遇到一个很拧巴的场景:上个会话里刚讨论完的接口设计、写进代码里的约定、排除过的坑,换个新会话再问,Claude表现得像个第一次见面的陌生人。这不是它变笨了,而是所有大模型对话默认都是"无状态"的——每次会话开始时,模型手里只有System Prompt和上下文窗口里的内容,一旦会话结束,记忆就清零。
很多人第一反应是:上下文窗口不是有200K吗?够大了吧。窗口大确实能装下更多内容,但它解决的是"单次会话内信息量"的问题,不是"跨会话连续性"的问题。我自己试过把历史对话全文塞进System Prompt,效果很差:一是Token占用太高,一次会话光历史就能吃掉几万Token;二是噪音太多,真正有用的决策散落在大量闲聊和调试过程里,模型反而被无关信息干扰;三是成本问题,每次请求都要重新计算这些Token的费用和延迟。
这套方案的另一个变种是"手动保存关键信息"——把结论复制到笔记文件里,下次再粘给Claude。偶尔用还行,一旦项目复杂起来就崩了:你会忘记录入、忘了在哪条笔记里、笔记和代码不同步。真正能扛住长期使用的做法,是让工具自动完成记忆的“写入—整理—召回”全过程。这就是我搞 claude-mem 的出发点:给Claude加一个轻量、可检索、跨会话的记忆层,让它在下次见面时还记得你是谁、你做过什么、你有哪些偏好。
这个名字没什么玄机,就是 "Claude memory" 的缩写。它不是Claude官方自带的功能,而是在Claude API和Claude Code之上搭建的辅助组件。你可以把它理解成一个外挂的“第二大脑”:平时聊天过程中,它默默记录那些值得留存的判断和偏好;新会话开始时,它把相关记忆重新放回到Claude的眼前。做这个项目之前我调研过市面上的记忆方案,有的太重、依赖外部数据库;有的太傻、只做简单的全文存取;claude-mem 的设计原则就三条:本地优先、结构化存储、按需注入。
2. claude-mem的记忆分层:会话、档案与检索各司其职
2.1 为什么不能只存聊天记录
早期版本里我干过一件笨事:把所有对话全文写入SQLite,等新会话开始时把最近几十条消息全捞出来塞给Claude。结果很快就发现两个问题:存储膨胀得厉害,一周对话就攒了几十万字;召回的“料”太杂,有用的事实淹没在废话里。后来我读了一些Agent记忆系统的设计思路,才意识到记忆不能是“堆料”,必须分层。
claude-mem 把记忆分成三层,各管各的,互不干扰:
| 记忆层 | 存什么 | 存储形式 | 谁来触发写入 |
|---|---|---|---|
| 短期会话记忆 | 当前对话上下文、临时状态 | 内存 + JSON | 对话进行中实时写入 |
| 长期档案记忆 | 用户偏好、项目约定、关键决策 | SQLite 结构化条目 | 会话结束时总结写入 |
| 检索记忆 | 历史对话摘要、关键技术片段 | SQLite + 全文索引 | 异步后台处理 |
短期会话记忆解决的是“一次长对话内部的连续性”,Claude原生能力已经很强,claude-mem 不太插手这一层,只在必要时做进度标记。长期档案记忆是核心,它保存的是“隔了好几天还能用”的信息。检索记忆则是兜底,当档案里没有现成答案时,通过全文搜索从历史对话里找线索。
2.2 档案条目的最小单位
长期档案如果只存一段段的总结文字,时间久了还是变成新的大杂烩。所以我把档案拆成最小可检索的记忆条目,每条只记录一个独立的事实、偏好或决策,结构大概是这样的:
{ "id": "mem_8f3a2c", "type": "preference | decision | fact | todo", "content": "用户不接受 YAML 配置复杂嵌套,偏好扁平结构", "source_session": "2025-06-11-feature-refactor", "created_at": "2025-06-11T14:20:00Z", "last_accessed": "2025-06-20T09:00:00Z", "tags": ["config", "coding-style"], "confidence": 0.9, "expire_at": null }type字段决定了记忆的优先级和过期策略。比如preference类条目会一直保留,直到用户明确推翻;todo类条目在确认完成后自动标记失效;fact类条目如果跟后续新条目产生冲突,系统会留着两条并标记为“待复核”。content的写法也有讲究,后面我会专门讲什么样的表述才是好记忆。
2.3 为什么不把所有东西都做成向量
现在一提记忆就默认要上向量数据库,我一开始也踩过这个坑。后来发现对 claude-mem 这种规模来说,全文索引 + 关键词过滤很多时候比向量检索更稳。原因很简单:项目里的代码规范、用户偏好这类记忆,本质上是精确匹配信息,你搜“REPL 风格配置”,就得把那条配置找出来,语义扩展反而容易带偏。SQLite 自带的 FTS5 全文索引够快,零依赖,又不用单独起一个服务。
向量检索我也保留着,但只用在“模糊联想”场景:比如用户说“我不喜欢那套复杂的流程”,向量检索可以把“流程繁琐、配置多、嵌套深”相关的记忆召回。大部分场景下,先做标签过滤 + 全文检索,再补少量向量召回,已经比单纯用向量好用得多,而且本地跑的延迟基本可以忽略。
3. 写入与召回链路:一次对话是怎么变成长期记忆的
3.1 触发时机:总结与归档策略
记忆不是鸡毛蒜皮都记,也不是等对话结束才一口气处理。我总结了四个写入时机:
- 即时写入:对话中出现明确的偏好表达、决策结论、用户纠正行为时,立刻生成一条档案条目。比如用户说“这个方案太绕了,以后接口名直接叫
run()”,这句话30秒内就该进数据库。 - 分段总结:单次会话超过一定轮数(我设的是 30 轮)后,对前面较旧的部分做局部总结,释放上下文空间。
- 会话结束归档:会话结束后,把所有未归档的重要信息汇总成摘要,并跟已有档案合并。
- 定期压缩:每周对旧记忆做一次“记忆保鲜”检查,压缩互相重复的条目,失效的标记删除。
分段总结这个动作很关键。上下文窗口再大也有上限,如果一次会话聊了100轮,后面基本是在“边聊边忘前面”。claude-mem 会监控 token 用量,超过阈值(默认是窗口的 70%)就触发一次中间总结,把靠前的次要对话压缩成几条摘要,然后告诉 Claude:“以下是此前的阶段性进展摘要,继续当前任务。”实测下来,长会话的有效处理能力明显提升,不会聊到一半模型开始“失忆”。
3.2 召回:新会话开场怎么“想”起来
新会话建立时,claude-mem 做的事情其实是一套“记忆唤起”流程:
- 注入用户档案卡片(包含姓名/项目角色/核心偏好,控制在 300~500 token 内);
- 从历史会话摘要里筛选最近活跃的 5~10 条,作为“最近在忙什么”的上下文;
- 扫描当前开场消息中提到的关键词(比如文件名、技术栈、动作指令),做一次检索召回;
- 把召回结果按相关性和时间排序,截断到预设的 token 预算内,统一注入 System Prompt。
这套流程在 Claude Code 里可以通过 MCP 工具暴露出来,让 Claude 自己决定“要不要翻记忆”。我会在下一节详细说接入方式。重要的是:注入不是越多越好。Token 预算内放10条强相关记忆,效果远好于塞100条弱相关记忆。我给 claude-mem 的默认注入上限是 1500 token,超过这个数,宁可漏一点,也不污染主线。
3.3 记忆的置信度与冲突处理
写记忆容易,改记忆难。假设上周用户说“后端用 Python”,这周又说“迁移到 Go”,两条记录都在库里,新会话召回时模型看到两个相反的结论,肯定会懵。claude-mem 的做法是:不直接删旧记录,而是给每条记忆加confidence和supersedes_id字段。新产生的记忆如果与旧记忆冲突,新条目覆盖时会把旧条目标记成superseded,但保留历史以便追溯。
这点我觉得是记忆系统最容易被忽视的地方。很多人的第一版记忆工具只会“增”,不会“改和删”,结果用几个月后库里全是过时的垃圾记忆,召回质量直线下降。claude-mem 的清理机制我放在最后一节细讲,但设计上从第一天起就要把“记忆会过期”当成默认前提。
4. 部署与接入:把claude-mem挂到Claude Code旁边
4.1 环境准备与初始化
claude-mem 的部署方式我刻意做得比较轻,不依赖 Docker、不依赖云服务,本地一个进程就能跑。环境中只需要 Node.js 18+ 和 SQLite(Node 生态里我直接用的better-sqlite3,省去了单独装 SQLite 的麻烦)。
初始化流程分三步:安装依赖、初始化数据库结构、配置 MCP server 地址。数据库文件默认放在项目的.claude-mem/目录下,方便跟着项目走;如果你有多个项目共用一个记忆库,也可以把DATA_DIR指到同一个路径。
npm install -g claude-mem claude-mem init --data-dir .claude-mem初始化后会在.claude-mem/下生成几个文件:mem.db(核心数据库)、config.json(记忆策略配置)、log/(运行日志)。config.json 里可以调的东西不少,我最常用的几个配置项是:max_history_retrieve(召回的条目数量上限)、summary_topic_threshold(会话轮数达到多少触发中间总结)、token_budget(每次注入记忆的 token 预算)。
4.2 以 MCP Server 方式接入
claude-mem 对 Claude Code 的接入走的是 Model Context Protocol(MCP)。MCP 可以理解为“给模型外接工具的标准接口”,Claude Code 本身就是 MCP 客户端的典型实现。把 claude-mem 变成 MCP server 之后,Claude 在对话过程中可以直接调用这几个工具:
| 工具名 | 作用 | 何时调用 |
|---|---|---|
remember | 主动写入一条记忆 | Claude 认为用户表达了一个应记住的信息时 |
search_memory | 检索与当前问题相关的历史记忆 | 新任务开始、用户问题看起来依赖旧上下文时 |
summarize_session | 对当前会话做一次阶段总结 | 会话接近 token 上限或结束 |
update_memory | 修改/标记记忆条目 | 用户纠正了过去的结论时 |
MCP server 的注册配置很简单,在 Claude Code 的配置文件里加一段:
{ "mcpServers": { "claude-mem": { "command": "claude-mem", "args": ["serve"], "env": { "DATA_DIR": "./.claude-mem" } } } }配置完成重启 Claude Code,输入/mcp能看到 claude-mem 在线,就可以正常使用了。我建议先用一句话测试:“记住,我所有测试环境的数据库统一用 test 作密码前缀。”然后新开一个会话问“测试环境数据库密码有什么规范”,如果能正确回忆出来,链路就是通的。
4.3 纯 API 场景的接入方式
如果你不是在 Claude Code 里用,而是通过 Claude API 搭建自己的应用,那 claude-mem 也可以直接作为库来调用,不需要 MCP。核心就两个函数:写入和检索。
from claude_mem import MemoryClient mem = MemoryClient("~/.claude-mem/mem.db") # 写入一条记忆 mem.remember( content="用户在代码评审中强调:函数命名要自解释,禁止用单字母变量", type="preference", tags=["code-review", "naming"], ) # 检索与当前任务相关的记忆 hits = mem.search( query="代码规范 命名 评审", limit=5, )这种 API 集成很适合自建 Agent 产品。我在一个内部工具里就是这么做:用户每天第一次打开小程序,先把记忆库里跟该用户相关的偏好全部捞出来组装成 System Prompt,相当于给每个人都配了“专属人设”,比每次从零开始的效果强太多。
5. 让记忆不变味:写入规则、压缩提示与人工复检
5.1 值得记忆的,其实就四类
记忆系统最怕“什么都记”。我对比过几个版本的写入规则,最后把“值得记”的信息收敛成四类:
- 偏好与风格:用户明确说喜欢/不喜欢什么。特征是从句里带“以后”“永远”“都要”这类绝对化表达。
- 决策与结论:在多个可选方案里拍板选了一个。特征是提到“决定”“不选”“就用它”。
- 项目事实:能减少重复提问的稳定信息,比如技术栈、目录约定、部署环境地址。
- 待办与承诺:明确说“下一步要做什么”“下周解决什么”。
判断要不要写入的简单标准是:这句话如果一周后还成立吗?如果成立,就值得记;如果只是当前上下文里的临时信息,就不记。“今天先把登录接口写完”是不该记的,“用户习惯用 POST 而不是 PUT 来做更新操作”才是该记的。
5.2 压缩提示词决定记忆的质
claude-mem 依赖一次 Claude 调用来做会话总结和记忆抽取,压缩提示词直接决定了产出的记忆质量。我迭代下来的模板核心大概长这样:
你正在为一个长期使用的记忆系统抽取信息。 请从以下对话中找出值得长期保留的内容,并输出为JSON条目: 1. preference:用户的明确偏好,必须带有“喜欢/不喜欢/希望/不接受”等语气 2. decision:明确的决策结论,带方案对比的更佳 3. fact:稳定的项目事实,能避免未来重复提问 4. todo:明确的后续行动,但只保留超过一周时效的 过滤掉:问候、客套、调试过程的中间状态、临时的进度信息。 每个条目控制在 30 字以内,使用陈述句,避免“用户说/用户认为”这类转述前缀。最后一句“避免转述前缀”很关键。如果提炼出来的记忆是“用户说他认为接口应该统一前缀”,这句话到了下次调用里就变成一种待确认的八卦信息。应该直接记成“所有内部接口统一使用 /api/internal 前缀”。Claude 能不能正确做出这种改写,就看提示词有没有讲清楚“你要写的是结论本身,而不是描述谁说了什么”。
5.3 定期人工复检:记忆库版“断舍离”
自动写入再强,也得给人留一个检视入口。claude-mem 提供一个简单的审查命令,把最近新增的记忆条目按 type 分组列出来:
claude-mem review --limit 30我自己的习惯是每周花十分钟过一遍。重点看三类条目:已经失效的 todo 删掉;重复描述的 preference 合并;content 写得模棱两可、只有自己看得懂的,当场改写成明确陈述句。别小看这十分钟,记忆库的长期可用性基本就是靠这种人工复核撑起来的。纯粹依赖自动抽取的库,三个月后要么肿胀不堪,要么专精度越来越差。
6. 实测避坑:Token开销、矛盾记忆与数据清洗
6.1 Token开销没有想象中大,但控不住就麻烦
有朋友一听“每次对话都注入记忆”,立刻算账:1500 token 的注入,一天 100 次对话,一年下来费用可观。实际用下来,开销大头不在注入,而在后台总结。每 30 轮触发一次中间总结、每次会话结束再来一次归档总结,等于把部分对话内容重新喂给模型处理一遍。claude-mem 里我给两次总结都开了“模型降级”配置:优先级高的总结用强模型,批量处理、不那么重要的总结用中等模型,成本能压掉一半以上。
如果你的预算极其敏感,还可以把中间总结的触发轮数调高,比如 50 轮。会话短就基本不触发,真正触发总结的只会是那种聊了几十轮的长对话,这类场景多花点钱是值得的。
6.2 矛盾记忆的处置不能靠“删旧存新”
前文提到 claude-mem 用supersedes_id保留冲突记录。实际用下来,这个设计的价值主要体现在“用户自己改主意了”这件事上。用户今天说“不要慢查询优化”,下个月可能又说“响应太慢了,得优化一下”。如果旧记忆直接删除,新召回时没有历史对照,模型可能把用户的新需求当成孤立需求来处理,反而少了上下文。
正确做法是:新记忆写入时加上“取代某条旧记忆”的关系,但召回模块默认只暴露最新的那版。只有当用户主动追问“我之前说过什么”时,才把被取代的旧记忆翻出来。这个“可追溯但不主动打扰”的策略,我实测下来体验最好。
6.3 数据量上来之后:建立索引与淘汰机制
记忆库用久了,SQLite 里会有几万条记录。不做清理的话,FTS5 全文搜索会明显变慢。我的处理策略是三管齐下:
- 召回只查
last_accessed最近 90 天内的活跃记录,冷数据不入常规查询; - 每周定期把已过期 todo 和重复条目物理删除;
- 对单条超长内容做切分,避免一条记忆占几个 KB、检索和注入都难受。
你还可以用一个更狠的淘汰策略:把记忆库当成“栈”,总量超过 N 条时,自动把confidence低且长时间没被访问的记录压缩进归档表,主表永远保持轻量。这套淘汰机制配合前面说的人工复检,记忆库用上一年也不会有臃肿感。
6.4 隐私边界:本地优先不是一句空话
因为 claude-mem 的所有数据都落在本地 SQLite 文件里,我敢在里面记录一些真实偏好和业务决策,不会担心数据被拿去训练或泄露。如果你要多人协作或同步,可以只把加密后的归档文件推到自己的私有存储里,不要让原始记忆库离开本地。
接入 Claude API 时会涉及把对话内容发送给模型做总结,这一点没办法完全避免,毕竟是靠模型提炼记忆。我的建议是敏感项目单独用 claude-mem 配置一个开关,可以关闭自动总结,改成人工复制重要内容进去手动录入。这个开关大多数情况下用不到,但真到了项目敏感度高的时候,它会保住你的底线。
最后再分享一点个人体会:搭 claude-mem 最大的收获不是省了多少 Token,而是它逼着我把“什么信息值得让 AI 记住”想清楚了。以前我依赖上下文窗口的无限续杯,总觉得自己跟 AI 的每次合作都是“露水情缘”;有了记忆层之后,同一个项目里 Claude 的产出质量一次比一次贴合我的习惯,这种积累感是单靠调 Prompt 换不来的。如果你正在做 Agent 类项目,建议从第一天就把记忆层的接口留好——后面数据攒多了再回头补,成本和复杂度都会大得多。