☰
AI编程助手健忘症自救:用claude-mem搭建跨会话长期记忆系统
2026/10/12 8:39:11 网站建设 项目流程

用了几个月AI编程助手之后,我发现自己陷入了一个循环:每次开新会话,都要把项目背景、技术栈、当前卡点重新交代一遍。明明昨天刚跟会话聊过的方案,今天新开一个窗口,它就完全不记得了,我又得从头解释。后来我认真折腾了一个叫claude-mem的思路——本质上是给AI助手装一套"长期记忆系统",把跨会话的关键信息存下来、整理好,在下一次对话开始的时候自动喂回去。这套东西我自己实践下来效果非常明显,也从最开始只会手动拷贝粘贴,逐渐演变成了一套半自动的管线。这篇就完整记录一下我的折腾过程,包含架构选型、实现细节、踩坑实录,如果你也在被AI助手的健忘症困扰,这套思路可以直接落地。

先说清楚这是什么东西。claude-mem不是某个神秘组件,它是一类"会话记忆增强工具"的统称和参考实现,核心目标就是解决会话上下文的断裂问题。它能自动保存每次对话的关键内容,定期生成摘要,并在下次启动会话时把相关的历史记忆注入到上下文中。对于重度使用AI编程助手的开发者、写技术文档的博主、以及任何需要和AI进行长期多轮协作的人来说,这东西能省掉大量重复沟通成本。

1. 为什么AI助手需要一个"外部记忆"

1.1 原生会话窗口的天然缺陷

不管是哪家大模型驱动的编程助手,本质上都是一个"无状态"的对话系统。它看到的是你当前发给它的上下文窗口,窗口之外的内容一概不知。这个设计本身没有错,属于大模型推理机制决定的——每次推理都是独立的前向计算,没法像人一样在脑子里长期驻留一份不断更新的记忆。

但问题在于,实际工作流是连续的。你可能这周都在改一个模块的重构,今天让助手生成了某个工具函数,明天又要基于它继续做测试。如果每天都开新会话,助手就完全不知道昨天已经写好了哪些东西,你只能把代码片段、设计方案重新贴一遍。更崩溃的是,贴进去之后还会遇到上下文窗口被占满的问题,聊不了多久就提示Token超限。

我试过几种常规解法,效果都不理想:

  • 手动维护一个项目文档,每次开新会话把文档贴进去。这个方法最原始,但维护成本高,写着写着就忘了更新。
  • 直接不关会话,让对话一直挂着。这个方法看着省事,但上下文越长,模型响应越慢,费用越高,而且一旦超过窗口限制,前面的内容会被强行截断。
  • 使用模型自带的超长上下文,把整个历史对话一股脑塞进去。这个只有表面可行,实际用起来大量Token都浪费在无关内容上,模型抓不住重点。

真正好用的做法,是像人一样做"笔记"——对话发生的当下就把关键信息提炼出来,按主题归档,需要的时候再定向调取。这就是记忆增强工具的出发点。

1.2 claude-mem解决的问题范围

claude-mem解决的,是三个层面的问题:

第一层是跨会话连续性。它把每次会话的产出保存到一个持久化存储中,下次对话时可以通过指令或自动机制调出相关信息。这一层解决的是"忘记"的问题。

第二层是信息密度控制。原始会话可能有几千行内容,但其中有价值的往往只是几十行。记忆系统要做的是把高密度信息从低密度对话中分离出来,避免下次注入时浪费宝贵的上下文空间。这一层解决的是"太笨重"的问题。

第三层是主动检索能力。当你在新会话中提问时,记忆系统能根据当前问题自动定位相关历史片段,而不是把所有历史都砸过去。这一层解决的是"找不到"的问题。

我记得第一次完整跑通这套流程时的感受:新会话里我说了一句"继续昨天的重构,记得对接口做兼容",它直接把昨天的设计决策、已改文件列表、待办警告都带了出来,那个体验确实让人一激灵。从此我就再没回到过手动复制粘贴的老路上。

2. 整体架构与关键选型

2.1 记忆系统的整体工作流

