
1. 项目缘起为什么我要给 Claude 造一个“记忆外挂”第一次用 Claude 做长周期项目的人大概率都经历过同一种崩溃昨天刚跟它对齐好的接口字段命名规范今天新开一个会话它就像失忆一样又把userId写成user_id把分页参数从pageSize改成per_page。你不得不把之前几十轮的上下文重新粘贴一遍token 烧得心疼时间也全耗在“复读”上。claude-mem这个项目就是冲着这个痛点去的。它不是官方功能而是社区里一群被“上下文失忆”折磨过的开发者自己动手攒出来的一套跨会话记忆层。核心目标很朴素让 Claude 在多次对话之间记住你的项目约定、代码风格、业务术语、甚至你个人的表达偏好而不是每次都从零开始。它解决的不是“模型不够聪明”的问题而是“模型记性太差”的问题。适合谁来参考三类人最值得看一是天天用 Claude 写代码、做重构的工程师二是拿 Claude 做长文档写作、知识库维护的内容工作者三是想在自己产品里集成 Claude、又不想每次调用都重传海量上下文的独立开发者。哪怕你只是偶尔用 Claude 查资料理解这套记忆机制的设计思路也能帮你省下不少重复解释的口水。我先把结论摆在这儿claude-mem的本质是把“会话内上下文”和“跨会话长期记忆”这两件事拆开处理。前者交给模型原生的 context window后者交给一套外部存储加检索机制。这个拆分思路是后面所有技术选型和实操细节的根基。2. 整体设计思路把“记忆”从模型里搬出来2.1 核心矛盾上下文窗口再大也扛不住长期项目很多人有个误区觉得现在模型上下文动辄 200K token记忆问题自然就解决了。实际用下来完全不是这么回事。上下文窗口是“工作台”不是“仓库”。你把三个月前的需求文档、上周的代码评审记录、昨天的接口变更全塞进工作台结果就是真正当前要处理的那段代码被淹没在噪音里模型注意力被稀释回答质量反而下降。更现实的问题是成本。200K token 的输入每次调用都按这个量计费一天调几十次账单能让你怀疑人生。所以claude-mem的设计出发点很明确当前会话只加载跟当前任务强相关的记忆片段而不是全量历史。这就引出了第一个关键设计——记忆的分层。2.2 记忆分层短期、中期、长期三档怎么分我在实际搭建时把记忆分成三档这个分法参考了常见的人类记忆模型也贴合工程实践记忆层级存储内容生命周期加载策略短期记忆当前会话的对话历史会话结束即弃全量保留在 context中期记忆当前项目的约定、术语、风格项目周期内有效按项目 ID 检索注入长期记忆跨项目的个人偏好、通用规范长期沉淀按语义相似度召回短期记忆不用管模型原生就支持。真正要动手的是中期和长期。中期记忆解决“同一个项目里别反复改口”长期记忆解决“我这个人一贯的偏好别每次都讲”。为什么这么分因为不同层级的记忆检索频率和更新频率完全不同。项目约定可能一周改一次但每次会话都要用个人偏好可能几个月才更新一次但一旦确定就长期稳定。混在一起存检索效率会很低。2.3 存储选型为什么我最终选了本地文件加向量索引存储方案我试过三种踩了不少坑最后落在一个组合上纯本地 JSON 文件最简单但检索只能靠关键词匹配语义相近但用词不同的记忆召不回来。纯向量数据库语义检索强但部署重小项目杀鸡用牛刀而且记忆条目少的时候向量检索的优势体现不出来。本地文件加轻量向量索引记忆正文存 Markdown 或 JSON向量只存索引检索时先向量召回候选再读文件拿全文。第三种是我实测下来最稳的。原因有三一是记忆内容人类可读出问题能直接打开文件排查不用去数据库里翻二进制二是向量索引可以随时重建不怕损坏三是迁移方便整个记忆库就是一个文件夹拷走就能用。提示向量索引和记忆正文分离存储是我踩过最大的坑之后定下的规矩。早期我把两者混在一起索引一坏记忆全丢血的教训。2.4 注入时机什么时候把记忆喂给 Claude记忆存好了什么时候注入也是个学问。我见过有人每次调用都把全部记忆塞进去结果 token 爆炸。我的做法是分两个注入点第一个注入点是会话初始化。新会话开始时根据当前项目 ID把该项目的中期记忆全量注入一次作为系统提示的一部分。这部分内容通常不大几百到几千 token但能立刻让 Claude 进入状态。第二个注入点是按需召回。在对话过程中当用户提到某个特定主题时用当前输入去向量索引里检索最相关的几条长期记忆动态追加到上下文里。这样既保证了相关性又控制了 token 消耗。这个“初始化全量加过程按需”的双注入策略是我反复调优后觉得最平衡的方案。全量注入保证基础一致性按需召回补充细节两者配合Claude 的表现明显比裸奔强一大截。3. 核心细节拆解记忆条目到底长什么样3.1 记忆条目的数据结构设计一条记忆不是随便写句话就完事。我设计的记忆条目包含这几个字段每个字段都有明确用途{ id: mem_20250101_001, project_id: proj_payment_service, layer: mid, category: convention, content: 所有接口的金额字段统一用整数分表示字段名后缀 _cents, tags: [api, naming, money], created_at: 2025-01-01T10:00:00Z, updated_at: 2025-01-01T10:00:00Z, hit_count: 0, embedding: [0.012, -0.034, ...] }project_id用来隔离不同项目的记忆避免串味。layer区分中期长期。category是分类方便按类型批量检索。content是记忆正文用自然语言写越具体越好。tags是辅助关键词。hit_count记录这条记忆被召回多少次用来做热度排序。embedding是向量单独存索引文件。为什么content要用自然语言而不是结构化字段因为最终是喂给 Claude 的自然语言它理解得最好。结构化字段反而增加了解析成本得不偿失。3.2 记忆的写入手动、半自动、自动三条路记忆怎么进库我实践下来有三条路各有适用场景手动写入最可靠。我在项目里定了个规矩每当跟 Claude 对齐了一个重要约定立刻手动记一条。比如“这个项目所有时间戳用 UTC”“错误码统一用五位数字”。手动写的好处是精准坏处是容易忘。半自动写入是折中方案。我写了个小脚本扫描当前会话的对话记录用规则提取出疑似约定的句子比如包含“统一”“一律”“以后都”“记住”这类词的句子生成候选记忆我确认后再入库。这样既减轻负担又保留人工把关。自动写入最省事但风险最高。让 Claude 自己在对话结束时总结本次会话的关键约定自动生成记忆条目。我试过准确率大概七成剩下三成要么总结偏了要么把临时决定当成了长期约定。所以自动写入我只用在低风险场景重要项目还是手动加半自动。注意自动写入一定要加人工复核环节。我有次偷懒没复核结果一条“临时用下划线命名”的测试约定被当成长期规范后面生成的代码全带下划线排查了半天才发现是记忆污染。3.3 记忆的检索向量加关键词的混合召回检索是记忆系统的核心。纯向量检索的问题是有时候关键词精确匹配更靠谱。比如你搜“payment_cents”向量可能召回一堆跟支付相关的记忆但真正包含这个精确字段名的那条反而排在后面。所以我用的是混合召回先用关键词在content和tags里做精确匹配命中直接加权。再用向量做语义召回取相似度 top N。两路结果合并去重按加权分数排序。取 top K 条注入上下文。加权分数怎么算我的经验公式是score 0.6 * 向量相似度 0.3 * 关键词命中权重 0.1 * 热度权重。热度权重就是hit_count归一化后的值。这个比例不是拍脑袋是我拿几十次实际检索结果调出来的。向量占大头保证语义相关性关键词保证精确性热度让常用记忆更容易被召回。3.4 记忆的更新与淘汰别让记忆库变成垃圾场记忆库用久了会膨胀里面混着过时约定、重复条目、甚至错误信息。我定了三条清理规则过期淘汰每条记忆可以设expire_at到期自动标记为失效检索时不再召回但保留在库里备查。冲突检测新记忆入库时跟同项目同 category 的旧记忆做相似度比对相似度超过阈值就提示冲突让我决定是覆盖还是并存。热度降权连续 90 天hit_count为 0 的记忆自动降权检索时排到最后相当于软删除。这三条规则配合使用记忆库能保持在一个健康规模。我有个项目跑了半年记忆条目稳定在两百条左右没有失控膨胀。4. 实操落地从零搭一套可用的记忆系统4.1 环境准备与依赖安装先说环境。我用的是 Python 3.10主要依赖三个库sentence-transformers做向量化numpy做向量运算scikit-learn做相似度计算。向量模型我选的是all-MiniLM-L6-v2理由是体积小、速度快、效果够用。你要是追求更高精度可以换更大的模型但推理成本会上去。pip install sentence-transformers numpy scikit-learn目录结构我这样组织claude-mem/ ├── memories/ │ ├── proj_payment_service.json │ └── proj_user_center.json ├── index/ │ └── embeddings.npy ├── config.yaml └── mem.pymemories放记忆正文按项目分文件。index放向量索引。config.yaml放配置。mem.py是主程序。4.2 记忆写入的完整代码实现写入逻辑我封装成一个函数核心是生成向量并追加到索引import json import numpy as np from sentence_transformers import SentenceTransformer from datetime import datetime model SentenceTransformer(all-MiniLM-L6-v2) def add_memory(project_id, layer, category, content, tagsNone): mem_id fmem_{datetime.now().strftime(%Y%m%d%H%M%S)} embedding model.encode(content).tolist() entry { id: mem_id, project_id: project_id, layer: layer, category: category, content: content, tags: tags or [], created_at: datetime.now().isoformat(), updated_at: datetime.now().isoformat(), hit_count: 0, embedding: embedding } filepath fmemories/{project_id}.json try: with open(filepath, r, encodingutf-8) as f: data json.load(f) except FileNotFoundError: data [] data.append(entry) with open(filepath, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) rebuild_index() return mem_id这里有个细节每次写入都重建索引。听起来低效但记忆写入频率很低一天可能就几次重建成本可以忽略。换来的是索引永远跟正文一致不会出现索引漂移。4.3 混合检索的核心算法检索函数是整套系统的灵魂我把向量召回和关键词召回合并def search_memory(project_id, query, top_k5): with open(fmemories/{project_id}.json, r, encodingutf-8) as f: memories json.load(f) query_vec model.encode(query) scored [] for mem in memories: vec_sim np.dot(query_vec, np.array(mem[embedding])) / ( np.linalg.norm(query_vec) * np.linalg.norm(mem[embedding]) ) kw_score 0 for tag in mem[tags]: if tag.lower() in query.lower(): kw_score 1 if any(word in mem[content] for word in query.split()): kw_score 0.5 heat min(mem[hit_count] / 10, 1.0) final_score 0.6 * vec_sim 0.3 * min(kw_score, 1.0) 0.1 * heat scored.append((final_score, mem)) scored.sort(keylambda x: x[0], reverseTrue) results [mem for _, mem in scored[:top_k]] for mem in results: mem[hit_count] 1 save_memories(project_id, memories) return results这段代码里hit_count的更新是写回文件的所以检索本身也有副作用。这是故意的让热度统计自然累积。但要注意并发问题多进程同时检索会互相覆盖。我的做法是加文件锁或者干脆单进程串行处理。4.4 注入 Claude 的拼接模板检索出来的记忆怎么拼进 prompt 也有讲究。我用的模板是这样的[项目记忆] 以下是本项目已确认的约定请严格遵守 - 所有接口的金额字段统一用整数分表示字段名后缀 _cents - 时间戳统一使用 UTC 格式 - 错误码统一使用五位数字 [当前任务] {用户输入}为什么用这个格式因为 Claude 对“请严格遵守”这类指令响应很好明确告诉它这些是约束而不是参考遵守率明显提高。另外把记忆放在用户输入之前符合系统提示在前、用户输入在后的常规顺序模型处理起来更自然。4.5 会话初始化的自动化脚本每次新会话手动拼记忆太麻烦我写了个初始化脚本自动拉取项目记忆并生成系统提示def build_system_prompt(project_id): with open(fmemories/{project_id}.json, r, encodingutf-8) as f: memories json.load(f) mid_memories [m for m in memories if m[layer] mid] if not mid_memories: return 你是一个乐于助人的助手。 lines [[项目记忆], 以下是本项目已确认的约定请严格遵守] for mem in mid_memories: lines.append(f- {mem[content]}) return \n.join(lines)这个函数返回的字符串直接作为 system prompt 传给 Claude。实测下来新会话第一轮回答就能带上项目约定不用再手动提醒。5. 常见问题与排查技巧实录5.1 记忆召回不准怎么办最常见的问题是检索出来的记忆跟当前任务不相关。排查思路分三步第一步检查向量模型是否适合你的领域。通用模型在专业领域比如医疗、法律表现会打折。如果发现语义召回质量差考虑换领域微调过的模型。第二步检查记忆条目的content写得够不够具体。我见过有人写“注意命名规范”这种记忆召回后等于没召回因为太模糊。好的记忆应该像“所有接口的金额字段统一用整数分表示字段名后缀 _cents”这样具体到能直接执行。第三步调整加权公式。如果发现关键词命中太少把关键词权重从 0.3 提到 0.4 试试。如果发现老记忆总被召回把热度权重降下来。这个公式没有标准答案得根据你的实际数据调。5.2 记忆冲突怎么处理冲突的典型场景是项目初期定了“用驼峰命名”中期改成“用下划线命名”两条记忆都在库里检索时都召回Claude 就懵了。我的处理流程是新记忆入库时自动跟同项目同 category 的旧记忆做相似度比对相似度超过 0.85 就标记为潜在冲突在写入时返回警告。我看到警告后手动决定是删除旧记忆还是保留两条并加时间戳区分。提示给记忆加updated_at字段很重要。检索时可以优先召回更新的记忆或者在注入时标注“此约定于 X 日期更新”让 Claude 知道哪条是最新的。5.3 记忆库膨胀太快怎么控制有个项目我用了两个月记忆条目从 20 条涨到 500 多条检索质量明显下降。后来我加了三条限制每个项目的中期记忆上限 100 条超了就触发合并或淘汰。自动写入的记忆默认 30 天过期手动写入的不过期。每周跑一次清理脚本把hit_count为 0 且超过 60 天的记忆归档。加了限制之后记忆库稳定在 80 条左右检索又快又准。5.4 常见问题速查表问题现象可能原因排查方法解决方案记忆召回不相关向量模型不匹配领域人工检查 top 10 召回结果换领域模型或调权重记忆冲突新旧约定并存检查同 category 记忆删除旧记忆或加时间戳记忆库膨胀无淘汰机制统计条目增长曲线加上限和过期规则检索变慢索引未优化测单次检索耗时重建索引或分片记忆丢失索引与正文不一致对比索引和文件从正文重建索引5.5 几个我踩过的坑第一个坑是向量维度不一致。我中途换过一次向量模型新旧模型维度不同索引直接报错。教训是换模型必须全量重建索引不能增量更新。第二个坑是中文编码问题。早期用默认编码写 JSON中文记忆读出来是乱码。后来统一用ensure_asciiFalse加 UTF-8问题解决。第三个坑是并发写入覆盖。有次我开了两个终端同时写记忆后写的把先写的覆盖了。后来加了文件锁或者干脆规定记忆写入必须串行。第四个坑是过度依赖自动写入。前面提过自动总结准确率只有七成重要项目千万别偷懒。6. 进阶玩法让记忆系统更聪明6.1 记忆的自动摘要与合并当同 category 记忆超过一定数量可以触发自动摘要。比如十条关于命名的记忆让 Claude 总结成一条综合规范。这样既压缩了体积又保留了核心信息。我试过十条压成一条信息保留率大概八成但检索效率提升明显。6.2 跨项目记忆共享长期记忆层可以跨项目共享。比如“我习惯用中文注释”“我偏好函数式写法”这类个人偏好所有项目都能用。实现上就是给长期记忆不设project_id检索时全局召回。这样新项目启动时个人偏好自动带上不用重新配置。6.3 记忆的可视化面板记忆条目多了之后纯靠命令行管理很累。我后来写了个简单的 Web 面板能浏览、搜索、编辑、删除记忆还能看每条记忆的召回次数。工具不复杂但管理效率提升很大。你要是懒得写直接打开 JSON 文件用编辑器改也行就是麻烦点。6.4 与版本控制结合记忆文件我建议纳入 Git 管理。每次记忆变更都有 commit 记录出问题能回滚还能看到约定是怎么演进的。我有个项目的记忆库半年下来 commit 记录清清楚楚哪条约定什么时候加的、为什么加的一目了然。这对团队协作尤其有用新人接手看记忆库的 Git 历史比看文档还快。7. 我个人的使用体会这套claude-mem我用了大半年最大的感受是记忆系统的价值不在于技术多复杂而在于坚持维护。工具本身几百行代码就搞定了难的是养成习惯——每次对齐约定就记一条每次发现冲突就清理一次。我见过太多人搭好了系统用两周就荒废了因为懒得维护记忆库变成垃圾场检索质量下降最后干脆不用了。所以我的建议是从小处开始。别一上来就搞全自动、搞复杂架构。先手动记十条最重要的约定用起来感受到好处再慢慢加自动化。记忆系统是养出来的不是搭出来的。另外一点体会是记忆的粒度很关键。太粗了没用太细了爆炸。我的经验是一条记忆对应一个可执行的约定能直接指导一次具体操作。比如“金额用分”就是好粒度“注意代码质量”就是坏粒度。你拿这个标准去筛记忆库的质量自然就上去了。最后分享一个小技巧我会定期大概每月一次把记忆库整个导出来让 Claude 自己读一遍问它“这些约定有没有互相矛盾的地方”。它有时候能发现我自己没注意到的冲突。这个“记忆体检”习惯帮我避免了好几次潜在的规范打架。