1. 项目缘起与核心定位
第一次看到claude-mem这个名字,我的直觉是:这大概率是给 Claude 这类大模型做“记忆层”的东西。事实也确实如此。它要解决的是一个所有长期跟 AI 打交道的人都会遇到的痛点——模型本身没有跨会话的持久记忆。你这次跟它聊完一个项目的架构决策,关掉窗口,下次再开,它对你、对你的项目、对你们之前达成的共识一无所知,一切从头再来。
claude-mem就是冲着这个来的。它本质上是一套面向 Claude 的持久化记忆系统,把对话中产生的关键信息抽取、压缩、存储下来,在后续会话里按需召回,重新注入上下文。你可以把它理解成给 Claude 外挂了一个“长期记忆硬盘”,而不是只靠那点有限的上下文窗口硬撑。
它适合谁?三类人最该关注。第一类是重度使用 Claude 做开发的人,比如天天用 Claude Code 写代码、做重构的工程师,项目上下文反复丢失非常折磨人。第二类是把 Claude 接入自己产品/工作流的开发者,需要给用户提供“它记得我”的体验。第三类是对 AI 记忆机制本身感兴趣的技术爱好者,想搞清楚记忆抽取、压缩、召回这套链路到底怎么落地。
这篇文章我不打算写成官方文档的复述,而是按一个实际搭过、踩过坑的人的视角,把claude-mem的整体设计思路、核心机制、实操步骤、参数取舍和排查经验完整拆一遍。读完你应该能自己判断:这东西值不值得接进你的工作流,以及接进去之后怎么调才不翻车。
2. 整体设计与思路拆解
2.1 为什么“记忆”不能只靠加大上下文窗口
很多人第一反应是:上下文窗口不是越来越大吗,直接全塞进去不就行了?我一开始也这么想,实测下来完全不是这么回事。原因有三个,而且都很硬。
第一,成本和延迟是线性甚至超线性增长的。你把几万 token 的历史全塞进去,每次请求的输入 token 都在烧钱,响应也变慢。第二,长上下文里信息会被稀释。模型对上下文中段的注意力本来就弱,塞得越多,真正关键的那条决策反而越容易被淹没,这就是常说的“lost in the middle”。第三,上下文窗口再大也有上限,而一个人的项目历史是无限增长的,今天塞得下,三个月后一定塞不下。
所以claude-mem的核心思路不是“存更多”,而是“存得聪明、取得精准”。它把记忆做成一个独立的、可检索的存储层,而不是无脑堆上下文。这个定位决定了它整个架构的走向。
2.2 记忆分层:短期、长期、摘要三层结构
我拆下来,claude-mem的记忆大致分三层,这个分层是它设计的精髓,理解了这三层,后面所有操作都顺了。
- 短期记忆(会话内):当前这次对话的原始消息,还在上下文窗口里,直接可用,不需要检索。
- 长期记忆(跨会话):从历史对话里抽取出来的结构化条目,比如“用户偏好用 TypeScript 严格模式”“项目 X 的数据库是 PostgreSQL 16”,存进持久化存储,带向量或关键词索引。
- 摘要记忆(压缩层):把一整段冗长对话压缩成一段高密度摘要,保留决策和结论,丢掉寒暄和试错过程。
为什么要分三层?因为不同信息的生命周期和访问频率完全不同。当前会话的细节要全,历史决策要精,中间过程可以压。如果全用一套机制处理,要么太贵,要么太糊。分层之后,每一层用最适合它的存储和检索方式,整体效率和精度都上来了。
2.3 存储与检索的选型逻辑
存储这块,claude-mem常见实践是本地优先。为什么?因为记忆数据高度个人化,涉及你的项目细节、偏好、甚至一些敏感的业务上下文,放本地最省心,也省去了网络往返的延迟。通常用一个轻量数据库(比如 SQLite)做结构化存储,再配一个向量索引做语义检索。
检索这块是重点。纯关键词检索的问题是“换个说法就找不到”,纯向量检索的问题是“语义相近但事实不对”。所以实际好用的方案是混合检索:先用向量召回一批语义相关的候选,再用关键词或元数据(时间、项目、标签)做过滤和重排。这个组合我实测下来召回质量比单一方式高一大截,尤其是当你的记忆条目积累到几百上千条之后,差距非常明显。
提示:选型时不要一上来就追求“最先进”的方案。先用最简单的 SQLite + 关键词跑通全链路,确认记忆抽取和注入的逻辑没问题,再逐步加向量检索。反过来做,你会在调试检索效果上浪费大量时间,却搞不清问题到底出在抽取还是检索。
3. 核心机制与实操要点
3.1 记忆抽取:什么该记,什么该丢
抽取是整条链路的第一环,也是最容易做砸的一环。我见过太多人一上来就想“把所有对话都存下来”,结果记忆库迅速膨胀,检索出来的全是噪音。claude-mem的抽取逻辑,核心是判断信息是否具备“跨会话复用价值”。
具体来说,值得记的通常是这几类:用户偏好和约束(“我不喜欢过度注释”“统一用 pnpm”)、项目事实(技术栈、目录结构、关键配置)、已做出的决策及理由(“选 Redis 而不是 Memcached,因为需要持久化”)、未完成的待办和上下文(“这个重构做到一半,下一步要处理鉴权模块”)。
不值得记的:寒暄、一次性的调试输出、已经被推翻的中间方案、纯事实查询的答案(这些模型自己能答)。判断标准可以简化成一句话:这条信息在下次会话里,能不能帮我省掉重新解释的成本?能,就记;不能,就丢。
实操上,抽取一般交给模型自己完成,用一个专门的 prompt 让它从对话里输出结构化的记忆条目(JSON 格式,带类型、内容、标签)。这里有个坑:抽取 prompt 一定要约束输出格式,否则模型会自由发挥,返回一堆没法解析的自然语言,后面全链路都崩。
3.2 记忆压缩:摘要的粒度怎么定
压缩的粒度是个需要反复调的参数。压得太狠,细节全丢,召回回来没用;压得太松,等于没压,存储和检索成本下不来。
我的经验是按“会话段落”而不是“单条消息”来压。一次对话里往往围绕一个主题来回好几轮,把这几轮压成一段摘要,既保留了决策脉络,又去掉了冗余。粒度上,一段摘要控制在 100 到 300 字比较合适,太短信息不够,太长又失去了压缩的意义。
还有一个技巧:摘要里要显式保留“结论”和“理由”,而不是只留结论。因为下次召回时,模型需要知道“为什么这么定”,才能在新的情境下正确复用,而不是机械照搬。比如不要只记“用 PostgreSQL”,要记“用 PostgreSQL,因为需要复杂事务和 JSON 字段查询”。
3.3 记忆召回:注入时机与数量控制
召回决定了“什么时候把哪条记忆塞回上下文”。这里有两个关键决策:时机和数量。
时机上,常见做法是在每次新会话开始时做一次批量召回,把最相关的若干条记忆注入系统提示或首轮上下文。进阶做法是按需召回,在对话进行中,当检测到相关话题时再动态拉取。前者简单稳定,后者更精准但实现复杂。我建议先用前者跑通,再考虑后者。
数量上,宁少勿多。召回太多记忆,一是占上下文,二是引入噪音干扰模型判断。我的经验值是单次召回 5 到 10 条,按相关度排序,超过阈值的才注入。这个阈值需要根据你的记忆库规模调,库小的时候可以放宽,库大了必须收紧。
注意:召回的相关度阈值不要设死。我踩过的坑是阈值定太高,结果新项目冷启动时几乎召回不到任何记忆,体验反而比没有记忆还差。建议设一个“最低保底条数”,比如无论相关度如何,至少注入最近活跃项目的 2 到 3 条记忆。
4. 完整实操流程与关键环节
4.1 环境准备与依赖安装
先把基础环境搭起来。claude-mem这类项目通常以 Node.js 或 Python 包的形式提供,我按最常见的 Node 生态走一遍,Python 生态逻辑一致,替换包管理器即可。
# 确认 Node 版本,建议 18 以上 node -v # 初始化项目目录 mkdir claude-mem-demo && cd claude-mem-demo npm init -y # 安装核心依赖(以常见组合为例) npm install better-sqlite3 npm install @anthropic-ai/sdk这里选better-sqlite3而不是异步的 sqlite 库,原因是记忆读写是高频同步操作,同步 API 写起来更直观,性能也足够。异步库在这个场景下带来的复杂度收益不成正比。向量检索如果要用,可以再加一个轻量向量库,但初期不必。
4.2 记忆存储结构设计
存储结构直接决定了后面检索好不好做。我推荐一张主表加必要的索引字段,结构大致如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INTEGER | 主键,自增 |
| type | TEXT | 记忆类型:preference / fact / decision / todo |
| content | TEXT | 记忆正文,压缩后的高密度文本 |
| project | TEXT | 所属项目标识,用于过滤 |
| tags | TEXT | 逗号分隔的标签,辅助检索 |
| created_at | INTEGER | 创建时间戳 |
| last_used_at | INTEGER | 最近一次被召回的时间 |
| use_count | INTEGER | 被召回次数,用于热度排序 |
last_used_at和use_count这两个字段很多人会忽略,但它们非常有用。长期不被召回的记忆可以降权甚至归档,避免记忆库无限膨胀;高频召回的记忆可以优先注入。这就是一个简单的“记忆热度”机制,实测能显著提升召回质量。
4.3 抽取与写入的代码实现
抽取环节的核心是构造一个稳定的 prompt,让模型输出结构化结果。下面是一个简化示例:
const EXTRACT_PROMPT = ` 你是一个记忆抽取器。从下面的对话中提取值得跨会话复用的信息。 只提取以下类型:用户偏好、项目事实、已做决策及理由、未完成待办。 输出严格的 JSON 数组,每个元素包含 type、content、tags 三个字段。 不要输出任何解释性文字。 对话内容: {{CONVERSATION}} `;拿到模型返回后,一定要做 JSON 解析的容错。模型偶尔会在 JSON 外面包一层 markdown 代码块,或者多一句“好的,这是结果”。我的处理方式是先用正则把第一个[到最后一个]之间的内容抠出来,再解析,解析失败就丢弃这一批而不是让整个流程崩掉。
写入时,先做去重。同样的偏好被反复记录是常见问题,可以用内容相似度或简单的关键词匹配做一次过滤,避免记忆库里全是重复条目。
4.4 召回与注入的完整链路
召回发生在每次新会话启动时。流程是:拿当前会话的初始输入(或项目标识)作为查询,去记忆库检索,按相关度和热度排序,取前 N 条,拼成一段“背景记忆”注入到系统提示里。
function buildMemoryContext(memories) { if (!memories.length) return ''; const lines = memories.map(m => `- [${m.type}] ${m.content}`); return `以下是关于用户和项目的已知背景,请在回答时参考:\n${lines.join('\n')}`; }注入的位置很关键。放在系统提示的靠后位置,比放在最前面效果好,因为靠近用户输入的内容对模型的影响更强。这一点是我反复对比后确认的,同样的记忆内容,位置不同,模型的实际利用率差别很明显。
4.5 参数计算:召回数量与阈值怎么定
这里给一个可落地的计算方法。假设你的记忆库有 N 条,单次召回上限设为 K,相关度阈值设为 T。
- K 的经验公式:
K = min(10, max(3, floor(N / 50)))。库小的时候保底 3 条,库大了最多 10 条,避免注入过多。 - T 的设定:先用向量相似度跑一批测试查询,观察正确命中的相似度分布,取一个能过滤掉大部分噪音又不太严的值,通常在 0.7 到 0.8 之间(具体取决于你用的向量模型,必须实测校准)。
这两个参数不是拍脑袋定的,必须用你自己的真实数据跑一遍再定。别人的推荐值只能当起点。
5. 常见问题与排查技巧实录
5.1 记忆召回不相关怎么办
这是最高频的问题。排查顺序我总结成一张表:
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 召回内容完全不相关 | 查询向量质量差 | 换更强的 embedding 模型,或改用关键词兜底 |
| 召回内容沾边但没用 | 抽取时信息太泛 | 检查抽取 prompt,要求更具体的事实 |
| 该召回的没召回 | 阈值太高 | 降低阈值,加保底条数 |
| 召回一堆重复 | 写入没去重 | 在写入环节加相似度过滤 |
我的经验是,八成的问题出在抽取环节而不是检索环节。抽取出来的记忆本身就是模糊的、泛泛的,检索再准也救不回来。所以排查时先看记忆库里的原始条目质量,别一上来就调检索参数。
5.2 记忆库膨胀太快怎么控制
跑一段时间后记忆库动辄几千条,检索变慢、噪音变多。控制手段有三个:定期归档低热度记忆(use_count低且last_used_at很久以前的)、合并相似条目、设置单项目记忆上限。我一般会写一个定时任务,每周跑一次清理,把半年没被召回过的记忆移到归档表,主表只留活跃的。
5.3 注入记忆后模型反而变笨了
这个坑很隐蔽。有时候注入的记忆和当前任务无关,模型却被带偏,强行往记忆内容上靠。解决办法是在注入时明确告诉模型“这些是背景参考,不一定与当前问题相关”,并且控制注入量。记忆不是越多越好,无关记忆就是干扰项。
提示:如果发现某类记忆总是造成干扰,可以在注入时按类型过滤。比如纯代码任务就只注入 fact 和 decision 类型,不注入 preference,减少噪音。
5.4 跨项目记忆串味怎么隔离
如果你同时维护多个项目,记忆必须按项目隔离,否则 A 项目的技术栈会被错误地应用到 B 项目。做法很简单:每条记忆都带 project 字段,召回时强制按当前项目过滤。全局性的偏好(比如编码风格)可以单独标记为 global,允许跨项目召回。这个隔离一定要在检索层做死,不能靠模型自己判断。
6. 我踩过的坑与实操心得
说几个文档里不会写、但实际一定会遇到的点。
第一,抽取不要追求实时。一开始我想每轮对话都抽取一次,结果请求量翻倍,成本飙升,而且很多中间轮次根本没有值得记的东西。后来改成会话结束时批量抽取一次,效果一样,成本降了一大截。
第二,记忆内容要“自包含”。一条记忆脱离原始对话后必须能独立看懂。我早期存过“改成用那个方案了”这种条目,召回回来完全不知道指什么。后来强制要求抽取时补全主语和背景,召回质量立刻上来了。
第三,给记忆加时间戳并善用它。有些记忆会过期,比如“当前正在用 v1 接口”。召回时如果发现是很久以前的记忆,要么降权,要么在注入时标注时间,让模型自己判断是否还适用。
第四,别迷信全自动。我留了一个手动审查的入口,定期看看记忆库里都存了什么。跑一段时间你会发现,模型抽取的偏好和你真实的偏好经常有偏差,手动修正几条,整体体验会好很多。
这套东西搭起来不难,难的是持续调优。记忆系统的价值是随着使用时间累积的,用得越久,召回越准,省下的重复解释成本越多。如果你天天跟 Claude 打交道,花一个周末把claude-mem这套链路跑通,长期回报非常划算。后续想扩展的话,可以往多模态记忆(记住图片、文件)、记忆共享(团队共用一套项目记忆)这些方向走,但那是把基础链路跑稳之后的事了。