我给这套系统设计了四个阶段,模型参考了人类记笔记到复习的完整链路:

  1. 捕获:监听每次会话过程中产生的文本流,完整落盘。
  2. 提炼:会话结束后(或者达到一定体量时),调用模型对原始内容做摘要,提取决策、代码产出、注意事项。
  3. 归档:把摘要写入结构化存储,同时保留原始内容和检索索引。
  4. 注入:新会话启动时,读取相关的历史记忆,以系统提示词或上下文片段的形式注入。

这四步看起来简单,但每一步都有很多实现取舍。比如捕获是整段抓还是按事件抓,提炼是一次完成还是递归多轮,归档是存纯文本还是数据库,注入是全量还是检索——这些选择直接影响最终的使用体验。

2.2 捕获方式的方案对比

捕获环节是整个系统的入口,如果这一步漏数据,后面全白搭。我对比过三种常见的捕获方案:

方案一:手动导出。让AI助手自己把当前会话导出成文件,然后手动移动到这个工具指定的目录。这个方案实现最简单,稳定可靠,缺点是每次都要多一步操作,容易忘。比较适合使用频率不高的人。

方案二:协议层拦截。如果编程助手本身提供事件钩子(hook),可以在会话事件发生时自动触发捕获动作。比如会话开始、每次消息完成、会话结束,分别调用接口把内容追加到存档。这个方案的体验最顺滑,是和工具配合最好的方式。

方案三:终端包装器。用脚本包一层命令,在命令执行时用管道同时抓取stdout和stderr。好处是和具体工具解耦,任何对话式工具都能接;坏处是输出流里会混入大量非对话内容,比如命令回显、进度条之类的,需要额外做过滤。

我自己用的方案二是主,方案三做兜底。日常主要依赖事件钩子自动捕获,偶尔遇到钩子不生效的情况用手动导出弥补。

捕获频率和粒度的控制也很重要。最开始我贪多,每轮都记录完整对话,结果存档文件膨胀得很快。后来调整策略:按一次完整的问答块为单位切分,单条消息不落盘,攒够一个交互回合再写入。这样既保证了完整性,又控制了文件数量。

2.3 存储格式:纯文件还是数据库

存储层的选择,直接影响后续检索写的代码量。我最初用的是纯文本文件加目录结构,按日期和会话ID命名:

memory/ 2025-06-01/ session_abc123.md session_def456.md 2025-06-02/ session_ghi789.md

好处是直观、可读、方便手动检查,坏处是检索基本只能靠文件名加代码里的字符串匹配,一旦记忆量大了,搜索效率低得让人心烦。

后来我迁移到了SQLite,专门用了一张表存会话元数据,一张表存记忆片段,还开了全文搜索。对于一个单机使用的工具来说,SQLite是最合适的——不需要单独跑服务,文件即数据库,备份拷贝非常方便。这一步迁移我做了很久,回归测试时还发现几个旧数据字段没映射对,但迁移完之后的体验提升是质的。

这里有个经验:如果你只是想快速验证这套玩法,直接用JSONL文件就行,每行一条JSON记录,写入追加、读取遍历,几百条记忆的时候性能完全够。等确定要长期用了,再迁到SQLite不迟。没必要一开始就上复杂方案。

2.4 检索方案:关键词匹配还是向量检索

检索是决定"记忆注入质量"的关键。有两种主流方向:关键词匹配和向量语义检索。

关键词匹配,具体说就是用SQLite的FTS5做全文搜索,把记忆片段分词后建立倒排索引。查询时直接跑SQL,按匹配度排序返回。优点是实现简单、速度秒回、结果完全可控;缺点是得保证查询词和记忆文本有字面上的重叠,如果说法不一样就会漏检。

向量语义检索,是用嵌入模型把文本转成向量,然后用距离计算找语义相近的内容。优点是能处理"换个说法"的情况,比如你昨天说的是"修复了数组越界崩溃",今天问"为什么程序之前会闪退",语义检索引擎也能关联上;缺点是引入了额外模型依赖,增加了系统复杂度,而且小规模数据下优势并不明显。

