
用了大半年 Claude Code最让我崩溃的从来不是代码写得不对而是它“记性太差”——今天下午刚跟你敲定的技术选型、接口约定、命名规范睡一觉回来新开个会话它全忘了同一个问题能来回解释三遍。后来我自己折腾了一个工具名字就叫 claude-mem核心目标很直接给 Claude 装一套长期记忆让每次对话结束的时候自动把重要结论沉淀成结构化文件下次开新会话之前再读回去。这篇东西不聊虚的把我踩过的坑、拆过的设计、最后跑通的方案全部摊开讲适合所有被 AI 编码助手“失忆”问题折磨的开发者。1. 痛点拆解Claude Code 的“失忆症”到底是什么1.1 会话隔离与长期记忆的根本矛盾Claude Code 这一类终端编码助手本质上是一个“无状态”的对话引擎。每一次启动会话它拿到的是当前项目的文件快照、系统提示词外加你最近几轮输入的上下文。会话一结束除非手动把关键信息写进某个文件否则这些讨论结果就像没发生过一样。这个设计本身不算缺陷——上下文有窗口上限token 有成本保持会话隔离能避免历史噪音污染新任务。但对长期项目来说它就成了一个隐蔽的坑。我举一个实际场景一个中型 Web 项目前后端加起来几十个模块我用 Claude Code 帮我把认证模块从 JWT 方案换成 Session 方案。换的过程里我们讨论了很多细节——为什么放弃无状态、session 存储放在哪、Redis 的 key 格式怎么设计、旧 token 怎么兼容迁移。当时聊得很顺代码也改完了。结果第三天新开一个会话想让它继续优化登录接口的并发问题它居然问我“这个项目用的什么认证方案”。我当时血压就上来了。这不是个例。代码风格偏好在会话之间丢失、项目约定反复被打破、之前排掉的坑换一种问法又踩一遍。真正的问题在于AI 的对话记忆是短暂的而项目的知识沉淀需要是长期的。这两个诉求天然矛盾。于是市场上的解决方案大致分两派一派是把所有约定写进CLAUDE.md这类项目文档让模型每次启动都读另一派就是给对话过程增加“事后总结”机制也就是 claude-mem 走的路线。前者依赖你手动维护写少了没用写多了干扰后者通过事件触发自动沉淀省心很多而且和你实际聊的内容严格对齐。1.2 claude-mem 解决的问题边界先说清楚 claude-mem 不是什么。它不是一个插件式的人格里存储系统也不是一个向量数据库更不是要在每次请求里塞一堆 RAG 检索结果。它要解决的问题非常具体在不需要人工干预的前提下把一次有价值的 AI 对话压缩成若干条长期有效的记忆并且在下次对话开始前自动、有优先级地注入上下文。所以它的核心价值有三层。第一层是沉淀对话结束自动提炼出项目决策、用户偏好、约定、待办、外部工具知识点落盘成可读文本。第二层是复用新会话启动时把与当前项目相关的记忆按优先级拼进上下文让模型“想起来”之前的事。第三层是可控所有记忆都是纯文本你可以随时打开文件增删改也可以设置哪些分类不参与注入甚至完全关闭自动注入只在需要时手动拉取。这三层决定了我后续所有设计选择。比如为什么用 Markdown 而不建数据库为什么按项目目录做隔离为什么提炼动作放在会话结束而不是对话进行中——这些都是在“沉淀、复用、可控”这三个关键词下推导出来的。2. 整体设计思路把记忆做成一棵可读可改的知识树2.1 三层记忆结构全局、项目、会话最开始我踩过一个方向性错误把所有记忆堆在一个大文件里。结果几天之后那个文件就变成了一锅粥——项目 A 的决策混着项目 B 的约定你让模型读它读得头大你也不愿意手动去整理。后来我重新设计把记忆拆成三层。全局层global跨项目通用的内容比如你个人偏好的代码风格、常用的工具链、通用禁忌。例如“接口返回结构统一用{ code, data, message }”这种放在全局层。项目层projects按项目目录隔离的内容比如某个仓库里的技术选型、目录约定、待办事项。这一层严格跟随当前工作目录命中。会话层session单次对话产生的临时上下文只在当前会话内有效结束后不会被沉淀为长期记忆除非内容足够重要被升级到前两层。对话层的存在非常重要。它避免了一个问题不是每句闲聊都值得被记住。如果每一次会话的结束总结都自动写入长期记忆噪音会迅速淹没信号。所以我在设计的时候加了一个“升级判定”环节——提炼出的候选记忆里只有被判定为“对未来的项目工作有影响”的条目才会从会话层进入项目层或全局层。这套三层结构在实践中很容易理解全局层是你的工作习惯项目层是你所有项目的共同记忆仓库会话层是每段对话的短期视野。模型每次启动会话时注入顺序也是先全局后项目最后叠加会话过程中的实时上下文。2.2 Markdown 存储和我们为什么不用数据库有人问过我记忆数据为什么不用 SQLite 或者 JSON 存非要落成 Markdown 文件这是个好问题我从两个角度回答。第一可读性优先。记忆系统的最终消费者其实是两个一个是模型一个是人。模型读文本没有任何问题但人——也就是你——总会想打开文件看看 claude-mem 到底记住了什么。Markdown 用记事本就能打开结构一目了然可以手动改、手动删、手动补注释。如果用数据库你要么写命令行查询要么做个管理界面都属于本末倒置。第二版本管理友好。我的项目层记忆目录我直接纳入了代码仓库的.gitignore之外的独立目录但这不代表不能用 Git 做版本管理。Markdown 文件天然适合 diff我可以清楚地看到某条记忆是哪一天加的、哪天被标成废弃的。如果是数据库里的二进制文件做 diff 就是一场灾难。存储格式上我规定每个记忆条目用固定的叶子格式。单个记忆文件按分类命名比如user_preferences.md、project_decisions.md里面每条记录是一个两行结构## 2025-06-12 认证方案确定 - id: 47f3c2a1 - category: project_decisions - status: active - 我们最终放弃 JWT改用 Session Redis。迁移期间保留旧 token 校验接口 30 天。这个格式有三个好处时间戳给了记忆时效信息id 字段用来做去重和覆盖status 字段用来标记废弃。模型读取时规则明确——只关注 status 为 active 的条目。2.3 记忆文件的目录规范与命名规则目录规范直接决定了注入逻辑的复杂度。我的实际目录结构长这样~/.claude-mem/ ├── config.toml ├── global/ │ └── memories/ │ ├── user_preferences.md │ └── external_knowledge.md └── projects/ └── my-web-app/ ├── meta.json └── memories/ ├── project_decisions.md ├── conventions.md └── todos.mdconfig.toml是全局配置控制提炼阈值、注入开关、分类映射等global/memories/目录按分类文件名组织全局记忆projects/项目名/下面挂每个项目的记忆目录。项目名是从当前工作目录推断出来的默认取目录名也可以在meta.json里显式指定别名这样同一个仓库换了一层目录名记忆还能对上。这个设计最核心的一点是记忆与项目目录强绑定。早期版本我试图用“仓库 URL”来识别项目但在本地多分支、多目录迁移的场景下很容易失效。换成目录名匹配之后一套简单的字符串匹配就解决了绝大多数命中问题而且两个同名目录下的记忆永远不会串。3. 核心机制拆解记忆是如何被自动提炼的3.1 会话结束后的自动提炼链路claude-mem 的触发时机我最终确定在会话结束而不是对话进行中。原因有两个第一对话进行中做实时提炼会打断主任务的 token 预算而且摘要质量不高——你还没聊完怎么知道哪件事值得被记住第二结束后提炼可以一次性拿到完整会话记录总结的上下文更全视角也更接近“事后复盘”。具体链路是这样每次 Claude Code 会话结束会触发一个 hook 事件claude-mem 监听到之后启动提炼流程。整个流程分四步读取本次会话的原始记录文件通常是 JSONL 格式的对话日志。调用大模型 API输入一份“提炼指令”和完整对话日志输出结构化结果。对结构化结果做后处理去重、过滤低置信度条目、按分类写入对应的 Markdown 文件。更新meta.json里的项目元信息比如“最后一次记忆生成时间”、“当前项目活跃记忆条数”。这个链路我用伪代码表示大概是def on_session_end(session_id): logs load_session_logs(session_id) if len(logs) MIN_DIALOGUE_LENGTH: return # 对话太短不提炼 result llm.extract_memories(logs, EXTRACT_PROMPT) cleaned deduplicate(filter_candidates(result)) write_memories(projectcurrent_project(), memoriescleaned) update_meta(projectcurrent_project())有个细节很重要MIN_DIALOGUE_LENGTH这个阈值。如果对话只有三五轮基本没有沉淀价值直接跳过提炼避免为噪音付费。我实测下来阈值设在 20 轮对话左右比较合理低于这个数提炼出的东西要么是空泛的客套要么是已经被代码本身表达的内容。3.2 提炼 Prompt 的核心设计提炼的质量完全取决于 Prompt。我早期犯过懒写过一版极简提示词“请总结这段对话的重要信息”。结果提炼出一堆“用户讨论了登录功能”“用户决定使用 Redis”——全是废话。后来我把 Prompt 拆成三层约束分类约束、长度约束、时效约束。分类约束要求模型把每条记忆归入五类之一project_decisions项目决策、user_preferences用户偏好、conventions约定规范、todos待办事项、external_knowledge外部知识。长度约束要求每条记忆的正文控制在 80 字以内并且必须是一句能被后续模型直接理解的话不允许“根据我们之前的讨论”这种模糊表述。时效约束要求模型判断这条信息如果三个月后还存在对项目有没有影响没有就直接丢弃。我实际在用的提炼提示词核心段落长这样你是一个记忆提炼助手。我给你一段 AI 编码助手的完整对话记录你的任务是从中提取出对未来长期工作有参考价值的要点。 要求 1. 忽略问答过程中的寒暄、试探、临时性任务讨论。 2. 只保留下面五类信息 - project_decisions团队/项目在本次对话中确定的技术选型和架构决策 - user_preferences用户反复强调的工作方式、代码风格偏好 - conventions明确说出的命名规则、目录规范、提交约定 - todos清晰的、需要后续完成的事项 - external_knowledge对话中确认过可用的外部工具、库、服务要点 3. 每条记忆正文不超过 80 字必须是一句完整、独立、可执行的话。 4. 预估这条信息在 3 个月后是否仍有用如果基本无用不要输出。 5. 输出 JSON字段为{memories: [{category: ..., content: ...}], summary: 一句话总结本次会话}这个 Prompt 我在真实使用中迭代了五个版本。最初没有“3 个月后是否仍有用”这个约束结果提炼出大量过期的临时信息比如“用户今天在调登录接口报错”——这种信息第二天就没价值了。加上时效约束之后提炼的信噪比明显提升。3.3 记忆注入策略优先级、去重与时效记忆生成到位剩下的一半功夫在“注入”。claude-mem 的注入逻辑不是把所有记忆一股脑塞进上下文而是做了一套轻量优先级排序。规则大致是这样注入时先读全局层再读项目层全局层里user_preferences优先级最高项目层里conventions和project_decisions优先todos看情况——如果本次会话的主题和某个 todo 强相关才注入否则不注入。external_knowledge永远排最后。排序之后还要处理总长度。我设了一个硬上限每次注入的记忆正文总字符数不超过 3000 字符。超过的部分按“最近更新时间”从新到旧截断。这个上限是我根据上下文窗口算出来的——如果一段对话的上下文是 200K tokens塞 3000 个字符的记忆进去大约只占 1000 tokens 左右几乎感觉不到成本但对模型行为的引导效果非常明显。去重逻辑上我采用的是“摘要相似度 关键字碰撞”的两层策略。摘要相似度用简单的字符重叠率来判断重叠率超过 70% 就视为重复。关键字碰撞是针对project_decisions和todos的比如新提炼的记忆里有“认证”旧的也有“认证”就认为可能是同一件事的延续新条目优先生效旧条目标记为 superseded。时效性方面我在config.toml里给每个分类配了默认过期时间比如todos默认 30 天过期project_decisions默认 180 天。过期条目不会被注入但不会立即删除——文件里保留用 status 标记为 expired你想翻历史还能翻到。4. 实操配置从零跑通 claude-mem 的完整步骤4.1 安装配置与初始化安装这部分不复杂前提是你已经有 Claude Code 的使用环境并且拿到可用的 Anthropic API 密钥。claude-mem 本身是一个命令行工具执行安装命令之后它会自动创建~/.claude-mem/目录并生成一个默认配置文件。npm install -g claude-mem claude-mem initinit命令会做三件事创建配置目录、生成默认config.toml、检查 Anthropic API 密钥是否可用。如果环境变量里已经有ANTHROPIC_API_KEY它会直接复用如果没有会提示你手动填入。初始化的config.toml里面我常用的是这几个核心字段[general] min_dialogue_length 20 # 低于 20 轮对话不提炼 max_inject_chars 3000 # 注入记忆的总字符上限 auto_inject true # 新会话自动注入记忆 [extract] base_url https://api.anthropic.com model claude-sonnet-4-20250514 # 提炼用的模型可以用便宜的 max_candidates 20 # 单次提炼最多生成 20 条候选记忆 [expiration] project_decisions 180 # 项目决策 180 天过期 conventions 180 todos 30 user_preferences 365 external_knowledge 365 [projects] my-web-app web/auth # 手动指定项目别名与目录的映射这里有个选型要点提炼用的模型不需要最强便宜快速的型号就够用。提炼任务本质上是文本摘要不是复杂推理我多数时候用中端型号不仅节省成本速度也快会话结束一两秒就能完成提炼。4.2 hooks 配置与命令详解安装好之后最关键的一步是配置 Claude Code 的 hooks。如果你不配置claude-mem 就是个孤立的命令行工具不会自动触发。hooks 的作用是让 Claude Code 在会话生命周期的事件点上执行外部命令。我需要的两个事件SessionStart会话开始时和SessionEnd会话结束时。配置方法是在 Claude Code 的配置文件里声明 hooks。实际配置如下claude-mem setup-hooks这条命令会自动往 Claude Code 的配置里写入 hooks 声明。手工写的话对应的 JSON 片段长这样{ hooks: { SessionStart: [ { hooks: [ { type: command, command: claude-mem inject --project \$(basename $PWD)\ } ] } ], SessionEnd: [ { hooks: [ { type: command, command: claude-mem extract --session \$CLAUDE_SESSION_ID\ } ] } ] } }解释一下这两个命令extract负责在会话结束的时候跑提炼流程需要拿到当前会话 ID 才能去读对话日志inject负责在新会话启动时读取记忆并注入上下文注入的方式是往系统提示词末尾追加一段“项目记忆”内容。$PWD 用来识别当前项目目录这是项目层记忆能否命中的关键。这里有一个我踩过的坑SessionEnd 事件并不是所有退出路径都能触发。如果 Claude Code 进程被强制杀掉或者终端直接关闭没有走正常退出流程hook 不会执行这次对话的记忆就丢了。规避办法是在配置文件里开启一个“兜底策略”——每次启动新会话时如果发现有“上次未提炼”的会话日志标记先补提炼一次。这个策略帮我捞回了好几次以为丢掉的记忆。4.3 验证记忆生效与日常使用技巧配置完之后验证链路是否通的最快方法很简单故意在对话里说一个明确的偏好比如“以后所有数据库查询都强制加 LIMIT 100”然后正常结束会话看~/.claude-mem/projects/你的项目/memories/下是否出现了user_preferences.md内容里有没有刚才那句。我第一次验证的时候文件是生成了但发现它记住的和我预想的不太一样——它记住的是“用户要求数据库查询限制结果条数”然后还额外加了一条“用户可能受到性能问题困扰”。后者明显是模型脑补的并不存在。这个问题靠 Prompt 里的长度约束和时效约束缓解了不少但也很难完全消除。好在这套系统的优势在于“可控”——我打开文件手动删掉那条脑补内容下次注入就干净了。日常使用中我总结出几个技巧第一重要结论在对话里说清楚比如“这是我们最终的决定请记下来”比聊到一半顺带带过更容易被提炼出来。第二定期翻一下项目层记忆文件发现过期条目直接手动把 status 改成 expired这比依赖自动过期更准确。第三想临时关闭注入时直接设auto_inject false但保留extract让记忆继续沉淀只是不再自动注入——这在做专注型任务时很管用不会让历史记忆干扰模型对当前问题的判断。5. 踩坑记录高频问题与排查速查表5.1 高频问题排查实录这部分把我实际遇到过的、以及社区里大家最容易遇到的高频问题整理成了一份速查表供你直接对着排查。现象可能原因排查方法解决方案会话正常结束但没生成记忆文件对话轮数低于min_dialogue_length检查会话日志长度降低阈值或手动执行claude-mem extractextract执行了但记忆内容为空对话全是临时性提问无长期价值查看提炼日志的 summary 字段正常现象无需处理注入的记忆和当前任务无关项目目录匹配错了检查meta.json里的项目名手动指定--project参数记忆内容重复出现去重阈值太低或 id 冲突查看记忆文件的 id 字段提高摘要重叠率阈值到 75% 以上SessionEnd hook 没触发进程被强制杀掉或终端闪退检查兜底策略日志开启补提炼兜底开关注入后模型行为明显变怪记忆文件有陈旧或错误条目打开文件人工检查手动修正或删掉问题条目记忆写入很慢会话日志太长提炼 token 数大查看提取耗时统计限制单次提炼的日志输入行数这里最值得展开的是第一和第三个问题。阈值过低会导致提炼任务执行得非常频繁但产出的几乎都是低价值信息阈值过高又会漏掉真正重要的内容。我在多台机器、多个项目上观察下来20 轮是一个比较平衡的点但如果你的对话普遍较短可以下调到 15 轮试试跑几天看效果再回调。项目匹配错误的坑则更隐蔽。你把项目克隆到另一个目录比如从~/work/my-app变成了~/work/my-app-v2目录名变了旧记忆就匹配不上了。解决的办法就是上面提到的[projects]配置里手动指定映射把记忆目录固定到my-app不管代码目录叫什么名字都能命中。5.2 记忆膨胀控制 token 成本的实用手段用了一个月之后你会面临一个新问题项目层记忆越来越多注入的 3000 字符上限根本装不下所有 active 条目。这时候如果只是按时间截断很可能导致重要的早期决策被顶掉而那些最近的、琐碎的记忆反而占了位置。我尝试过几种做法最终留下的是“分类配额制”。思路是在 3000 字符总预算内给每个分类分配固定比例。比如conventions占 40%、project_decisions占 30%、user_preferences占 20%、todos占 10%、external_knowledge不占预算只等剩余空间。这样做的好处是无论记忆总量增长多少核心的约定和决策始终有位置临时性的 todo 和外部知识随时可以被顶掉。这个策略结合自动过期实测下来即使记忆库里有几百条记录注入给模型的内容依然能维持在可控范围内。我也试过用向量检索来动态筛选记忆效果确实更好但引入了额外组件和复杂度。对于一个“加记忆”这么简单的需求重了。所以最终的方案还是偏务实的规则引擎。再说一下成本。提炼一次对话假设会话日志有 2000 tokens加上提示词本身一次调用大概消耗 2500 tokens 输入、400 tokens 输出。按 API 价格粗算一次提炼折算下来不到人民币一毛钱。就算一天结束十个会话成本也处于完全可以忽略的水平。5.3 多项目、多分支场景下的记忆隔离最后提醒一个容易被忽略的场景同时维护多个项目、或者在多个分支之间横跳时的记忆隔离问题。claude-mem 按目录名隔离项目这意味着如果你在一个项目的两个分支之间切换只要目录名不变记忆就是共享的。这通常没问题因为项目的约定和技术选型不会因为分支不同而改变。但有一种情况例外你有一个“大改造”分支比如把后端从 REST 改成 GraphQL这个分支上的对话产生了大量关于 GraphQL 的临时决策。如果你切回主线分支开启新会话这些记忆会被注入而主线根本不相关。我的应对办法是分支级记忆前缀在config.toml里给项目开启分支感知记忆路径从projects/my-app/变成projects/my-app/feature-graphql/。开启之后每个分支拥有独立的记忆空间互不干扰。代价是分支合并之后需要手动合并记忆但这个操作一般半年碰不上一次成本很低。还有一个细节团队协作场景下如果多个人共用同一台机器或者同一个项目目录被多人使用全局层的user_preferences会变成“多人的平均数”反而没有任何一个人的偏好能得到尊重。这个问题目前没有完美的自动化方案我的建议是全局层按用户目录拆分成~/.claude-mem/users/username/各人的偏好走各自的文件项目层仍然共享。最后分享一个我近期感受到的变化。以前我总觉得自己养成的“写 CLAUDE.md 习惯”就是给 AI 加记忆的正解但真正用上 claude-mem 之后才发现手写的文档和自动提炼的记忆是互补关系——CLAUDE.md 适合放“一开始就确定、长期不变”的规则claude-mem 负责捕获那些“今天聊出来、明天就忘掉”的动态结论。我现在的工作习惯是大原则写进 CLAUDE.md细节约定交给 claude-mem 自动沉淀每隔一两周翻一次记忆文件做一次轻量清理。这套组合拳坚持下来Claude Code 在我手上的生产力确实上了一个台阶至少它不会再一脸无辜地问我“这个项目用的什么认证方案”了。