如果你跟我一样,每天都要跟 Claude 这类编程助手打交道,一定遇到过这种让人抓狂的瞬间:昨天刚讨论过的项目架构,今天开一个新会话,它全忘了。你得重新把背景贴一遍,把上次的结论再讲一次,运气不好还要把踩过的坑原原本本复述一遍。这种“间歇性失忆”是当前大模型会话机制的天生短板——每次对话都是独立上下文,聊完即焚,下次从头再来。
我做了个小工具叫 claude-mem,目的就是给 AI 助手补上这一块“长期记忆”。简单说,它能把每次会话中产出的事实、决策、偏好自动沉淀成可复用的记忆文件,并在下一次会话开始前重新注入到提示词里。适合谁用?三类人最需要:经常用编程助手做项目的开发者、需要维护多个项目上下文的技术负责人、还想给 AI 工作流做自动化沉淀的折腾党。如果你只是想随手问点问题,这个工具意义不大;但只要你有一整套持续推进的技术脉络,它就能帮你少说 70% 的废话。
1. 先搞明白 claude-mem 到底在解决什么问题
1.1 为什么 AI 助手总是“记不住事”
先把这个“失忆”的根子说清楚。LLM 的推理过程是基于当前对话窗口里的全部 token,窗口之外的信息对它来说等于不存在。关闭一个会话后,历史记录虽然还在你的终端里,但它的模型权重并没有发生任何更新,下一次开新会话,它还是那个“读了所有训练数据但不知道你们昨天聊了什么的”AI。
这不是某个具体产品的 bug,而是架构层面的限制。模型的设计思路是“无状态”——每次请求都按你给的上下文独立推理。没有状态,就意味着没有“长期记忆”这个概念。为了在工程上弥补这一点,各家产品都在做上下文管理,有的靠把历史消息全部重放,有的靠 RAG 检索知识库,但本质上都是“把记忆放在外部,随用随取”。
我们遇到的痛点通常是这样的:项目里已经定好的技术选型、目录约定、命名规范,换了会话就丢;用户表达过很多次的偏好(“不要用 redux,用 zustand”“错误信息要中文”),下次照样违反;还有一些从长对话里慢慢推敲出来的结论,因为上下文太长被截断,直接消失。这些信息其实不是“知识”,而是“结果”,是之前花了不少 token 和时间才得出来的结论,丢了非常可惜。
1.2 一句话说清楚它的定位
claude-mem 做的事情,就是在对话外部维护一个“记忆层”。它从每次会话中提取值得长期保留的信息,写入本地文件,等下次会话开始,再把文件内容拼进提示词里给模型看。这样模型虽然本身无状态,但每次开工都自带“会议纪要”,行为的连贯性立刻就不一样了。
它的定位被我刻意压在中间:不是那种厚重的知识库平台,也不是需要独立服务的检索系统,就是一个有纪律的文件目录加上一套约定的读写逻辑。好处是你可以完全掌控数据,所有记忆都是纯文本,随时能用编辑器打开看、改、删,不存在“数据进了黑盒”的问题。我把这种方案叫“手写记忆”——数据格式自己定,写入逻辑自己写,读取注入自己管,正好也是理解 AI 记忆机制的最佳入门路径。
2. 设计思路:为什么“轻量纯文本”反而是最优解
2.1 技术选型的对比与分析
刚开始规划 claude-mem 的时候,我确实动过用向量数据库的念头。毕竟一说到“记忆”,很容易联想到知识库、嵌入向量、语义相似度检索。但仔细分析后,我放弃了这条路,而且理由很充分。
| 方案 | 优点 | 关键短板 | 适用场景 |
|---|---|---|---|
| 向量数据库 | 相似检索强、容量大 | 依赖重,需要安装服务,数据不可读,调试困难 | 知识库文档量达到万级以上 |
| 关系数据库 | 结构化查询、并发安全 | 需要建表、迁移,看不到纯文本,备份迁移繁琐 | 多用户协同的强事务场景 |
| JSONL + Markdown 文件 | 零依赖、纯文本、可读可改、git 友好 | 数据量上去后检索性能下降 | 个人与团队小规模长期记忆 |
个人项目的数据量其实远没有到需要用数据库的程度。一个人用一年,沉淀下来的记忆也就是几千条文本,几十万字符。这点数据量,顺序读一遍的时间在毫秒级,根本不需要建索引。更重要的是,纯文本文件可以被人工审查——我可以在每次会话结束后打开 memory 文件,快速扫一遍刚才写入的条目,不对就改,不想要就删。这种“看得见摸得着”的感觉,是数据库和向量库都给不了的。
2.2 目录结构与数据格式怎么定
我最终确定的目录结构长这样:
~/.claude-mem/ ├── global/ │ ├── memory.md # 全局性偏好与常识 │ └── sessions/ │ └── 2025-05-01.md # 按日期归档的会话记录 ├── projects/ │ ├── <project-name>/ │ │ ├── memory.md # 该项目专属记忆 │ │ ├── session-logs/ │ │ │ └── 2025-05-01.jsonl # 原始会话记录,备查 │ │ └── decisions.md # 关键决策与理由 └── config.yaml # 开关、路径、注入策略我把记忆分成两层:global 放跨项目通用的偏好,projects 下按项目隔离。这样设计的原因很直观——项目的技术栈决策不应该污染另一个项目的上下文,全局偏好则适用于所有项目。比如“所有提交信息要用规范前缀”这类习惯放 global,而“此项目用 pnpm workspace”则只出现在该项目记忆里。
格式上,原始会话记录用 JSONL 保存,每条消息一行,方便追加和按行处理;长期记忆则用 Markdown 写,因为它本身就是给人看的文本,排版和分段都能自然表达。这里有一个我认为非常重要的原则:记忆文件必须能直接被人读懂。如果哪一天工具逻辑全废了,这些文件还能靠人手继续维护,这才是最抗风险的存储设计。
3. 核心实现:把记忆管起来
3.1 记忆从哪里来:会话信息提取
整个 claude-mem 的工作流可以拆成三环:提取、存储、注入。先看提取。
每次会话开始,我都会在提示词里注入一段附加指令,让模型在对话过程中主动识别“值得记忆的信息”,并结构化地输出。这听起来像是给模型布置任务,但实际效果非常好——现代大模型在遵循指令方面足够可靠,你只要给出明确的标准,它就能准确判断哪些信息需要沉淀。
我定的提取标准有三条:事实、决策、偏好。
# 从会话记录中提取记忆条目的核心逻辑 def extract_memories(raw_messages: str, project_name: str) -> list[dict]: prompt = f"""请分析以下对话文本,提取所有值得长期记住的信息。 提取标准: 1. 事实 - 关于项目/系统的客观信息,如技术栈、目录结构、运行命令 2. 决策 - 已经拍板定论的方案选择,如选型理由、取舍结论 3. 偏好 - 用户表达过的倾向,如命名习惯、代码风格、工具偏好 输出为 JSON 数组,每项包含 type, content, source_topic 三个字段。 不要提取一次性步骤和临时性讨论。 对话文本: {raw_messages[-3000:]} 项目:{project_name}""" # 调用模型,解析返回的 JSON results = call_model(prompt) return results这段代码模拟的是实际实现里的核心函数。你可能会问为什么只取最后 3000 字符,因为提取指令只需要关注最近的对话内容——上下文越长,模型反而抓不住重点。你说“内存”特征就说“提取临时题目”。我将指明在下面独立地解释。
3.2 记忆怎么写:存储层逻辑
提取出条目后,下一步是把它们写入对应文件。在写这一层时,我踩过不少坑,最典型的就是“重复写入”。同一件事在多个会话里被重复提起,如果不做去重,memory.md 很快就堆满了相似条目,注入时既浪费 token 又干扰模型判断。
我的解决策略是“先读后写,同主题合并”。每个记忆条目都会绑定一个 source_topic 字段,写入时先扫描已有内容,如果发现同主题条目,就把新旧内容合并成一条,更新时间戳;如果没有,则追加新条目。
def write_memory(project: str, new_memories: list[dict]) -> None: mem_path = Path.home() / ".claude-mem" / "projects" / project / "memory.md" existing = parse_existing_memory(mem_path) for mem in new_memories: topic = mem["source_topic"] if topic in existing: # 合并:保留旧信息,补充新信息,更新修改时间 existing[topic].content = merge_content(existing[topic].content, mem["content"]) existing[topic].updated_at = time_now() else: existing[topic] = MemoryEntry( type=mem["type"], content=mem["content"], updated_at=time_now() ) rendered = render_markdown(existing) mem_path.write_text(rendered, encoding="utf-8")这个“同主题合并”的设计是记忆系统里最关键的细节。如果只是无脑追加,看起来什么都记了,实际上久远的细节早就被淹没了。相反,合并会让文件保持稳定大小,只记录“当前有效”的状态。用版本管理的思路来理解:你不是在保存所有历史,而是在维护一份持续更新的“当前基线”。
3.3 记忆怎么用:注入时机的选择
存储做得再好,如果注入时机不对,效果等于零。记忆注入要解决的第一个问题就是“每次对话都要带上吗?”我的答案是:不要。
我来解释为什么。记忆文件再精简,一个月下来也会积累几百行文本。如果你每次发消息都把这些全塞进去,token 消耗会持续增长,而且大量与当前任务无关的信息会稀释模型的专注力。更合理的策略是分段注入:
- 会话开始:注入全部记忆作为“项目背景”,让模型一开始就进入状态。
- 每轮对话:只注入与当前问题关键词相关的记忆片段。
- 上下文过长时:优先注入最近更新的记忆,丢弃低频旧条目。
在实现层面,“会话开始注入全部”是最好做的,只要在初始化提示词前拼接一段记忆文本就行。难在中间阶段的关联注入——你需要在每个请求前做一次关键词匹配,把 memory.md 中与当前用户提问相关的条目挑出来。
# 简易实现:按关键词匹配记忆条目 grep -i "$(echo "$USER_INPUT" | cut -d' ' -f1-10) " ~/.claude-mem/projects/$PROJECT/memory.md | head -30这个方法当然算不上优雅,它只是在做词面上的匹配,但效果已经足够用了。毕竟个人项目的记忆量不大,词面匹配带来的误差可以接受。真正讲究的时候,你可以用向量检索替代这段逻辑,但对一个小工具来说,越简单越可靠。
4. 实操实录:跑通一个完整的记忆闭环
4.1 从零初始化一个项目
整件事的实操从初始化开始。我预设了 CLI 框架里的初始化命令:
claude-mem init --project my-api这条命令会创建好目录骨架,并写入一个初始的 memory 文件。里面只有项目名、创建时间、和一段模板说明:
# 项目记忆:my-api ## 项目概述 功能:提供用户认证与数据管理 API 技术栈:(待补充) 启动命令:(待补充) ## 已确认决策 (此项目暂无记录) ## 用户偏好 - 才数据集错误:用户偏好已记录,如"错误信息用中文提示"。初始化阶段不急着写太多,等会话慢慢长出内容。这也是记忆工具的特质——它不是一个配置完就能立刻看到效果的即时工具,需要与 AI 助手协作一段时间,记忆才会从无到有地积累起来。想验证效果,请耐心跑完整跑完一个任务流程。
4.2 一次真实的会话闭环
我用“为 my-api 设计一个数据库模型”这个任务来做实测。第一次会话是全新的,我打开了记忆中初始的 memory 片段,模型会意识到“这似乎是一个新项目,背景信息还很少”。对话中,我明确表示“用 PostgreSQL,不用 MySQL,以后可能要做全文检索”,模型很快围绕 PostgreSQL 给出了模型设计。会话结束后,claude-mem 把这条决策写入 memory.md,变成:
## 已确认决策 - [2025-05-01] 数据库选型:PostgreSQL(考虑全文检索需求,MySQL 不合适)第二天我重新开一个会话,让它继续完善 API 设计。这次注入记忆后,模型一开始就自动说“基于你们项目已经确定使用 PostgreSQL,我这边建议...”——看到这种情况真的很爽,它记得你昨天拍过板的事,不需要你从头再解释一遍。这就是记忆闭环跑通以后的效果。
4.3 中途审查与人工修正
但我很快发现不能完全信任自动提取,人工审查环节必须有。有一次它把“这次部署到测试环境验证一下”这种一次性操作也记成了长期记忆,理由是“部署”听起来像项目信息。这种误判如果不纠正,就会污染后续所有会话。
所以我在每次会话结束后的收尾逻辑里加了一个确认步骤:
claude-mem review --project my-api这条命令把最近新增的记忆条目列出来,让你确认或删除。实际操作中,我发现这个 review 步骤不是可选项,而是必须项。AI 提取记忆的标准再准,也不可能完全理解你的意图,有些信息你虽然说了,但你并不希望它被沉淀下来。人机协同的朴素道理在这里体现得很充分:机器负责批量化生产,人负责做最终裁决。
5. 常见问题与避坑指南
5.1 记忆文件越来越长,上下文都被吃掉了
这是使用记忆工具必然遇到的第一个瓶颈。当 memory.md 膨胀到几千行,即使分段注入也会带来性能负担。我的处理办法是“分层老化”:
- 活跃记忆:最近 30 天内更新过的条目,每次会话全量注入。
- 归档记忆:超过 30 天未更新的条目,移入 archive 文件,只在人工调用时检索。
- 淘汰机制:与当前活跃项目无关的旧决策,可以安全删除或用 git 历史找回。
这套老化策略很朴素,但它解决了一个关键问题:记忆不是越全越好,而是越贴切越好。一条一年前的技术选型如果今天还在全量注入,它不仅帮不上忙,还可能误导模型——因为项目早就换了技术栈。
5.2 多项目同时推进,记忆乱窜
如果你同时维护五个项目,最怕的就是 A 项目的记忆跑到 B 项目的对话里。我在实现里用目录隔离彻底解决了这个问题:projects 下的每个子目录对应一个项目,读取时只扫描对应目录。
但还有一个更隐蔽的坑:全局偏好与项目记忆冲突。比如全局记忆里写了“代码风格使用 4 空格缩进”,而某个项目的 memory 里明确写了“此项目使用 2 空格”。如果不做优先级规则,模型会被矛盾信息搞糊涂。
我的解决方式是给记忆注入做层级排序,让具体项目记忆覆盖全局记忆:
# 注入顺序:项目记忆在后,全局记忆在前 GLOBAL=$(cat ~/.claude-mem/global/memory.md) PROJECT=$(cat ~/.claude-mem/projects/$PROJECT/memory.md) PROMPT="全局背景:${GLOBAL} 项目背景(优先级更高):${PROJECT} 现在开始处理任务:"优先级的含义在提示词里写清楚,让模型明确知道“冲突时以项目记忆为准”。这是提示词工程里很小但很关键的一个细节,不需要复杂的逻辑,只需在文本顺序和表述中埋入信号。
5.3 隐私与数据边界怎么守
这一点我必须单独拎出来强调。claude-mem 默认把所有记忆存在本地,不经过任何第三方服务,这是它最大的安全优势。但使用中仍要注意:一旦你把记忆内容注入到大模型的请求里,就等于把它交给了模型服务商。所以敏感数据、密钥、未公开的商业信息,不要留在记忆文件里。
我养成了一个习惯:每个月把 memory 目录 git 提交一次,定期检查有没有混入敏感内容。也建议你在提取环节就加上过滤规则,比如正则匹配身份证号、密钥格式等,直接丢弃。
| 常见问题 | 症状 | 解决思路 |
|---|---|---|
| 记忆注入太多 | 响应变慢、token 消耗激增 | 设置 max_memory_chars 上限,超限只取最近更新 |
| 过时决策误导 | 模型还在用旧方案给建议 | 定期 review,删除失效条目,并标注 deprecated |
| 跨项目串记忆 | 项目 A 的偏好出现在 B | 检查目录隔离,确认未误读全局文件 |
| 重复记录同一事实 | memory.md 大量相似内容 | 合并逻辑加入主题字段,写前先查重 |
| 提取误判 | 一次性操作被记录 | 加入 review 确认步骤,人工过滤 |
5.4 我踩过最大的一个坑
最后说一个最隐蔽的坑。我早期把记忆注入放在每轮对话的固定位置,不管用户问什么都不变。结果发现,当用户问一个简单问题时,模型也会先“回顾”一遍记忆,回答反而变得啰嗦。后来我调整策略,把记忆注入分成“全量”和“定向”两档:会话开头用全量建立背景,普通轮次用关键词匹配定向注入。就这么一个小改动,回答质量和 token 消耗都立刻好转了不少。
这种教训的核心是:记忆工具的目标不是让模型记住所有事,而是让它在正确的时机想起正确的事。无差别注入是一种偷懒的做法,短期看不出问题,长期一定导致信息熵爆炸。
6. 关于这个工具的后续想法
做到这一步,claude-mem 对我来说已经不只是一个工具,更像是我和 AI 助手之间的一种协作协议。它的价值不在于代码本身有多精巧,而在于它重新定义了“记忆”这个概念在 AI 工作流里的位置——由外部系统管理、可审查、可修正,而不是模型里的隐层参数。
有一点我想强调:这类第三方记忆工具的效果高度依赖你自己的维护习惯。不要指望装完就一劳永逸,你需要花时间定期 review、清理失效内容、调整提取标准。我用下来的体会是,当记忆文件能真实反映你当前的工作状态时,AI 助手的表现会有一个肉眼可见的提升;而如果你放任它乱长,记忆反而会成为干扰源。
如果你也打算在自己的项目里实现类似的功能,我建议从最小闭环开始:先做一个能写入 memory.md 的小脚本,再做一个能在提示词里拼文件内容的模板,跑通一次“记忆→注入”的流程后,再逐步加检索、去重、老化这些进阶功能。千万别一上来就去搞独立的记忆服务或者向量引擎,过度设计是这类项目最大的敌人。
最后再分享一个小技巧:把记忆文件纳入版本管理。git 不仅能帮你回溯误删的内容,还能让你在历史记录里观察 AI 记忆的演变过程——当你发现某一条记忆反复出现、反复被修正,那一件事多半就是你工作流里真正的痛点。顺着这个线索去优化你的提示词和工作方法,效果往往比单纯调工具来得更快。