我实际的做法是两级检索:第一级用全文搜索,锁定候选集;如果候选集太小或者匹配度太低,再启动向量检索作为兜底。日常大多数情况下,全文搜索就够了。只有当记忆库超过几千条、或者我确实经常换说法提问时,向量检索的价值才会放大。

选型层面还有一个容易被忽视的点:中文支持。SQLite默认的分词对中文很不友好,英文按空格分词没问题,中文就连成一整块,基本搜不到。我用的是预处理方案:写入时先用分词工具把中文切成词序列,以空格拼接后再存进FTS索引。这样既保留了全文搜索的高效,又解决了中文分词问题。

3. 核心实现细节与实操过程

3.1 会话捕获与归档的完整实现

先说捕获这部分的落地。我使用事件钩子机制,配置非常简单。先创建一个配置目录,在里面放一个配置文件,内容大概是这样的:

{ "memory_dir": "~/.claude-mem/store", "capture": { "enabled": true, "hook_mode": "event", "min_block_size": 1 }, "summary": { "enabled": true, "interval_messages": 50, "model": "当前使用的模型", "max_summary_length": 2000 }, "retrieval": { "fts_enabled": true, "vector_fallback": false, "max_results": 5, "relevance_threshold": 0.6 } }

配置好之后,每次会话开始时,钩子脚本会生成一个唯一的会话ID,格式是YYYYMMDD_HHMMSS_随机后缀,同时创建对应的存档文件。

当每次交互块完成时,钩子捕获文本流,追加写入存档文件。写入的格式我做了约定:

## 用户提问 [内容] ## 助手回复 [内容] ## 元信息 时间: 2025-06-01T10:23:11+08:00 工作目录: /path/to/project 相关文件: src/utils/parser.rs

这个"相关文件"字段很有用——如果助手回复里涉及了代码文件,钩子会解析回复中的文件路径并记录到元信息里。后期检索时,我可以直接在文件路径和记忆片段之间建立关联,查"parser.rs"就能找到所有和这个文件相关的历史对话。

会话结束的标志是进程退出或者超时。结束时钩子会做三件事:补写最后的未刷新内容、把会话标记为completed、触发一次摘要生成。如果是异常退出,我会标记为interrupted,这类会话的摘要质量通常不高,后期检索时会降低权重。

3.2 自动摘要生成的两级策略

摘要这一步是整个系统里最需要调参的地方。最开始我图省事,每个会话结束以后做一次完整摘要,后来发现两个问题:一是长会话摘要时容易丢细节,二是摘要质量不稳定。

后来我改成两级摘要策略。第一级叫"增量摘要",发生在会话进行中——每当交互块数量达到50个,就对最近这50个块做一次局部摘要,生成一个中间文件,同时把这些块的原始内容标记为"已摘要",后续如果会话还在继续,新的内容会另开新块累积。第二级叫"周期聚合摘要",发生在每天结束时或者活动静默超过12小时后,把所有增量摘要汇总成一份当天的整体摘要。

这个设计的思路是:摘要本身也是一个有损压缩过程,一次性压得太多,丢失率高;分而治之,每次压缩的损失都小,最后汇总时质量更有保障。用大白话说就是:先做流水账笔记,再定期整理成周报。

摘要生成的提示词模板也很关键。我踩过好几次坑——早期模板太开放,让模型"总结一下这个会话",结果它开始写散文,全是"用户提出了一系列问题,助手给出了详细解答"这种废话。后来我把模板改成强结构化输出:

请基于以下对话片段,提取事实性信息。要求: 1. 只记录用户和助手明确表达的内容,不要推测动机。 2. 输出格式为列表,每条以[类型]开头。 3. 类型包括:决策、实现方案、代码产出、依赖关系、错误修复、待办事项、用户偏好。 4. 不要输出"用户说/助手说"这类转述,直接写事实。 5. 如果某条信息与更早的记忆重复,标注[重复]并简述关联。 对话片段: [对话内容]

实测这个模板的产出质量高很多,每条摘要都是可直接作为上下文再次使用的原子信息。比如"决策:确定使用SQLite替代JSONL存储,理由是需要支持全文检索",再比如"错误修复:解析器无法处理UTF-8 BOM,已在打开文件时跳过前三个字节"。这类信息在后续会话中直接复用价值极高。

