ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

给 Claude 装上记忆:claude-mem 跨会话上下文与检索注入实战

给 Claude 装上记忆:claude-mem 跨会话上下文与检索注入实战 1. 项目概述与核心需求拆解我记得很清楚最开始用 claude-mem 这个想法是被一次极其憋屈的对话逼出来的。当时我接了一个跨平台小系统的活前后断断续续改了两周技术栈的选型、接口约定、UI 组件的命名习惯全都是在几十轮对话里慢慢定下来的。结果某天我开了一个新对话问了个很简单的问题模型直接按照另一套方案回答前两周定下的约定全被丢到了爪哇国。那一刻我意识到大模型本身的能力再强如果缺了“记忆”这一层所有长线协作都要靠人力反复搬运上下文。claude-mem 就是冲着这个痛点去的。它的定位非常单一给 Claude 这类对话模型加一层独立的、可检索的会话记忆系统。你可以理解成给模型配了一个“工作笔记簿”平时对话中产生的关键决策、用户偏好、技术约定、常见问题的答案都会被自动结构化地记录到本地存储。下一次开新对话时只要把相关记忆作为前置上下文注入模型就能接着上次的思路继续干不用你重新复述一遍“我们之前定过什么”。这个项目不是要替代模型本身的提示词功能也不是想做成一个通用知识库它解决的就是“跨会话连续性”这一件事。适合谁用我觉得至少有三类人值得关注第一类是经常用 Claude 做长周期研发的工程师。一个项目从原型到上线往往要跨几个月、几十次会话每开一次新会话就要重新对齐一次代码风格和架构约定效率低到离谱。有了 claude-mem这些约定能自动沉淀下来并重新注入。第二类是把 Claude 当写作助手、分析工具使用的重度用户。比如我每周都要让模型帮我整理某几个固定主题的资讯摘要模型记不住上周已经把哪些来源看过了容易重复分析。用记忆系统记录“已经处理过的来源 TOP 列表”就能避免这种低效重复。第三类是做内部工具的小团队。几个人共用一个模型入口希望模型能记住团队内部的术语、规范、项目代号但又不想把所有聊天记录全量喂给模型也不想把数据放到云上。claude-mem 这种本地优先的方案就特别合适。如果把项目价值压缩成一句话那就是让对话模型真正具备“翻阅历史任务记录”的能力而不是每次见面都像第一次认识你。2. 整体方案设计与工具选型解析2.1 从记录到回放的完整链路你要让一个模型“记住”事情表面上看很简单实际上涉及一整条链路缺一环效果都会打折。我把 claude-mem 需要做的事拆成了四条流水线捕获链路从对话中自动提取关键信息比如用户明确表达的偏好、技术方案的选定结果、某个排错的结论。存储链路把提取到的信息去重、合并、关联到某个会话或项目写入本地持久化存储。检索链路在新对话开始时根据当前对话主题或用户输入从存储中找到最相关的记忆片段。注入链路把检索到的记忆按一定格式组织成上下文插入模型的对话消息中。很多人做记忆工具容易把全部精力放在“捕获”上天天研究怎么从对话里提炼高质量摘要结果到了检索注入这一步就草草了事随便把最近十几条记忆全塞进去。这么做有两个副作用一是记忆太杂模型分不清哪些与当前任务强相关反而被无关记忆误导二是上下文占用过大token 成本直线上升。所以我在设计 claude-mem 时把真正的重点放在了“结构化存储”和“按需检索”上。捕获端只要求能把关键信息拆成相对独立、可查询的记忆单元就够了不需要追求面面俱到的摘要检索端才决定这条记忆是否真的能产生影响。2.2 存储层为什么选择本地嵌入式关系库第一版 claude-mem 我其实试过用纯文本文件来做存储就是每个会话生成一个 Markdown 文件里面写摘要。用了一周我就把自己劝退了文件一多你根本没法跨会话检索想找一条“关于日志采集方案的约定”只能一个个文件翻。后来我改用本地嵌入式关系库。选它的原因很明确首先是部署简单单文件搞定不需要额外起服务其次是数据模型天然适合用关系表来表达记忆之间有会话归属、项目归属、标签还能用时间字段做排序和过滤最后是备份迁移极其方便直接把数据库文件拷走就行不绑任何云端服务。对于向量检索的部分claude-mem 也没有一开始就上重型方案。初期我用的是一个基于社区的轻量向量检索库直接把向量存在同一个数据库里几百条记忆规模下的检索性能完全够用。只有当你的记忆量级冲到几万条以上才需要考虑单独的向量检索服务对大多数个人项目和中小团队来说嵌入式方案反而是最省心的。2.3 召回策略不能傻乎乎地全量注入我把记忆召回设计成三层过滤器逐层缩小范围而不是把所有历史记忆都翻出来塞给模型。第一层是项目空间隔离。claude-mem 支持多项目每条记忆都带着 project_id。新对话开始前一定要明确当前属于哪个项目检索只在当前项目空间内进行否则就会把 A 项目的约定带到 B 项目里去。第二层是元数据过滤。按记忆类型过滤比如只召回“决策类”记忆或者只召回“代码规范类”记忆按时间窗口过滤比如只召回最近 14 天的内容按标签过滤比如记忆被打上了“登录认证”标签当前对话主题也是认证相关就优先召回。第三层才是语义相似度召回。也就是说先用文本嵌入把记忆转成向量再和当前对话的主题描述做相似度计算取 Top K 条K 的默认值我建议控制在 5 到 10 条。有人会觉得 5 条会不会太少实际上对于上下文注入来说少而精准永远优于多而杂乱模型不是搜索引擎它不需要看几百条历史记录它只需要看到与当前问题最相关的几条关键约定。2.4 交互入口怎么设计才顺手市面上的记忆管理工具败就败在“用起来重”。如果你想记录一条记忆要打开一个专门界面点半天那用户很快就会放弃记录。所以 claude-mem 的交互我设计成两条腿走路一条自动化一条手动补录。自动化路径是后台监听对话每隔一段时间自动生成工作摘要并把摘要中的关键决策抽取出来写入记忆表。这条路不用人管适合记录那些你根本没意识到“这算重要约定”的对话。手动补录路径只保留一个轻量命令比如claude-mem remember 团队统一用 pnpm 管理依赖一条命令把一条记忆手动加进去可以附加标签和项目归属。很多正式约定其实不适合自动抽取我强烈建议关键决策还是要手动补录可靠性远高于自动抽取。查询和回放的操作也尽量简化比如claude-mem inject就能把当前项目相关记忆组装成一段提示词打印到终端你直接复制进模型对话即可。少折腾才能坚持用下去。3. 核心机制与关键实现细节3.1 对话摘要的“压缩-还原”模型claude-mem 的记忆建立在一个二元结构上摘要层和细节层。摘要层是核心记忆用一两句话概括一次对话里最有价值的结论。比如“本 session 确定了微服务的限流方案使用令牌桶算法阈值默认 1000 QPS”。摘要层体积小、数量少、可快速检索适合注入上下文。细节层则是支撑摘要的原始材料比如当时的完整讨论记录、相关的接口定义片段、修改过的文件路径。细节层不进入日常注入上下文但在用户明确追问细节时可以按需调出来。这套设计模仿的是人脑的回忆机制大脑先浮现的是一个概略印象只有当你往细处想时具体的场景细节才会浮现。如果 claude-mem 只存摘要那模型回答细节问题时就容易含糊如果只存原文检索噪声又太大。摘要层负责“想起来有这件事”细节层负责“把证据找出来给你看”两层配合才符合实际使用节奏。实现上摘要的生成我会用一次独立的模型调用输入是当前会话的增量对话记录输出是一条严格按固定格式封装的 JSON里面包含decision、preference、problem_solution、other四种记忆类型。固定格式是重中之重不固定格式解析就不是一般地痛苦我前两周就被模型生成的自由文本摘要坑过无数次。3.2 记忆表结构设计以下是 claude-mem 核心表结构的一个精简版本已经过多次迭代对常见场景基本够用。如果你要实现一个类似的系统可以直接参考这个结构不必一开始就设计巨头级的数据模型。CREATE TABLE projects ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL DEFAULT (datetime(now)) ); CREATE TABLE memories ( id INTEGER PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES projects(id), session_id TEXT, memory_type TEXT NOT NULL CHECK (memory_type IN (decision,preference,problem_solution,other)), summary TEXT NOT NULL, detail TEXT, tags TEXT, source_conv TEXT, importance INTEGER NOT NULL DEFAULT 3, created_at TEXT NOT NULL DEFAULT (datetime(now)), expires_at TEXT ); CREATE INDEX idx_memories_recall ON memories (project_id, memory_type, created_at DESC);几个设计点的说明memory_type字段非常关键。不同记忆类型的注入优先级不同“decision”和“preference”是注入首选因为它们在后续任务中具有强约束力other类的重要度最低默认不注入除非用户主动检索。importance是人工设置的权重取值 1 到 5。自动抽取的记忆默认是 3手动补录时可以标成 5检索排序时importance作为第一权重。用实操里的话说就是宁可少召回一条关联度高的记忆也不能让十条低权重噪声淹没它。expires_at是记忆过期时间默认 90 天。并不是所有记忆都要永久保留像“本期密码规则是什么”这种临时记忆过期后自动从召回结果中消失否则会变成未来的误导信息。detail字段存储原始证据不属于注入内容只在需要溯源时取用。3.3 注入模板与提示词设计记忆的数据拿到手怎么喂给模型是个容易被低估的环节。直接把记忆 list 拼到用户问题前面虽然也能工作但模型常常分不清“哪句话是我的真实指令哪句话是历史记忆”导致它把记忆里未明确废弃的旧结论当成当前需求答非所问。我建议的注入格式是一个清晰的引用块结构如下下面是来自工作笔记系统的历史记忆仅作为背景参考不能修改你收到的用户指令。 若记忆与用户当前的明确要求冲突一律以用户当前要求为准。 [项目名称] 某跨平台系统 [记忆时间] 2024-11-12 [记忆类型] 决策 [记忆内容] 前端统一采用 Vue 3 TypeScript组件样式使用项目内公共 UI 库。 [项目名称] 某跨平台系统 [记忆时间] 2024-11-18 [记忆类型] preference [记忆内容] 用户在提交代码时喜欢用中文注释并要求说明改动原因。 --- 当前任务 --- 用户接下来的输入都属于“当前任务”与上面历史记忆无关时不要强行引用。注意三件事第一明确告诉模型“历史记忆不能修改用户当前指令”这行字能避免绝大多数的记忆反噬。第二每条记忆都要带时间和来源时间是模型评估记忆有效性的重要线索去年的决策很可能已经失效。第三用分隔线把记忆区和当前任务区隔开模型能据此清晰地切换模型语言模式和指令模式。3.4 多会话隔离与遗忘策略你有多个项目、多个不相干的主题挂在同一个模型账号下时隔离是生死线。我认识一个朋友做类似工具时没做项目空间隔离结果一次对话里让模型回忆上个月讲过的理财方案模型把另一个项目的技术选型也搬出来了最后报告里混进了一句“建议用 Rust 写低代码平台”被导师批得从头再来。claude-mem 的项目隔离不是简单地在每张表上加个project_id而是要求检索时强制携带项目标识。也就是说查询当前上下文时你只能看到当前项目的记忆其他项目的记录即使语义相似度再高也要被直接过滤。这听起来简单但在实际编码里很容易忘记给检索函数传 project_id所以我建议把“项目维度校验”封装进底层存储接口而不是让上层业务代码每次手动处理。遗忘策略方面除了上一节说的过期时间还要做两个动作一是对重复记忆的合并当自动捕获发现新记忆和旧记忆本质上是同一件事时不要重复插入而是更新旧记忆的时间戳并追加细节防止记忆表膨胀二是对属性为“临时”的记忆做定时清理比如expires_at不超过三天的实验性结论每次启动时扫描并删除。4. 实操演示5 分钟搭一个可用版本4.1 环境准备手把手走一遍 claude-mem 的最小实现。以下代码用 Python 编写依赖被我压到了最低你只需要安装两个东西一个轻量的本地嵌入式数据库Python 自带不需要额外装以及一个支持文本嵌入的向量检索库。如果连向量检索库都懒得装还有一个取巧方案直接用中文分词 TF-IDF 做关键词召回在几百条记忆规模下效果不输语义检索太多。先建项目目录和虚拟环境mkdir claude-mem cd claude-mem python3 -m venv venv source venv/bin/activate pip install 向量检索库-名称4.2 最小脚本主体我并没有把整个 claude-mem 的所有业务逻辑都放进脚本里那会很大。我拆了两个关键函数大家能看懂骨架即可。第一个函数是写入记忆。入参是项目名、记忆类型、摘要内容、标签和可选细节。写之前先做去重判断如果四小时内存在一条相同摘要的未过期记忆就跳过并更新原记录的created_at避免同一个结论反复出现几百条。import sqlite3 def remember(project_id, memory_type, summary, tags, detailNone): conn sqlite3.connect(mem.db) cur conn.cursor() # 简单去重已有同摘要且未过期则只更新时间 cur.execute( SELECT id FROM memories WHERE project_id? AND summary? AND expires_at IS NULL, (project_id, summary) ) row cur.fetchone() if row: cur.execute( UPDATE memories SET created_atdatetime(now) WHERE id?, (row[0],) ) else: cur.execute( INSERT INTO memories (project_id, memory_type, summary, tags, detail) VALUES (?,?,?,?,?), (project_id, memory_type, summary, tags, detail) ) conn.commit() conn.close()第二个函数是召回。先按项目、类型筛选再按时间逆序最后对候选集做简单的重叠词打分取 Top 5。如果你接入了向量检索库把打分替换成向量相似度即可顺序完全一致。def recall(project_id, query, limit5): conn sqlite3.connect(mem.db) cur conn.cursor() cur.execute( SELECT id, summary, detail, memory_type, created_at FROM memories WHERE project_id? AND expires_at IS NULL ORDER BY importance DESC, created_at DESC LIMIT 20, (project_id,) ) candidates cur.fetchall() scored [] for mem in candidates: # 简化的关键词重叠打分可替换为向量相似度 score sum(1 for word in query.keywords if word in mem[1]) scored.append((score, mem)) scored.sort(keylambda x: x[0], reverseTrue) return [m for s, m in scored[:limit]]最后拼一个输出函数把召回结果按注入模板格式拼装打印出来。这个输出可以直接复制进新对话的前置上下文。4.3 连续两轮对话的演示肉眼可见的效果提升我实际跑过一套完整流程来验证效果。第一轮对话我让模型帮我梳理一个“时钟提醒工具”的需求明确说了三条偏好“偏好使用命令行方式不想要 GUI”“用户数据默认存储在本地 JSON 文件而非数据库”“项目命名风格统一用下划线小写”。然后我启动 claude-mem 的自动捕获把这三条偏好分别存成三条 preference 记忆。接着关掉这个会话全新开启一个会话只输入一句“继续做那个时钟提醒工具的需求”不提供任何其他背景。没有 claude-mem 时模型回答会重新问我“你希望用 GUI 还是命令行”甚至推荐引入数据库框架。挂上 claude-mem 后模型第一段回复就是“按照你之前的偏好继续命令行交互、本地 JSON 存储、命名使用下划线风格下面是接下来的细化方案”。省掉的这轮复述换来的不仅是效率还有对话的连续感模型不再是每次见面都失忆的陌生人。5. 常见问题与排查技巧实录5.1 记忆污染模型被过期的旧决策带偏这是我遇到的最严重、也最隐蔽的一个问题。某次项目里我们之前定过“缓存统一手动失效”后来方案演进为“缓存 Redis 自动过期”技术上已经取代了旧决策。但是因为旧记忆没有被标记为过期召回时它抢占了较高的分数结果模型在后续设计中死死坚持“统一手动失效”差点把新方案推翻。解决办法有两层。第一层是主动失效当模型或用户明确推翻旧方案时调用一个obsolete命令将该记忆的expires_at置为当前时间并关联一条新记忆“旧方案已废弃改为新方案”。第二层是被动抑制注入模板中明确写“若记忆与当前要求冲突以用户当前要求为准”同时在检索时降低超过 30 天的 decision 记忆权重。这个坑我不止踩了一次现在的经验是每当方案有变更先跑一遍手动补录把旧决策标记为废弃再写入新决策顺序不能反。5.2 记忆碎片化一条完整结论被拆成三条半截话自动捕获有时会提取出一个对话的中间过程而不是最终结论。比如模型说“我觉得可以用 A 方案B 方案也行后面再看”自动捕获可能会把这句话存成一条决策记忆可实际上根本没有形成决策。这种半成品记忆多了会让你在召回时看到一堆含糊不清的记录反而干扰模型理解。针对这个问题我做了两个调整第一自动捕获只在会话末尾或达到一定轮数后才触发不实时截取每一条消息给模型足够的信息量去判断“是否真正落定了结论”第二摘要生成时增加了严格的判定条件只有出现了明确的倾向性动词比如“确定”“改为”“不采用”“统一使用”才允许写入决策类记忆。含糊的讨论不记宁可漏记也不误记。5.3 上下文膨胀与 token 成本失控早期我给 claude-mem 设计的召回上限是 20 条 Top-K结果每条记忆平均 200 字20 条就是 4000 字还没开始干活上下文先吃了一大块。后来我把默认召回降到 5 条同时增加了一个压缩选项即当检索到的记忆内容整体超过一个阈值时自动用一个摘要任务把记忆合并成 3 条更凝练的要点。现在的默认参数组合是Top-K 为 5单条记忆摘要上限 120 字总注入上限 600 字。600 字对中文模型来说是个很小的开销但能承载 5 条核心决策信息性价比很高。如果你用的场景对成本特别敏感可以把每个项目的召回基数再设低一点。5.4 路径、权限与备份的各种坑本地优先有一个绕不开的麻烦Windows 和 macOS 的路径差异。项目根目录如果存在中文路径或者带空格的路径有些嵌入模型加载本地模型文件时会直接报错建议 claude-mem 的数据目录默认放在当前用户主目录的.claude-mem/下面不跟随项目代码目录走也避免被代码版本管理误提交。备份策略我的建议是每天定时复制数据库文件到另一个盘符或云盘。我遇到过数据库文件损坏导致全部记忆丢失的情况后来做了两个保险一是启用数据库的 WAL 模式提升写入容错能力二是每天凌晨把数据库文件归档一份保留最近 30 天的备份再多的备份必要性不大毕竟记忆不是巨额货币资产。6. 后续扩展方向最后分享两个我正准备动手做的扩展方向给同样想深入的朋友一些启发。第一个是接入 MCP 协议。把 claude-mem 的能力暴露成一组 MCP 工具让模型在对话中能自主发起记忆写入和检索而不是只能在对话开始前注入一次。优势是记忆不再依赖人工判断模型发现自己缺少某项背景时会自己去查。这会带来一个需要注意的风险模型可能频繁调用查询工具导致上下文里混入过多中间结果所以要在工具端加上调用频率限制和单次返回条数限制。第二个是模糊归并。用向量检索技术在后台每隔一段固定时间扫描重复度较高的记忆对把“内容相似但说法不同”的记忆合并成一条。这个能力短时间内看不出多大效果但记忆库运行一个月后碎片率明显降低召回准确率也能稳定在高位值得投入。我个人在实际使用中最深的体会是记忆系统的价值不在于“存了多少”而在于“注入时给得准”。克制地存储、精准地召回远比简单地全量记录更能解决实际问题。如果你也在为模型反复失忆而苦恼不妨照着我这个项目的最小版本搭一套先用起来再谈迭代优化。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进