用 Claude 的时间一长,最折磨人的往往不是模型能力不够,而是它始终记不住你这个人。每次打开一个新会话,它都像刚入职的实习生,态度挺好,但完全忘了三天前你反复强调的项目背景和代码风格偏好。我后来实在忍不了,开始折腾社区里那个叫 claude-mem 的方案:用一层本地持久化记忆,把 Claude 的跨会话记忆真正留住。这篇文章不是我对着官方文档念经,而是把 claude-mem 这类“记忆层”从原理到落地完整跑一遍的实战记录,包括安装配置、记忆抽取、注入细节,以及连续使用一个月后遇到的坑和取舍。适合正在重度使用 Claude,又不想每次开工都重新交代背景的人参考。
1. Claude 的“跨会话失忆症”,到底卡掉了多少生产力
1.1 每个新会话都在重复劳动的真实代价
很多人刚开始觉得“新会话重新说一遍背景也没什么”,但真实场景里这笔隐性成本高得吓人。我手头有个长期维护的代码库,光是一个 API 封装模块就有二十多处设计决策:为什么用同步而不是异步、异常处理的统一模式是什么、字段命名走什么风格、哪些历史包袱打死不能碰。这些信息散落在旧对话里,每次开新会话,我得花三五分钟重新描述一遍,还经常描述不全。
更麻烦的是偏好类信息。比如我习惯让 Claude 在代码评审时优先挑并发安全的问题,而不是先抠命名;写周报时不要提具体加班时长;给某个项目的注释必须中英双语。这些偏好说了第一次,第二个会话它忘干净,第三个会话还得再说。一天开几十个会话,累积下来就是一个小时起步的无意义重复。
还有一个最容易被忽略的损失:Claude 记得越多,回答质量的连续性越好。项目状态在变,方案演进有脉络,如果每个会话都从零开始,模型只能基于你当前零散描述猜测,给出的建议经常和上一版的决策打架。这不像“多花几分钟”那么简单,而是整个协作过程缺乏上下文连续感。
1.2 claude-mem 的定位:不是聊天记录备份,而是可检索的长期记忆
我第一次看到 claude-mem 这个概念时,以为它只是把聊天记录存档,方便事后搜索,但实际思路比这有意思得多。它要做的是把每次会话里“值得记住的信息”抽取出来,转成结构化记忆存到本地,然后在后续会话开始前把相关的记忆注入给 Claude。
这里的区别很关键。聊天记录是流水账,里面大量内容是寒暄、中间状态、废稿,直接全量塞回去既不现实也没必要。而完整记忆应该是三类的结合:
- 事实型记忆:项目用了什么技术栈、数据库有哪些表、当前部署到哪个环境。
- 偏好型记忆:回答风格要求、代码规范、禁用项、喜欢先讲结论还是先讲过程。
- 状态型记忆:上一次做到哪一步、下一步计划是什么、哪个问题悬而未决。
claude-mem 类的工具本质上就是在这三类信息上做抽取、存储、检索和注入。它不替代 Claude 本身的能力,而是给模型补上“持续工作记忆”的模块,让每次新会话都像老员工回来上班,而不是新实习生第一天入职。
注意:claude-mem 这类方案社区实现不少,有的是 Python 写的,有的是 Node,也有 Go 版本。它们核心思路大同小异,差别主要在安装方式、存储格式和提示词模板。这篇文章按通用架构来讲,具体配置你用任何实现都可以对照着改。
2. claude-mem 的运行骨架:记录、抽取、存储、注入四条链路
2.1 数据从哪来:会话日志的实时捕获
要留住记忆,第一步是拿到对话原始数据。如果你用的是 Claude Code 这类终端工具,对话日志其实已经自动落盘了,通常存放在用户目录下的~/.claude相关路径里,格式是 JSONL,每一行是一条消息或一个事件。claude-mem 的第一步就是盯住这些文件。
具体做法有两种:一种是用文件系统事件监听(比如watchdog或chokidar),只要有新行写入就立即处理;另一种是简单粗暴地在会话结束后扫描整个日志目录,把新增的会话文件送去抽取。前者实时性好,会话结束几秒内记忆就进库了;后者实现简单、不容易崩,代价是记忆更新有延迟。我自己的经验是,本地开发场景用“会话结束后扫描”就够了,除非你有大量并发会话,否则没必要上实时监听。
抓日志的时候有一个细节值得注意:一定要按会话边界切分,不要把所有日志揉成一锅。因为一条记忆需要知道它的来源项目、发生时间、关联人物,这些元信息都依赖会话维度的隔离。做得讲究一点的实现,会顺带解析会话里的用户消息、助手消息、工具调用结果,分别标注来源,方便后续检索时过滤掉那些模型“自言自语”的无效内容。
2.2 记忆抽取:别用主力模型,用便宜模型做结构化
拿到原始日志之后,接下来是把流水账变成记忆。这一步听起来不难,实际上是最容易翻车的地方。
早期我试过直接把整个会话塞给 Claude 让它“总结要点”,效果其实也行,但成本扛不住。后来 claude-mem 的常见实践是专门用一个便宜快的小模型做抽取,比如 Haiku 这类轻量模型。抽取任务固定、输出格式明确,不需要太多推理能力,用主力大模型纯属浪费。
抽取环节一般分成三个动作。首先是过滤,把寒暄、试错过程、重复内容丢掉;然后是提炼,把有效信息压成几个短句,每条记忆控制在几十个字以内;最后是打标签,给每条记忆加上类型(事实/偏好/状态)、重要级别、所属项目、涉及的实体关键词。标签这东西特别重要,它直接决定后面的检索能不能命中。宁可多打几个词,也不要只写一句“用户喜欢简洁回复”。
抽取模型的提示词我也建议固定下来,写成模板放在配置文件里,别在代码里硬编码。模板里要明确告诉模型三种记忆类型的定义,并要求它输出成统一的 JSON 结构。有的实现还会让抽取模型顺手做一次“冲突检测”,如果新抽取的记忆和库里的旧记忆明显矛盾,就先把旧条目标记为待复核。
2.3 存储设计:SQLite 为主,Markdown 为辅
记忆存哪里、存成什么格式,直接决定了检索效率和导出体验。社区实现里我见到过三种主要方案,各有各的适用场景。
| 存储方式 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| SQLite 单文件库 | 查询灵活、支持结构化过滤、事务安全 | 直接读文件不方便 | 主存储,最推荐 |
| JSON / JSONL 文件 | 实现简单、人类可读 | 数据量大时检索要全量扫描 | 超轻量部署 |
| Markdown 目录 | 可读性最强、方便手动编辑 | 结构化属性缺失、检索靠 grep | 导出备份、人工审阅 |
我目前的主存储用的就是 SQLite,一张记忆表,字段大致是:id、project、content、memory_type、importance、keywords、created_at、last_accessed_at、source_session_id。这张表同时承担全量备份和检索两个职责。Markdown 则作为导出层存在,我会定期把库里记忆导出成一个MEMORY.md,方便自己刷一眼,也方便在没有工具的环境下直接把内容手动粘给 Claude。
选 SQLite 而不是纯文本文件,核心原因是检索和过滤的灵活性。你想想看,记忆库跑几个月后轻松上千条,纯文本里用关键词搜还能顶一阵,但想做“最近两周内、某个项目里、类型为偏好、重要级别高”这种组合条件查询,没有结构化字段就非常痛苦。SQLite 这条路线基本没有妥协成本。
2.4 注入时机:什么时候把记忆塞回给 Claude
记忆存进去只是第一步,关键是在合适的时机取出来用。最早的实现是简单粗暴地把最近所有记忆全部塞进 system prompt,试用两天就发现不太对:无关记忆太多,既占上下文又干扰输出。
现在主流的做法是“按需注入”。新会话启动时,claude-mem 读一下当前项目的标识,结合会话开头用户说的话,在库里做一轮检索,挑出最相关的记忆,按重要度和新鲜度排序后,把 top-N 条拼成一份上下文摘要,注入到 system prompt 或作为对话的第一条消息。
注入时间点也有讲究。我强烈建议放在 system prompt 里,而不是等用户说完第一句话再塞。因为如果先让用户说话,用户可能已经基于“Claude 不记得”的前提重新描述了一遍背景,这时候再注入记忆就会产生冲突,模型不知道该信记忆还是信用户当场的话。提前注入,Claude 会在回答时自然地把记忆和当前问题结合,体验上更顺。
注入的格式可以是这样一段结构化的文本:首先是“以下是从本地记忆中检索到的历史上下文,来自你与用户的过往对话,请把它当作事实背景来使用”,然后分项目、分类型列出记忆条目。注意不要用“用户说”这种容易混淆来源的说法,直接陈述事实即可,减少模型把记忆当成当前对话内容的概率。
3. 从零搭一套 claude-mem:安装配置与单模型设定
3.1 环境准备与安装方式
先明确一下环境要求。claude-mem 类工具大多依赖两样东西:一个能访问 Claude 日志的环境,以及一个用于抽取摘要的模型 API。日常开发用的电脑基本都能跑,不需要 GPU,因为抽取模型走的是云端 API。唯一要注意的是 Python 或 Node 版本别太老,建议 Python 3.10+ 或 Node 18+,否则依赖库可能装不上。
安装方式主要看具体实现,但大体是三种:pip install、npm install -g、直接git clone跑源码。我个人的建议是不要用全局安装,装在一个独立的虚拟环境或目录里更好,因为这类工具更新频繁,全局装容易跟其他项目冲突。很多实现会提供一个setup命令,让你引导式地填入 API Key、模型名称和存储路径,比手改配置文件省事不少。
装完之后先跑一次自检命令,比如claude-mem doctor或claude-mem check。这个命令会检查三件事:Claude 日志目录是否存在且可读、API Key 有没有配好、存储目录能不能写入。很多第一次使用时找不到记忆的问题,都是日志路径选错了,自检能帮你快速定位。
3.2 配置文件里的关键字段
不管哪个实现,配置文件里这几个字段都是核心。
# claude-mem 配置示例 memory: storage_path: ~/.claude-mem/memory.db export_format: markdown export_path: ~/.claude-mem/export extraction: model: claude-3-5-haiku-latest enabled: true interval: session_end min_message_count: 6 # 少于6条消息的会话不抽取 injection: enabled: true max_items: 8 # 单次最多注入记忆条数 max_tokens: 1200 # 注入记忆总token上限 strategy: relevance_first # 相关度优先 retrieval: method: hybrid # 混合检索:关键词+向量 time_decay_half_life_days: 30 # 记忆新鲜度半衰期 enable_rerank: true简单拆解一下。min_message_count是我后来才加上的字段,因为很多会话只有两三条消息,属于随手问答,这类内容抽取价值不大,还容易把记忆库搞脏。max_items和max_tokens共同控制注入的体量,防止记忆反客为主占满上下文。strategy我用的相关度优先,也就是检索排序后取最前面的,而不是机械地按时间取最新的。这个设置很值得细调,我在下一章专门说。
值得注意的还有project_scope这类字段,通常是个目录名或项目名列表。没有它的话,记忆库是所有项目混在一起的,写代码时抽到另一个项目的技术决策就很别扭。配置好项目维度隔离后,注入记忆的准确率会上升一大截。
3.3 引导 Claude 使用记忆的提示词模板
提示词模板是整个记忆层能不能被模型正确使用的最后一块拼图。有些 claude-mem 实现里默认模板效果平平,我改成下面这版后明显好了不少:
以下是来自本地持久化记忆的历史上下文,来自你与用户之前的会话,请把它们作为你已经知道的事实背景。 [记忆条目开始] - (项目: api-gateway, 类型: 偏好) 用户要求评审时优先检查并发安全和超时配置,不要先抠命名。 - (项目: api-gateway, 类型: 状态) 上一次会话确定采用异步队列模式改造日志上报,尚未开始实施。 - (项目: 通用, 类型: 偏好) 用户喜欢先给直接答案,再解释原理,优先避免冗长的前置说明。 [记忆条目结束] 使用原则: 1. 当记忆与当前用户描述冲突时,以当前对话为准,但主动提醒用户存在冲突并请求确认。 2. 如果记忆能帮助回答当前问题,直接使用,不要声明“根据我的记忆”之类的话。 3. 不要把所有记忆条目都复述一遍,只在回答内容需要时自然引用。我专门加了一条“冲突时以当前对话为准”的约定。为什么要写这条?因为用户在新会话里可能会修正之前的偏好或决策,如果模型死守旧记忆,反而帮倒忙。有了这一条,Claude 会主动指出差异,让用户拍板,这样记忆系统就有了纠偏能力,不会成为一潭死水。
4. 记忆检索的匹配逻辑与 token 成本控制
4.1 关键词和语义检索,两者结合才够稳
记忆库里条目过百后,检索策略就开始影响体验了。最基础的方案是关键词检索,把记忆条目的内容、标签、项目名倒排索引建起来,用户新会话里出现的高频词去匹配。这个方案的好处是零额外依赖、速度极快、行为可预期。缺点是模型表达方式多变,比如库里存的是“用户不喜欢让 AI 做前端设计”,但新会话里用户说的是“帮我切一下图”,关键词对不上,记忆就检索不到。
语义检索能缓解这种问题,它会把记忆和当前输入都转成向量,用余弦相似度找语义相近的条目。但语义检索也有自己的毛病:本地 embedding 模型效果一般,云端的要额外花费,而且偶尔会召回到语义相近但实际无关的内容,干扰反而不小。
所以我用的策略是 hybrid,也就是混合检索。先跑一轮关键词,再跑一轮语义,两边的结果合并去重,最后统一按相关度打分排序。实测下来,混合检索的命中率比单独用任何一种都高不少,尤其是用户用口语描述时,语义那一路经常能捞回关键词漏掉的记忆。如果你刚上手想省事,可以先用关键词,但记忆库超过三五百条后,建议把语义这路补上。
4.2 记忆新鲜度的衰减策略
记忆不是越久越值钱。有些偏好型记忆是长期稳定的,比如“回复风格”,半年前的和现在差别不大。但状态型记忆恰恰相反,比如“正在重构登录模块”,过两个礼拜早就完成或废弃了。如果检索时不分新旧全量展示,那些过期状态会把真正有用的近期记忆挤下去。
我给记忆加了一个时间衰减因子,核心公式是:
实际权重 = 基础重要度 * 0.5 ^ (流逝天数 / 半衰期)默认半衰期设为 30 天,意味着一条记忆 30 天后权重减半,60 天后只剩四分之一。基础重要度是抽取模型在写入时打的 1 到 5 分,比如“项目技术栈”这种长期事实我给 5,“本次会话临时结论”给 2。两个维度结合起来,排序结果就基本符合直觉:重要度高又新鲜的优先,冷门但长期有效的知识也还能露脸,过期状态型记忆则慢慢沉底。
这个衰减系数我调过好几版。半衰期太短会导致重要的长期偏好也被刷掉,太长又会造成状态型记忆泛滥。如果你的使用场景偏长期维护型项目,半衰期放 45 天;如果是快节奏的短期迭代,20 天左右更合适。
4.3 把 token 花在刀刃上:注入预算怎么设
注入记忆本质是在花上下文窗口的预算。Claude 的上下文窗口虽然越来越大,但塞进去的无关 token 会稀释注意力,影响生成质量,所以一定要设上限,而不是无脑全塞。
我的建议是给注入环节设一个 800 到 1500 token 的预算,换算成中文大概就是几百到一千字。在这个预算内,按排序分数取 top-N 条记忆,宁可少而精准,不要多而杂。一次会话注入二十条记忆,其中十五条用不上,效果一定不如只注入五条但条条都相关的。上下文里塞满陈旧背景,模型反而会犹豫该信哪条。
实现上还要对单条记忆做截断。抽取出的记忆条目最好控制在 60 个 token 以内,超过的就让抽取模型压缩成一句话。如果某条记忆实在重要且内容长,可以让它在检索结果里保持完整,但单独标记为“长记忆”,在注入时放最后面。这样预算分配更灵活,也不会因为一条长记忆把整个注入段撑爆。
4.4 记忆碎片化的合并机制
记忆库跑久了,碎片化是一个逃不掉的问题。同一个项目里,可能十几天里抽出了十五条互相补充的小记忆,每条都只讲了一点细节。检索时它们被捞出来,占了一堆 token,效果却像一篇被切碎的文档。融合机制就是为了解决这个问题的:定期把同一项目、同一主题下的相近记忆合并成一条总结,再写回记忆库。
具体做法可以很简单,每天或每周固定时段,对库里的记忆做一个聚类:先按项目和关键词分组,组内条数超过阈值且内容相似度高的,就把它们一并送到抽取模型,生成一条合并后的记忆,原条目标记为已合并并隐藏。合并触发条件要设置得保守一点,因为两条看起来相似但上下文不同的记忆,合并后反而可能丢信息。我的标准是:同组超过 5 条、且两两相似度大于设定值,才触发合并。
八卦一句,早期我没加合并机制,跑一个月后记忆库小两千条,检索结果全是零碎信息。加了合并之后,库里长期活跃条目稳定在两三百条左右,检索质量彻底稳定下来。这一步几乎不需要额外成本,建议一定要做。
5. 实跑一个月后:值得注意的坑和边界场景
5.1 过期记忆的干扰:比没记忆更可怕
跑 claude-mem 第一个星期,我遇到一个很典型的翻车场景。一个项目里旧记忆写着“数据库暂用 SQLite,后续评估是否迁移 PostgreSQL”,两周后项目已经确定迁移了,并且新会话里用户也明确说了“现在在 PostgreSQL 上”。但记忆库里的旧条目没有自动失效,检索时又被捞了出来,Claude 在回答里把 SQLite 和 PostgreSQL 都当作有效背景,产出了一篇两头矛盾的建议。
这就是我在注入模板里加“冲突时以当前对话为准”的原因,但这也只能救当前会话。长期看,记忆库必须有过期标识机制:
- 状态型记忆要设置有效期,到时间后自动转成“待确认”状态,不再参与注入。
- 每次新会话结束,可以让抽取模型顺便比对当前对话与命中记忆,遇到明显冲突就在旧条目上打上“可能已过时”。
- 保留一个
review命令,定期导出记忆库,人肉扫一遍,把失效条目批量标记。
这是所有记忆类工具绕不开的功课:记忆的价值建立在准确性的基础上,而准确性需要持续维护,不能写完就放任不管。
5.2 隐私与数据边界:记忆库是敏感数据仓库
记忆库里存的内容经常比聊天记录更敏感,因为它经过提炼,全是“用户在意什么、项目用什么架构、下一步要做什么”这类高价值信息。本地存储只是第一道防线,真正要做好边界,还有几个点:
- 存储目录权限收紧,不要随便给 777 权限。SQLite 文件最好只有当前用户可读写。
- 不要用同步盘默认同步整个记忆目录,如果要跨设备同步,至少做一层加密,或者干脆把记忆库排除在同步范围之外。
- 抽取模型和主力模型使用的 API Key 分开配置,避免因为一个工具泄露而影响全局。
- 涉及客户名、密钥、内部事故等内容的会话,要么在配置层面直接跳过不抽取,要么在抽取提示词里明确要求脱敏。
这一点说出来可能有点危言耸听,但记忆库一旦维护了几个月,它就变成了比你聊天记录更“懂你”的文件。安全方面的动作做多不做少。
5.3 哪些场景确实不适合 claude-mem
不是所有使用 Cluade 的场景都适合套一层记忆层。我自己踩过之后总结出几类不太划算的情况。
一次性任务型对话,比如“帮我解释这段代码”“写个一次性脚本”,硬要上记忆反而是负担,抽取浪费成本,注入又占了 token。这类场景记忆库长期攒的是低价值碎片,拉低整体检索质量。短平快问答工具场景里,关掉记忆注入或者干脆不用 claude-mem,体验反而干净。
还有探索性头脑风暴场景。这类对话的特点是思路跳跃、伪命题多,今天说“要不要上微服务”,明天又觉得“单体就行”。如果把这类中间想法都记进库,记忆库很快就会变得自相矛盾。我现在的处理是给会话打标记:brainstorm类型的会话单独建一个命名空间,不参与默认注入,除非明确在检索条件里带上这个标记。
成本方面也得掂量。抽取每一条记忆都需要一次模型调用,哪怕用的是便宜模型,上千次调用累计起来也是肉眼可见的费用。用量大的人可以给抽取模型设一个每日预算上限,超过后当天只记录原始日志,不抽取新记忆,第二天再补。虽然会损失一点实时性,但成本曲线会平滑很多。
5.4 多项目隔离与记忆迁移的进阶玩法
最后聊聊我目前还在折腾的两个方向。第一个是多项目隔离。早期记忆库只有一个命名空间,所有项目混在一起,检索时靠项目名字段过滤,效果差强人意。后来我改成物理隔离,每个项目一个 SQLite 文件或一个独立 schema,切换项目时只加载对应项目下的记忆。这个改动之后,注入准确率的提升非常明显,因为彻底消除了跨项目干扰。
第二个是记忆迁移。很多时候,某个项目的记忆对另一个项目同样有参考价值,比如“用户写代码规范化要求”这类偏好其实是通用的。我在记忆表里加了一个scope字段,取值可以是global或某个项目名。全局记忆在所有会话里都能注入,项目记忆只在该项目内生效。这样既保留了跨项目复用的价值,又避免了全量混用的混乱。
通用偏好这块,它的厉害之处在于一旦整理好,几乎不需要随项目变化而重写。我现在新项目初始化时,会自动把库里scope=global的记忆复制一份到项目记忆库中,相当于给每个新项目先配好一套“用户使用说明书”。这个动作看着不起眼,实际上大大降低了新项目启动时的沟通成本,是我用了 claude-mem 之后最满意的一个习惯。
最后再补一个已经固定的使用习惯:我会每周跑一次claude-mem review,把当周新写入的记忆导出成 Markdown 快速扫一眼,顺手淘汰一批已经失效的条目。别小看这个动作,它花不了五分钟,但能让记忆库长期保持在干净状态。Claude 记住的东西越规整,它作为“长期员工”的靠谱程度才越高,claude-mem 这层记忆才能真正从玩具变成生产力工具。