给Claude加一层长期记忆:claude-mem的设计思路与实战
用 Claude 写代码写了快半年我最大的痛点不是它写得不对而是它“记性太差”。上午刚讨论完的技术方案下午新开会话它就完全不记得了项目里改了三轮的接口约定每次都要重新贴一遍上下文。直到我基于一套开源方案改出了自己的 claude-mem 工具这个困扰才算彻底解决。这个工具的核心思路一句话就能说清给 Claude 加一层可检索的长期记忆让跨会话、跨项目的关键信息不再丢失。它解决的是 Claude 原生交互中“上下文窗口有限、会话结束即遗忘”的硬伤适合三类人Claude Code 的重度用户、用 Claude API 搭应用的开发者以及所有觉得“每次都要反复交代背景”很烦的人。下面我把整套方案的设计思路、核心机制、实操流程和踩坑记录完整拆开讲。1. 项目背景与核心设计思路1.1 为什么我会想给 Claude 上一套记忆层先说说我一开始遇到的场景。某段时间我在做一个跨平台系统的重构代码量不大但业务规则非常碎。我习惯开着 Claude Code 帮我改代码、写测试、整理变更。问题在于Claude 的上下文窗口虽然不小但每次都相当于一个“失忆专家”只要新开会话之前所有约定都要重新讲一遍。我算过一笔账每次会话光重复交代背景就要花掉 3000 到 5000 token而且交代得再详细它还是会漏掉一些隐含约定。最离谱的一次我在一个会话里和它敲定了某个模块的错误码规范第二天新会话里它按旧规范写出了完全相反的判断逻辑排查浪费了我大半天。当时我想到的解决方案无非两种要么把所有约定写进一个地方每次手动粘贴要么找一套类似 RAG 的方案把历史对话向量化需要时检索相关片段再塞进上下文。前者太笨后者太重。claude-mem 走的是一条更轻的中间路线不是把历史对话当文档去做全文检索而是先把对话内容“提炼”成结构化记忆再按需注入。1.2 与传统 RAG、向量问答的本质区别很多人一听“给 AI 加记忆”就想到 RAG但 claude-mem 的思路和传统 RAG 有本质差异。RAG 的核心是“外部知识检索”它假设知识库是预先整理好的文档用户提问后系统去库里找相关片段然后交给模型生成答案。这套方案适合做客服、知识库问答但不适合记录“这个项目的接口约定是什么”“我偏好用什么风格写代码”这类动态变化的信息。claude-mem 关注的不是“知识”而是“状态”。它记录的是你和 Claude 在协作过程中沉淀下来的事实、决策、偏好、待办事项。比如事实模拟项目 X 的订单模块使用 PostgreSQL数据库连接串放在环境变量里。偏好代码注释用中文函数命名用动词开头。决策错误码统一采用 A 类前缀不再用数字码。待办下周需要重构登录模块的鉴权逻辑。这类信息的特点是数量少、密度高、变化快和“知识库文档”完全不是一回事。用传统 RAG 去存对话历史会产生大量噪声检索出来的片段常常是废话而 claude-mem 是先做一轮提炼把闲聊和关键信息剥离开只保留值得长期记住的内容。1.3 整体架构捕获、存储、调用三层整个系统拆成三层来看会清晰很多。捕获层负责监听各种事件。我在 Claude Code 的 hook 配置里挂了会话结束和代码提交两个事件也支持通过 API 应用手动调用记录接口。事件触发后会调用一次模型做结构化提取把对话或文本内容转成前面说的那四类记忆。存储层用的是本地 SQLite 加向量扩展。消息原文、提取出的记忆条目、向量索引分别存在三张表里逻辑清晰备份也方便。选 SQLite 而不是单独跑一个向量数据库服务理由很简单个人工具不值得为了几十条记忆去维护一个独立服务单文件方案零运维还能用 Git 管理。调用层负责检索与注入。当新会话开启或者对话进行中系统会把当前项目的相关记忆按相关度排序取 Top K 条拼进 system prompt 或上下文开头。检索时同时使用向量相似度和关键词匹配配合时间衰减保证既找得准又不至于塞入过期信息。这套三层架构最大的受益点在于Claude 本身完全不需要改我们只是在外围搭了一个记忆读写通道。这意味着无论底层模型怎么升级记忆层都能稳定工作。2. 核心机制拆解记忆从捕获到注入的完整链路2.1 捕获不靠定时总结而是事件驱动最早做原型时我犯过一个错误用定时任务每隔一小时去总结一次当前所有对话。结果很灾难因为对话经常进行到一半总结出来的东西残缺不全而且大量中间步骤根本不是值得长期保留的信息。后来我改成事件驱动只在三个关键节点触发提取会话结束这是最主要的提取时机对话上下文最完整。代码提交配合 Git hook在 commit 之后抓取 diff 摘要和 commit message 作为候选记忆源。手动标记对话中如果出现明显需要记住的内容我会输入特定指令手动触发记录比如/remember 数据库连接串统一放在环境变量中。每次触发提取时我会调用 Claude 做一次结构化输出把原始文本转成一个 JSON 数组每一项包含typefact / preference / decision / todo、content、tags、importance四个字段。为什么用结构化输出而不是直接存原文因为注入时有严格的 token 预算结构化记忆可以压缩成一行摘要信息密度远高于对话原文。2.2 存储本地优先的表设计与向量索引存储层我最终选的组合是 SQLite sqlite-vec 扩展。表结构分为五张表名作用核心字段projects项目注册表id, name, path_hash, created_atsessions会话记录id, project_id, started_at, ended_at, summarymemories记忆主表id, project_id, type, content, tags, importance, created_at, last_access_atmemory_vectors向量索引memory_id, vector, model_namememory_links记忆关联memory_id, related_memory_id, relation选 SQLite 而不是 PostgreSQL 或专门的向量库我是有明确理由的。个人工具的数据量级最多也就几万条记忆这个规模下 SQLite 的查询性能完全够用而且备份就是一个文件cp一下就行。sqlite-vec 是它的扩展能在同一个数据库文件里做向量相似度查询不用额外起服务对个人开发者来说是最省心的组合。memory_links表一开始没有后来发现一个问题很多记忆之间是有逻辑关联的比如“订单模块用 PostgreSQL”和“数据库连接串放在环境变量中”其实是同一件事的两个侧面。没有关联关系的话检索时可能只命中其中一条上下文里出现信息断裂。后来我在提取后增加了一步关联计算用模型判断新记忆和现有记忆是否有归属或因果关系有就写入关联表。2.3 检索与注入相关度、多样性与 token 预算的平衡注入策略是整个工具里最考验细节的部分。注入太多会把宝贵的上下文窗口浪费在低价值记忆上注入太少又起不到作用。我调了几轮之后把默认参数定成下面这样每次最多注入五条记忆总 token 预算 800。候选集合是“向量相似度 Top 20 关键词匹配 Top 10”合并去重。排序时使用加权公式score 0.6 * vector_sim 0.3 * recency_score 0.1 * importance_score。只有得分超过 0.35 的记忆才进入最终注入列表低于这个阈值的宁可不注入也不能拿噪声污染上下文。recency_score的设计是我比较满意的一点。它是根据last_access_at和当前时间的间隔算出来的衰减分间隔超过 30 天的记忆分数会降到很低。这样可以防止两个月前的陈旧决策在今天的对话里反复刷存在感。如果你正在重构某个模块旧约定可能已经废弃这时候宁可让模型根据当前代码去推断也不要被旧记忆带偏。注入位置我试过两种放 system prompt 和放用户消息开头。实际效果差别不大但放 system prompt 更稳定因为某些模型对 system 部分的遵从度更高。格式上用多行引用块包裹每行以“记忆”开头这样模型能明显区分“这是历史记忆”和“这是当前上下文”。2.4 融合与去重让记忆不是简单堆砌如果只做提取和注入跑一段时间就会发现问题记忆库里充斥着相似条目。比如我在不同会话里三次提到“接口统一返回格式为 code, message, data”如果三次都存进去注入时会看到三条几乎一样的记忆既浪费 token又可能让模型误解成三个不同约定。所以在写入前必须做一轮去重与融合。逻辑是候选记忆先和现有同项目、同类型的记忆做向量相似度比对相似度超过 0.85 就认为是重复内容不直接写入而是把新内容与旧记忆做一次合并。合并操作我调用一次 Claude 完成给它两条几乎相同的记忆让它输出一条更完整的合并版本。这样做的附加好处是反复出现的偏好会被合并成一条高度浓缩的记忆比如“数据库连接串放环境变量”这种信息经过三次合并后会变成“所有数据库连接串放在环境变量中本地开发用 .env 文件生产环境用部署平台的环境变量配置”信息量明显增加。这个去重融合步骤看似不起眼其实决定了整个系统长期运行后会不会变成一锅粥。我见过有人做类似工具用了三个月后记忆库里堆了两千多条互相关联不上的碎片检索效果比不用记忆还差。定期融合是维持记忆库生命力的关键。3. 实操从零跑通 claude-mem 并接入 Claude Code3.1 安装与初始化先交代环境我在 macOS 和 Linux 上都有部署Windows 下用 WSL 也能跑通但原生 Windows 没测试过。基础依赖是 Python 3.11 以上建议用虚拟环境装别污染系统 Python。git clone https://example.com/claude-mem.git cd claude-mem python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt装依赖的时候最容易出问题的是 sqlite-vec 扩展它需要编译某些环境下没有装对应工具链会直接失败。我建议你提前确认系统里有gcc或clang并且在安装后跑一下自检命令claude-mem self-check这个命令会检查 SQLite 扩展是否加载成功、模型调用是否通、数据库文件能否创建。如果扩展加载失败通常是因为动态库路径没对上手动指定一下--sqlite-vec-path参数就行。初始化命令很简单claude-mem init --project 某跨平台系统首次运行会创建~/.claude-mem/目录里面包括主数据库文件、配置文件、日志目录。项目目录下会生成一个.claude-mem.json记录当前目录对应的项目 ID 和路径哈希。3.2 配置文件里那些值得关注的关键参数配置都在~/.claude-mem/config.yaml下面是我调整过的一份参考配置project: auto_detect: true path_hash_depth: 3 capture: on_session_end: true on_git_commit: true memory_types: [fact, preference, decision, todo] extract: model: claude-3-5-haiku temperature: 0.2 max_memories_per_session: 20 store: database: ~/.claude-mem/mem.db vector_dim: 384 dedup_threshold: 0.85 inject: max_items: 5 max_tokens: 800 min_score: 0.35 weight_vector: 0.6 weight_recency: 0.3 weight_importance: 0.1 position: system_prompt几个容易踩坑的参数extract.model不一定要用最强的模型我用 haiku 级别就够因为提取任务相对简单用大模型纯属浪费钱。但如果你发现提取质量经常漏掉关键信息可以换更强的模型试试。vector_dim必须和你选用的嵌入模型输出维度一致。我用的是 bge-small-zh 的 384 维如果你换别的模型这个值一定记得改否则向量查询会报维度不匹配的错误。inject.max_tokens我建议不要超过 1200超过之后对当前对话的干扰会明显增大。有一段时间我贪心设了 2000结果 Claude 经常在回答里引用记忆中的陈旧约定反而不看当前用户具体说了什么。3.3 接入 Claude Code通过 hook 自动记录Claude Code 自身支持 hook 机制我就是在它的配置里挂了两个钩子。先在项目根目录的配置文件中添加{ hooks: { SessionEnd: [ { matcher: *, hooks: [ { type: command, command: claude-mem capture --session-id $SESSION_ID --reason session-end, timeout: 60 } ] } ], PostToolUse: [ { matcher: Git_Commit, hooks: [ { type: command, command: claude-mem capture --git-diff --reason git-commit, timeout: 60 } ] } ] } }挂上之后基本就是全自动状态。每次会话结束所有对话内容会被提取成记忆存进库每次 Git 提交diff 摘要也会被吸收。我唯一需要手动介入的场景是当对话中出现了特别重要的约定但对话还远没结束时我会发一条/remember 某个约定让它立刻记录。这里有个细节值得注意capture命令执行时 Claude Code 的会话可能已经销毁了所以要保证历史消息在会话结束时已经落盘。我踩过的一个坑是某次会话异常中断导致消息文件不完整提取出来的记忆全是半句话。后来我在提取前加了一道完整性校验检测到消息不完整就直接跳过本次提取宁可不记录也不要记录错误信息。3.4 接入 API 应用两行代码让应用拥有长期记忆如果你不是用 Claude Code而是自己写应用调用 Claude APIclaude-mem 也提供了一套简单的接入层。核心就两个方法remember()和recall()。from claude_mem import Mem mem Mem(project_name某跨平台系统) # 对话中遇到关键信息主动记录 mem.remember(用户要求导出功能默认使用 CSV 格式) # 新会话开始前检索相关记忆 context_memories mem.recall(导出功能 格式) # 返回排序后的记忆列表拼进你的 system prompt接入层内部做的事情和我前面讲的一致remember走提取与去重流程recall走向量加关键词的混合检索。对已有应用来说接入成本极低不需要改模型调用逻辑只需要在拼上下文前多调用一次recall。我用这套方式把一个简单的客服问答 Demo 改造成了“记得用户偏好”的版本用户第二次访问时系统会自动想起他上次提到过的使用场景并主动调整回答风格。整个改造没动一行模型调用代码只是加了三行记忆读写。3.5 用命令行手动查看与检索记忆就算全自动化我也建议平时养成偶尔手动检查记忆库的习惯。几个常用命令# 查看当前项目所有记忆按重要度排序 claude-mem list --project 某跨平台系统 --limit 20 # 搜索某主题相关记忆 claude-mem search 数据库连接 # 手动删除一条错误记忆 claude-mem delete --id 42 # 查看最近一周新增的记忆 claude-mem list --since 7 days ago手动查看的价值在于你能及时发现记忆库里的脏数据。我平均每周删掉两三条记忆都是提取时把对话里的玩笑话或临时方案当成了长期事实。这类错误虽然不影响整体运行但时间长了会积累成干扰源。4. 真实使用中踩过的坑与排查思路4.1 记忆提取质量不稳定第一次跑通提取流程时我信心满满地直接挂了自动提取结果第二天一看记忆库简直没眼看。大量记忆是“用户说好的”“用户说没问题”这类无信息量内容而真正关键的接口约定反而漏掉了。排查后发现两个原因。一是提取提示词写得太笼统模型不知道哪些信息值得提取。二是原始对话里包含了太多寒暄和过程性描述模型被噪声干扰了。解决办法是给提取提示词加 few-shot 示例并且明确告诉模型只提取对后续协作有长期价值的信息宁可漏掉也不要存垃圾。效果立竿见影无效记忆比例从六七成降到两成以内。另外我还在提取前做了一次简单的长度过滤一句话以内的消息直接跳过不让模型处理这种低信息量输入。4.2 注入导致上下文膨胀与误导这个坑我在前面提到过一部分但它的严重程度值得单独拿出来讲。有一段时间我把max_tokens调到 2000满心以为记忆越多 Claude 表现越好结果恰恰相反。模型开始在回答中过度依赖历史记忆甚至出现“记忆比当前用户输入更重要”的倾向。典型表现是我新开会话问“当前订单模块的问题怎么排查”Claude 不去看当前代码而是翻出两周前一段“订单模块已重构完成”的记忆给我回了一段完全对不上现状的答案。从那次之后我定了一条铁律记忆是辅助不是主导。最终解决方案是把max_items限制在五条max_tokens限制在 800同时对min_score卡得更严低于 0.35 的记忆一律不注入。宁可让 Claude 当场问我要信息也不能让它拿着旧记忆一本正经地胡说。4.3 嵌入模型选型错误我第一次用的时候图省事选了一个英文为主的嵌入模型结果中文记忆的检索效果非常差。搜索“数据库连接”时返回的相关度普遍只有 0.2 左右根本过不了min_score阈值等于记忆层完全失效。换用针对中文优化的 bge-small-zh 之后同样内容的检索相关度提升到了 0.6 以上效果差距极大。这里给一个建议如果你的使用场景是中文嵌入模型必须选在中文语料上训练过的不要拿通用英文模型硬扛。另外注意不同嵌入模型输出的向量维度不同切换模型后要清空旧向量索引否则维度不匹配会直接报错。4.4 多项目之间记忆串味我的一个实际教训是同时维护两个技术栈完全不同的项目时如果不做隔离记忆会互相污染。有一阵子我两个项目目录相邻配置里项目自动识别又开了path_hash_depth: 3结果两个目录的路径哈希在深层前缀上撞了车记忆被写进了同一个项目。后来我改成path_hash_depth只取到项目根目录这一层并且初始化时手动指定项目名称彻底解决。现在两个项目的记忆完全隔离检索时也严格限定project_id过滤。如果你同时维护多个项目建议在配置里显式写清楚project_name不要完全依赖自动识别。4.5 存储膨胀与隐私问题SQLite 单文件的好处是方便坏处是不做清理就会无限膨胀。我运行三个月后数据库文件涨到了 800MB大部分空间被原始消息内容和向量占着。现在我的清理策略是原始消息保留三十天之后只保留提取出的结构化记忆。超过九十天且从未被检索命中的记忆自动降级为归档状态不再参与注入。每季度执行一次VACUUM回收碎片。隐私层面我的态度比较保守。这个工具所有数据都留在本机不会上传任何对话内容但正因为数据在本地反而要当心笔记本丢失或被人翻看的情况。建议数据库文件用系统自带的磁盘加密保护涉及密码、密钥、敏感个人信息的对话我干脆就不开自动提取手动控制哪些内容需要记录。5. 进阶玩法与一点个人体会5.1 定时归档与周报生成当记忆库积累到一定规模后我发现它本身也变成了一份很好的工作日志。于是加了一个定时任务每周一把过去七天的记忆按类型汇总调用模型生成一份本周项目进展摘要。claude-mem summarize --period 7d --format markdown输出结果会包含本周完成的事实性变更、新增的决策、遗留的待办事项。这份摘要虽然不是专门为周报设计的但稍微改改就能直接用帮我省了不少写周报的时间。后来我索性把它接入了内部知识库系统每周自动归档一份项目状态快照。5.2 联动 Git hook 和其它自动化除了 Claude Code 的 hook我还把 claude-mem 接进了 Git 的 post-commit hook。每次提交代码后自动把本次提交涉及的关键改动提取成记忆。配合我之前在代码层面做的自动化现在整个闭环是和 Claude 讨论方案会话结束自动提取决策记忆。按讨论结果写代码提交时 diff 提取事实记忆。新会话开始时相关记忆自动注入上下文。每周自动汇总生成项目进展快照。这个闭环跑起来之后我基本告别了“反复交代背景”的烦恼。新开的会话总能迅速进入正题那种“它居然记得上周的约定”的感觉用起来确实顺滑。5.3 我的一些体会最后说点个人层面的感受。给 Claude 加记忆这件事技术难度其实不高真正的难点在于克制。很多人在设计这类工具时容易陷入一个误区能记多少记多少恨不得把所有对话都塞进记忆库。但实际跑过之后你会发现记忆越多噪声越大模型被误导的概率也越高。我现在只记录四类信息事实、偏好、决策、待办。其他一切对话内容聊完就扔。另外坚持每周检查一次记忆库手动删除那些明显过时或错误的条目。这套工具让我和 Claude 的协作效率提升了一个档次但它不是银弹它只是一个组织信息的框架真正决定价值的还是使用者怎么规划边界。如果你也在为“AI 记不住事”发愁不妨照这个思路搭一套自己的轻量记忆层。从安装到跑通不到半小时但长期带来的效率提升相当可观。