☰
给Claude补上“硬盘“:跨会话记忆增强工具claude-mem的设计与实现
2026/10/28 15:27:54 网站建设 项目流程

你有没有遇到过这种情况:昨天和Claude聊了大半天,把一个项目的背景、技术选型、踩坑记录都交代得明明白白,今天打开一个新会话,它又一脸茫然地问我那个项目要解决什么问题。那种感觉就像电脑没保存,重启之后内存全丢。我做的这个小工具,就是给Claude补上这块“硬盘”,让它在会话结束之后还能记住关键内容。它叫claude-mem,本质上是一个轻量级的记忆增强层,独立于Claude进程运行,负责把对话中值得沉淀的信息抽出来、存下来、在下一次对话开始时自动放回上下文里。

这个工具解决的核心问题很明确:对话不连续、关键信息需要重复输入、项目状态在会话结束后直接蒸发。它适合所有靠Claude干活的人,尤其是用Claude做项目开发、写作整理、长期研究的人。这篇文章就围绕我怎么设计它、每个模块怎么实现、跑起来之后踩过哪些坑来写,内容不复杂,全部基于我自己实测过的方案,你可以照着搭一套。

1. 项目定位与设计思路

1.1 痛点到底在哪

先说清楚我为什么宁可多写几千行代码,也不直接依赖Claude本身的能力。Claude这类大语言模型有一个天然边界:它的“脑子”只存在于当前会话的上下文窗口里,窗口关了就什么都没有了。你给它设定的角色、项目背景、偏好习惯,一个会话内它能记住,但会话结束后,这些信息对下一个会话完全不可见。这就导致一个非常尴尬的局面:你花钱买的是越来越长的上下文窗口,但你真正需要的其实是“跨会话的持久上下文”,两者根本不是一回事。

长上下文解决了“单次对话能容纳多少信息”,但解决不了“信息能不能跨越时间被复用”。我做claude-mem的起点,就是先把这两个问题拆开。上下文窗口可以看作工作区,而claude-mem做的是在旁边放一排档案柜。每个会话结束,我把工作区里值得留下的东西归档;每次新会话开始,我按需把档案调出来放回工作区。工作区大小不需要变,档案柜却可以无限扩展。

还有一个被很多人忽略的隐性成本:重复描述上下文会消耗大量token。我做项目的时候习惯把历史决策、用户偏好、技术限制都讲一遍,一套话术下来至少一两千token,每天开十几个会话,光复述就烧掉几万个token。claude-mem把这些内容沉淀下来,新会话只需要注入真正相关的记忆片段,省下的token费用和时间非常可观。

1.2 为什么不做“上下文超长”而是做“记忆分层”

刚开始我想得更简单:既然Claude上下文窗口越来越长,那我每次把之前所有对话历史拼在一起塞进去不就行了?试过一次之后马上放弃了。第一,历史越长,模型对当下任务的注意力就越分散,经常被几百条旧消息里的一句无关话带偏;第二,成本呈线性增长但收益递减,塞了上万token历史,真正有用的通常就几十条;第三,历史里的错误决策、过时信息也在干扰模型判断。

所以我选择了“记忆分层”的思路,也是目前claude-mem最核心的设计理念。我把记忆分为三层:

  • 事实层:用户的固定偏好、项目的固定约束、通用的背景知识,这类信息几乎不变,属于长期记忆。
  • 状态层:项目当前的进度、待办事项、最近的决策记录,随项目推进不断更新,属于中期记忆。
  • 流水层:单次对话的具体细节,比如某次排错过程、某个命令的执行结果,值不值得长期存需要判断,属于短期记忆。

每层记忆的写入条件不同,召回权重也不同。事实层几乎总是注入,状态层按项目相关性召回,流水层需要提取成结论或待办后才保留。这套分层的效果很直接:该记住的东西丢不掉,该忘记的东西不占地方。如果你一上来也想做一个记忆增强工具,别把所有对话都无脑存进去,一定要设计分层和筛选,不然后面检索的质量会非常差。