3.3 记忆注入与查询的实际用法

记忆注入的时机,一般在新会话初始化阶段。在会话启动钩子中,读取当天的聚合摘要和由检索模块提供的相关历史片段,拼成一段"记忆上下文",插入到系统提示词的末尾。

我给注入设置了两种模式:

  • 主动模式:新会话启动时,自动注入最近三天的聚合摘要,让模型对新项目状态有基本的背景认知。
  • 被动模式:正常对话过程中不做注入,只有当你输入特定指令(比如#mem 继续昨天的接口兼容改造)时,系统才按指令中的关键词去检索历史记忆并注入。

实际使用中,被动模式更常用。原因是主动模式容易注入太多无关内容,干扰模型的注意力。刚开始我把所有历史都塞进上下文,结果模型反而忽略了一些关键约束,有种"信息过载"的感觉。后来改成默认只注入概要,详细记忆按需调取,效果明显好转。

查询指令的解析逻辑也不复杂。识别到记忆指令后,提取指令中的关键词,去掉停用词,拿剩余关键词去FTS5全文索引里搜索。SQL大概是这样的:

SELECT fragment_id, content, datetime, relevance FROM memory_fragments WHERE content MATCH ? ORDER BY rank LIMIT 5;

匹配结果按相关性排序返回,再注入到上下文。这里有个细节:注入的每条记忆前会加一行引用标注,比如"以下内容来自历史会话(时间:2025-06-01):",这样模型能区分当前对话和历史记忆,避免时间线混乱。

3.4 维护脚本和自动化配置

整套系统离不开几个辅助脚本。我的目录是这样的:

~/.claude-mem/ bin/ capture.sh # 捕获钩子入口 summarize.py # 摘要生成 search.py # 检索入口 inject.sh # 新会话注入 config.json # 配置文件 store/ # 记忆数据目录 logs/ # 运行日志

自动化方面,我用了两个机制:一是钩子触发,事件发生时调用相应脚本;二是定时任务兜底,每天固定时间跑一次聚合摘要和索引重建,防止漏触发。定时任务里还会做一次数据清理——超过90天的原始对话内容,只保留摘要部分,原始文本移到归档目录,减小存储体积。

capture.sh的核心逻辑其实很短,核心就是收到一行输入,判断是否是一个完整交互块的结束,然后追加写入归档文件。有一个小坑是并发问题——多个会话同时写入时,文件锁要处理好,否则会出现内容互相覆盖。我用的方案是按会话ID分配独立文件,从架构上就规避了并发写冲突。

summarize.py的设计重点是幂等性。每次会话结束后可能被多次触发,如果不做幂等控制,同一个会话会生成多份重复摘要。我的做法是:在会话存档中标记summary_version字段,摘要完成后记上版本号,下次触发时检查版本号,相同版本直接跳过。

4. 常见问题与排查实录

4.1 记忆注入后反而变笨了

这个问题很有迷惑性。现象是:开了记忆功能之后,模型回答质量下降,甚至开始瞎编。排查之后发现原因在于注入的记忆片段和当前问题的相关性不高,模型在试图给不相关的旧记忆找关联时,产生了幻觉。

解决思路分两步:一是收紧检索阈值。默认返回5条,有时候5条里有两条是边缘相关,后来我把阈值调到只返回相关度高于0.6的片段,宁可少注入也不注入无关内容。二是在注入时明确标注"历史记忆可能不适用于当前任务,请以当前对话内容为准"。这相当于给模型加了一道"官方免责声明",实践证明很有效。

4.2 摘要丢关键细节

摘要压缩永远面临信息丢失风险,尤其长会话。我遇到过最典型的:会话初期的某个用户明确约束,到会话结束已经经过了几十轮,增量摘要时模型觉得它不重要就给过滤了。等新会话打开,系统注入的摘要里完全没有这个约束,导致方案直接跑偏。

处理办法是增加"用户约束"的摘要优先级。在摘要模板中单列一类:用户明确说过的"必须/禁止/不要/只能",这类信息强制保留,不参与常规的摘要压缩。同时我加了一个安全机制:原始对话全文保留90天,如果某次检索发现摘要信息不足,可以回退到原始存档找完整上下文。

4.3 中文检索空空如也

这个坑前面提过,这里再详细说一下。FTS5默认的分词器对中文就是灾难,整段中文文本被当成一个token,你搜"重构"两个字,什么都匹配不到。而且最坑的是,你用不匹配的查询词去测试,费了很大劲也不知道问题在哪。

解决方法是预处理分词。写入FTS索引前,先用分词库把中文文本切词,然后空格拼接存储。这样"继续昨天的重构方案"会被切成"继续 昨天 的 重构 方案",搜索时同样把查询语句过了分词再跑匹配。实测召回率提升明显,简单方案解决大问题。

4.4 多个项目的记忆串味

如果你同时维护多个项目,记忆库混在一起会出大乱子。我之前把所有项目的记忆存到一个库里,去A项目会话里询问B项目的旧代码,检索系统还能搜出来,导致A项目的对话经常混入B项目的术语,简直一团浆糊。

解决办法是给记忆数据按项目隔离。实现很简单:每个会话启动时读取当前工作目录的路径,把目录名或项目名作为project字段写入元信息和存档文件;检索时强制带项目过滤条件。SQL改动很小:

SELECT fragment_id, content, datetime FROM memory_fragments WHERE project = ? AND content MATCH ? ORDER BY rank LIMIT 5;

这里我强烈建议:项目隔离别用数据库查询时筛选的方式,最好直接按目录隔离存储文件。因为这样不仅查询时干净,备份和清理也更简单。

5. 实操中的体会与后续扩展

5.1 使用习惯决定工具效果

同样的工具,不同人用效果差异很大。我观察到的规律是:用好claude-mem的人,大多会在会话中有意识地输出结构化信息。比如让AI助手生成方案时,要求它把决策原因单独列出来;结束一个任务时,要求它简明汇总成果。这相当于给记忆系统提供高质量原料,摘要出来的内容自然就精准。

反过来,如果跟模型随意闲聊式对话,会话里全是零散的"改这里""再看一下""不对",记忆系统能提取的信息就很稀薄。所以我现在的习惯是:重要对话前,先明确告诉助手"接下来这个会话产出的关键决策,请用列表帮我同步一遍",这样记忆文件里的捞出来就是结构化内容。

5.2 还能往什么方向扩展

这套系统目前已经满足了我的日常需求,但还有几个方向值得继续做。

一个是记忆关联图。现在记忆是线性的文本片段,但实际信息和信息之间是有拓扑关系的——某个决策关联了若干文件,某个文件又关联了若干优化点。如果能把碎片串成知识图谱,检索时就可以做多跳关联,比如"重构parser.rs会影响哪些历史决策"就能自动追溯出来。

另一个是定期记忆复盘。我打算做一个每周摘要的汇总页,把一周内的所有关键决策、修复、待办事项按项目列出来。这个文档对写周报、做复盘、梳理优先级都很有用。实现上就是在现有两级摘要上再叠加一层周聚合,工作量不大。

还有一个方向是多模态记忆。现在只能记录文本信息,但调试过程中经常有截图、复现步骤、日志片段,这些内容如果能归档进记忆库并在检索时关联引用,覆盖的场景会更完整。

我在实际使用中体会最深的一点是:记忆系统设计的好坏,很大程度上取决于"什么时候该记、什么时候该忘"。记太多,检索噪音大;忘太快,关键信息失联。我目前的策略是按时间和相关性双重衰减——时间越久远权重越低,相关度越低越不注入。但这个参数还需要不断调,不同项目、不同使用习惯的人,最优参数都不一样。

最后再分享一个小技巧:把search.py单独作为一个命令接入手动工作流中。聊天之外,我还经常在终端里直接跑claude-mem search "接口兼容性设计"来定位之前的讨论,这种操作方式比翻聊天记录效率高得多。如果你想上手这套方案,我建议从最小闭环开始:先把捕获和存储做出来,其他环节逐步加。别一开始就追求完整架构,跑通一次"记录-摘要-注入"的循环,你对这套系统的理解会完全不一样。

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

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

立即咨询