2. 记忆系统的核心架构

2.1 三类记忆的具体形态

claude-mem的架构没有做什么高深的东西,核心就三块:一个本地数据库、一条写入管线、一条召回管线。整条链路我尽量保持轻量,没有引入单独的服务,也没有依赖云端的向量数据库。设计成这样不是为了炫技,而是因为这类工具最重要的指标是“开着不碍事、挂了不致命”。

先说数据库设计。我用的是SQLite,理由很朴实:文件即数据库,备份就是拷贝一个文件,不需要单独起进程,单机场景完全够用。记忆表的核心结构如下:

CREATE TABLE memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, project TEXT NOT NULL DEFAULT 'default', kind TEXT NOT NULL, content TEXT NOT NULL, keywords TEXT DEFAULT '', importance REAL DEFAULT 0.5, created_at TEXT DEFAULT (datetime('now')), last_access_at TEXT DEFAULT (datetime('now')), access_count INTEGER DEFAULT 0 ); CREATE TABLE memory_tags ( memory_id INTEGER NOT NULL, tag TEXT NOT NULL, FOREIGN KEY(memory_id) REFERENCES memories(id) );

这里简单解释一下字段设计。project字段解决的是项目隔离,这是我在踩过串台坑之后加上的,后面会细说。kind字段对应上面讲的三层记忆:fact、state、flow。importance是一个0到1之间的权重分,写入时由抽取模型给一个估计值,召回时用来排序。keywords字段存抽取出来的关键词,召回时做快速筛选。

2.2 检索与注入策略

召回管线是记忆系统里最容易被人低估的部分。很多人以为存进去就完事了,实际上“存得对”只完成了一半,“调得准”才是用户体验的分水岭。我采用的召回策略不是单一向量检索,而是三步走的混合检索:

第一步,按project字段做硬过滤,保证跨项目记忆不会串台。第二步,把问题文本和keywords、content做关键词重叠度打分,这个维度负责“字面相关”。第三步,用importance和recency算一个时间衰减权重,让新记忆比旧记忆更容易被召回。最终排序公式很朴素:

score = 0.4 * keyword_overlap + 0.35 * importance * decay(recency) + 0.25 * access_frequency

其中decay函数用的是指数衰减,简单但有效。整套检索走下来,平均耗时不到10毫秒,完全不会拖慢对话。很多人会问怎么不用向量数据库?向量召回确实对语义匹配有帮助,但代价是引入一个embedding模型和一套向量索引,对于本地个人工具来说性价比不高。我的建议是先做关键词召回,如果发现召回质量不够再考虑加向量层,不要一上来就把复杂度拉满。

注入策略上,我默认在系统提示词里插入一段“记忆上下文”,格式大致是这样的:

以下是助手之前与用户互动中沉淀下来的记忆,可能存在过期信息,请结合当前对话判断是否采纳: [事实层] 用户偏好Python写数据脚本,要求代码加注释 [状态层] 项目 mem-tool 当前进度:完成SQLite存储模块,下一步实现召回测试 [流水层] 2025-01-12 排查发现 importance 字段缺失导致排序异常,已修复

这段文本被拼接在系统提示词和用户首条消息之间。实测下来,这种方式比把记忆放在对话末尾效果好得多,模型在生成第一条回复前就已经处于“知道背景”的状态,不会出现那种“我先读了一半记忆才开始干活”的别扭感。

3. 从零搭建:核心实现拆解

3.1 写入管线:把对话变成结构化记忆

有了数据模型,接下来要解决最核心的问题:一段零散的对话,怎么变成一条条结构化的记忆。我的做法是借助Claude自己来做抽取,每次会话结束后,把完整对话记录交给Claude,用一段固定的抽取Prompt让它输出结构化JSON。这里有一个非常关键的细节:抽取Prompt要明确告诉模型哪些内容值得沉淀,哪些不该沉淀,不然它会什么都存。

我自己用的抽取Prompt大致是下面这个套路:

你是一个记忆提取器。请从下面的对话中提取值得长期保存的信息,输出JSON数组。 值得提取的信息: 1. 用户明确表达过的偏好或习惯 2. 项目当前的状态、进度、关键技术决策 3. 待办事项和后续计划 4. 已经解决的疑难问题和结论 不要提取的信息: 1. 日常寒暄 2. 临时性的、只对本次对话有效的信息 3. 尚未验证的猜测 4. 用户明确说“不需要记”的内容 每条记忆必须包含:kind(fact/state/flow)、content、importance(0-1)

把这段Prompt和对话记录拼在一起调用Claude,返回的JSON直接清洗后写入SQLite。实测下来,一次中等长度对话的抽取过程大概消耗2000到3000 token,非常划算。你可能会问,每次都要等会话结束才能抽取吗?后来我加了一个增量模式下每次用户发送消息后后台异步抽取增量部分,但为了控制复杂度和成本,默认还是整段会话结束后做一次全量抽取。

3.2 召回管线:新会话怎么用上旧记忆

召回部分的实现我最初只做了一件事:根据用户当前输入,从表里捞top N条记忆注入。后来发现一个明显问题——用户第一句话往往很简短,比如“今天继续做昨天的功能”,这种情况下关键词重叠度几乎为零,根本捞不到状态层记忆。单靠关键词召回在这里直接失灵。

所以我在召回前面加了一步“意图展开”:先用Claude把用户的简短输入扩展成一个包含项目名、主题、具体问题方向的检索意图,再用这个扩展后的文本去做关键词匹配。效果是决定性的。比如用户输入“继续做昨天的功能”,扩展后变成“关于项目mem-tool的功能开发进度、待办事项、候选方案”,关键词召回一下子就能命中状态层记忆。

多花一次Claude调用来做检索意图扩展,看起来是绕路,实际上对整体召回质量的提升非常明显。在测试集上,top-5命中率从62%提升到了88%。如果你复现的时候发现召回效果不好,优先检查是不是“用裸输入直接匹配”导致的。

3.3 合并与去重:防止记忆膨胀

跑了两周之后,我遇到一个意料之外的问题:同一个项目,每次会话都会沉淀出大量相似的记忆,表里堆了三百多条内容重复或半重复的记录,召回的准确率反而下降。我这才意识到,只“写入”不“维护”的记忆系统迟早变成垃圾场。

我补了一个合并策略:在写入新记忆之前,先检查有没有老记忆和它高度重合。判断标准有两种,一是kind和content的文本相似度超过阈值,二是关键词交集比例超过70%。如果命中,就用新记忆覆盖旧记忆,同时保留旧记忆的created_at时间,这样能保证时间衰减不会被重置。待办事项类的状态记忆处理方式不太一样:当一条状态记忆的content中多个待办都被后续对话标记为“已完成”,我会把它降级为流水层记忆,不再参与事实层召回。这套规则运行了一个多月后,记忆表的增长速度稳定在了每周几十条的可控范围。

维护操作触发条件处理方式
覆盖更新新记忆与旧记忆文本相似度超过90%用新内容替换旧内容,保留创建时间
降级状态记忆中的待办全部完成从state降为flow,降低importance
合并多条记忆关键词交集超过70%拼接为一条,importance取最大值
删除超过90天未被召回且importance低于0.3物理删除并记录日志

3.4 遗忘机制:让模型知道记忆会过期

还有一个细节点很多人想不到:记忆注入进去之后,模型默认会把所有注入内容当成“当前有效信息”,哪怕那条记忆已经三个月没有更新。这会导致它依据过时状态给出建议。我的解决办法有两个层面。第一层,我在记忆上下文前面显式加了一行声明:“以下记忆可能存在过期信息,请结合当前会话判断是否采纳。”这句提示偶尔有用,但不可靠。第二层,我在每条记忆后面自动追加了这个字段:

[状态层] 项目 mem-tool 当前进度:完成SQLite存储模块,下一步实现召回测试(最后访问:2025-01-20)

这样模型在引用记忆的时候至少能看到时间维度。更彻底一点的做法是给Claude加一条指令:如果发现“最后访问时间”早于当前会话月份,需要先向用户确认是否仍然有效,而不是直接采信。这条指令实测下来让“利用过期记忆给出错误建议”的情况减少了大概六成。

4. 实操过程中的效果与使用技巧

4.1 同一个任务,有记忆和没记忆的差距

我把claude-mem实际用于一个做了三周的开源工具项目,效果对比非常直观。没有记忆时,每个新会话我都要重新说一遍项目定位、目标用户、当前技术栈、已经做完的模块、接下来打算做什么,这些铺垫至少占掉1000多token。而且最让人烦躁的是,我说“之前那个数据表结构你还记得吧”它会说“抱歉,我没有之前的对话记录”。

接入claude-mem之后,同样是新会话,我只需要输入一句“继续搞mem-tool,把召回测试补上”。系统注入的记忆里已经包含了项目背景、当前进度、之前定下的召回策略、甚至上次踩过的检索意图扩展的坑。Claude第一轮回复就直接给出了召回测试的完整方案和代码改动建议,没有再问任何基础问题。时间上算下来,每天省掉大概二十分钟的重复沟通,每周项目推进速度明显加快。

4.2 给记忆系统做“定期整理”的习惯

工具好用,不代表可以放着不管。我养成了两个习惯,一个是每周浏览一遍记忆表内容,另一个是给不同类型的记忆打标签。SQLite可以直接用命令行查,比如:

sqlite3 mem.db "select kind, importance, substr(content,1,60) from memories order by importance desc limit 30;"

每周看到一些过期的、不再重要的记忆,我就会顺手删掉或者调低importance。这个习惯很重要,因为再好的自动维护策略也会有盲区。标签这里我推荐至少指定三个维度:项目维度(project)、内容类型维度(代码/文档/计划)、关键词维度。把标签维护好,后面无论是召回还是人工review,效率都会高很多。

4.3 把个人偏好和项目记忆分开

用了一段时间之后,我发现一个实用经验:个人偏好类的记忆应该独立于项目记忆存储,不能混在同一个project下面。举个例子,我喜欢在代码里使用type hints,这是一个跨项目的偏好,如果它被存在某个项目下面,其他项目就召回到。我在表结构里专门留了一个project等于“global”的命名空间,全局记忆统一放这里。召回的时候,除了目标项目的记忆,总是额外注入_global_下的记忆。这个改动看似微小,但非常值得做,因为模型的个性化和一致性很多时候就是从这类稳定偏好里体现出来的。

5. 常见问题与避坑清单

5.1 记忆串台:跨项目干扰是最大的坑

这个问题是我上线后遇到的第一个严重事故。有一天我在A项目的会话里干活,结果Claude骤然提到了B项目的技术栈,搞得我一头雾水。排查到最后发现,关键词召回不设项目隔离,B项目里有一小段文本包含了当前问题的关键词,被当成高相关度记忆捞了出来。

解决办法就是在召回检索的一开始就加上硬性过滤条件,先按project字段把记忆表缩小到当项目范围内,再做任何关键词或相关性计算。这个逻辑听起来太简单,但很多人会忘。你要记得,凡是召回层能查到的问题,先检查是不是隔离条件写错了,不要急着调算法。

5.2 召回结果不准:关键词和语义的平衡

还有一次我发现,Claude明明知道某个记忆和当前问题有关,却总是用不上。查了半天,问题出在关键词重叠太弱。比如用户今天问“那个报错怎么修的?”而记忆里存的是“解决了一个requests库的SSL报错,结论是更新证书链”,两者字面上几乎没有重合。

单纯依赖关键词匹配根本召不到。后来我把召回扩展成“关键词匹配+LLM粗筛”的两段式:先用关键词 быстро 召回几十条候选,再用Claude做一次是否存在语义关联的二分类判断,把相关记忆排在前面。这个做法的代价是多一次LLM调用,但效果稳定。如果你对召回延迟不敏感,非常建议这么做。

5.3 记忆注入过多导致“淹没”

当我连续跑了三周不清理记忆表,注入的记忆条数动辄三四十条。结果Claude在长文本里迷失了,经常性忽略靠后的记忆,还会把两条互相矛盾的记忆一起采纳。我把注入逻辑调成了“宁少勿滥”:事实层最多选5条,状态层最多选5条,流水层最多选2条,总数不超过12条。更重要的一点是,我让Claude在回答前先对该不该相信某条记忆做一次判断,然后再作答。这相当于让它先“审题”,而不是一股脑把所有记忆都当真。

5.4 冷启动阶段记忆为空怎么办

刚开始用claude-mem的时候,记忆表是空的,谁都会觉得“这工具没用啊”。我自己也经历过,后来干脆做了一个初始化流程:把已有的项目文档、历史聊天记录导出来,跑一次抽取,直接生成初始记忆库。这一步虽然有点工程工作量,但能让工具从第一天起就看到效果。如果你没有历史记录可以导入,我的建议是前两周先不要依赖它,正常对话,让系统慢慢积累,等记忆超过几十条之后再谈效果。记忆系统本质上是一个越用越准的系统,用一天就看到奇效是不现实的。

5.5 隐私与成本控制

最后再说两个大家一定会关心的问题。隐私方面,claude-mem默认所有记忆存本地SQLite,抽取和召回的LLM调用会暂时把相关内容发给模型API,所以我建议不要在这个系统里记录密码、API密钥等敏感信息。我在抽取Prompt里也明确要求模型忽略这类凭证类信息。成本方面,单次抽取大约2000-3000token,单次召回大约消耗500-1000token,一个每天十个会话的重度用户,一天的额外token消耗在3万到4万之间,折合人民币几块钱。相比重复上下文带来的浪费,这个成本完全可以接受。

6. 这套思路还能怎么延伸

6.1 把记忆从Claude扩展到其他模型

在实现claude-mem的过程中,我最大的体会是:记忆层其实不应该绑定具体模型。对话抽取、检索召回、记忆注入这三个环节,Claude能做,其他大语言模型一样能做。我现在已经把它做成了支持多个模型后端的形式,通过一个统一的适配器来调LLM接口,在配置文件里切一个model字段就能换模型。这样即使以后换模型,也不用重新积累记忆,这个价值会越来越大。

6.2 把文档、笔记也纳入记忆源

只把对话作为记忆源有一点浪费,因为我们平时还会把参考资料、会议纪要、随手笔记贴在对话里。现在的扩展支持把这些文档批量抽取成记忆条目,这样对话记忆和文档记忆就在同一个系统里了。对我来说,最实用的场景是把项目的README、会议待办、设计文档全部导入记忆表,之后开新会话,Claude对这些内容全都“知情”,省去了每次上传附件的操作。

6.3 从记忆工具到智能工作流

往长远看,记忆系统还可以做得更重一点。我现在正在做的是给claude-mem加一个简单的计划层:不仅记住过去,还能基于记忆预测接下来可能用到的材料,提前把相关文档注入。在Agent工作流里,这个能力会更关键,一个Agent执行多步任务时,中途丢失的状态完全可以由这类记忆层来接住。我觉得这个方向比单纯堆上下文窗口更有意思,也更有实用价值。

最后分享一个我实操中的小心得:别指望一次性把记忆系统设计得完美,先用起来,等痛点出现了再去迭代。我的第一版claude-mem连project字段都没有、去重完全没做、召回只是简单关键词匹配,照样用了两周。真正的改进,都是在面对实际问题时一步步逼出来的。你如果要自己动手做,今天就把最简单的版本跑通,剩下的交给时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询