claude-mem:为Claude构建持久记忆系统的架构设计与实操指南 1. 项目概述与核心定位1.1 这个项目到底在解决什么问题claude-mem 这个标题从字面拆解就是 Claude 加 memmem 是 memory 的缩写。放在当前 AI 工具链的语境下它指向一个非常具体且普遍存在的痛点大语言模型在跨会话场景下没有持久记忆。我用 Claude 做开发辅助、文档撰写、代码审查已经有一段时间了最让人抓狂的就是每次新开一个对话窗口之前聊过的项目背景、代码规范、技术选型偏好全部归零。你得重新贴一遍上下文重新解释一遍项目结构重新强调一遍我们不用某个库日志格式要按这个来。一次两次还行天天这么干时间全耗在重复交代背景上了。claude-mem 要做的就是给 Claude 装一个外挂记忆层。它不是一个官方功能而是社区开发者围绕 Claude 的接口能力构建的一套记忆管理系统。核心思路是把每次对话中值得留存的信息抽取出来存到本地或远程的持久化存储里下次对话时再按需注入到上下文中。这样 Claude 就能记住你是谁、你在做什么项目、你偏好什么风格。适合谁来关注这个项目三类人最需要一是每天高频使用 Claude 做开发的工程师二是用 Claude 做内容创作需要保持风格一致性的写作者三是想在自己产品里集成 Claude 并需要多轮记忆能力的开发者。如果你只是偶尔问几个零散问题那确实用不上但只要你有持续性的项目协作需求这套东西的价值就非常明显。1.2 核心能力边界与预期管理在动手之前必须把预期摆正。claude-mem 不是魔法它本质上是一套信息抽取 存储 检索 注入的工程管道。它能让 Claude 看起来有记忆但记忆的质量完全取决于你抽取什么、怎么存、怎么召回。我见过不少人一开始期望过高以为装上了就能让 Claude 像人一样记住所有细节。实际用下来会发现几个硬约束第一上下文窗口是有限的你不可能把所有历史都塞进去必须做筛选和压缩第二抽取环节如果做得粗糙存进去的都是噪音召回时反而干扰判断第三记忆的时效性需要管理三个月前的技术决策可能现在已经不适用了。所以这个项目的定位应该是一个辅助记忆系统而不是全知全能的第二大脑。它的价值在于减少重复沟通成本而不是替代你管理知识。把这一点想清楚后面的方案选型和参数调优才有正确的方向。2. 整体架构设计与方案选型2.1 记忆系统的四层结构拆解一套能跑起来的记忆系统我习惯把它拆成四层来看这样每一层的职责清晰出问题也好定位。第一层是采集层。负责从对话流里识别哪些内容值得记。不是每句话都有价值好的收到谢谢这种直接丢弃。真正要抓的是项目背景陈述、技术决策及其理由、用户的明确偏好、反复出现的约束条件、以及被纠正过的错误认知。第二层是存储层。决定记忆存在哪里、以什么格式存。常见选择有本地文件JSON、Markdown、轻量数据库SQLite、向量数据库用于语义检索。格式上要么是结构化字段要么是自然语言摘要各有取舍。第三层是检索层。下次对话时怎么从一堆记忆里挑出跟当前问题最相关的几条。简单做法是关键词匹配进阶做法是向量相似度检索再复杂点可以加时间衰减和重要性权重。第四层是注入层。把检索出来的记忆以合适的格式拼接到系统提示或首轮用户消息里。这里的关键是控制 token 消耗同时保证 Claude 能正确理解这些记忆的语义。这四层里采集和检索是最容易出问题的环节。采集太宽会存一堆垃圾采集太窄会漏掉关键信息检索不准会把不相关的记忆塞进去反而干扰 Claude 的判断。存储和注入相对机械按规范做就行。2.2 为什么选择本地优先的存储方案在存储选型上我强烈建议本地优先。原因有三。其一是隐私。你的项目背景、代码片段、业务逻辑这些信息敏感度不低。存到第三方服务上多一层数据泄露风险。本地存储虽然麻烦一点但数据完全在自己手里心里踏实。其二是延迟。记忆检索是要在每次对话开始时实时发生的如果走远程接口网络往返会拖慢响应。本地 SQLite 或文件读取基本是毫秒级体验流畅得多。其三是可控性。本地存储你可以随时打开看、随时改、随时删。调试记忆系统时能直接看到存了什么、召回了什么这对排查问题至关重要。远程服务往往是个黑盒出问题只能猜。具体到格式我推荐SQLite 向量扩展的组合。SQLite 负责存结构化元数据时间戳、来源、标签、重要性评分向量扩展负责语义检索。如果不想引入向量库退而求其次用 SQLite 的全文检索FTS5也能凑合只是语义匹配能力弱一些需要靠关键词设计来弥补。2.3 抽取策略规则优先还是模型优先采集层有个关键决策用什么方式判断一段内容值不值得记规则优先的做法是预设一批模式比如检测到我们决定用 X以后都按 Y 来注意不要 Z这类句式就触发记录。优点是快、便宜、可控缺点是不够灵活用户换个说法就漏了。模型优先的做法是每次对话结束后把整段对话丢给一个轻量模型让它输出值得记住的要点列表。优点是覆盖全、理解深缺点是要多花一次推理成本而且模型可能过度抽取。我的实践经验是两者结合先用规则做粗筛把明显有价值的片段捞出来再用模型对这些片段做精炼和结构化。这样既控制了成本又保证了质量。规则层负责宁可多捞模型层负责精挑细选。具体规则可以这样设计包含决策动词决定、选择、采用、放弃、包含偏好表达我喜欢、我习惯、不要用、包含约束条件必须、不能、限制在、包含纠正信号不对、应该是、改成的句子都进入候选池。然后模型对候选池做二次判断输出标准化的记忆条目。3. 核心细节解析与实操要点3.1 记忆条目的数据结构设计记忆存成什么样直接决定了后面检索和注入的效果。我踩过的坑是一开始图省事直接把整段对话原文存进去结果检索时匹配到的是一大坨文本注入时又占大量 token还经常包含无关内容。后来改成结构化条目每条记忆包含这几个字段字段名类型说明id整数主键自增content文本记忆的核心内容一句话到三句话精炼表达category枚举分类项目背景、技术决策、用户偏好、约束条件、纠错记录tags文本逗号分隔的标签用于快速过滤importance整数重要性评分1-5影响检索排序created_at时间戳创建时间用于时间衰减last_used_at时间戳上次被召回的时间用于热度统计source文本来源标识比如哪个项目、哪次对话content 字段的写法有讲究。不要写成用户说他喜欢用 TypeScript而要写成项目偏好使用 TypeScript禁用 any 类型。前者是叙述后者是可直接执行的约束。Claude 读到后者能立刻理解并遵守读到前者还要再推理一层。importance 评分我建议这样定涉及安全、架构、核心业务逻辑的给 5 分一般技术选型给 3-4 分风格偏好给 2-3 分临时性约定给 1-2 分。这个分数在检索时会乘以权重确保重要记忆优先被召回。3.2 检索排序的权重计算检索不是简单地把匹配到的记忆全捞出来而是要排序后取 Top-K。排序公式我用的这套score similarity * 0.5 importance_norm * 0.3 recency * 0.2其中 similarity 是语义相似度0-1importance_norm 是重要性归一化后的值importance/5recency 是时间衰减因子用指数衰减计算recency exp(-λ * days_since_created)λ 取值建议 0.01 到 0.05 之间。取 0.01 意味着记忆半衰期约 70 天取 0.05 约 14 天。具体取多少看你的项目节奏快速迭代的项目用大一点的值长期稳定的项目用小一点。这三个权重的分配不是拍脑袋来的。similarity 占大头是因为相关性是第一位的不相关的记忆再重要也是干扰。importance 占三成是为了让关键决策不被淹没。recency 占两成是平衡既不让老记忆完全失效也不让新记忆无条件压过旧的。注意权重不要频繁调整。每次调完都要重新观察一段时间的效果频繁改动会让你无法判断到底是哪个因素在起作用。3.3 注入格式与 token 预算控制检索出来的记忆怎么塞给 Claude这里有个 token 预算的硬约束。我的做法是给记忆注入预留固定的 token 额度比如 2000 token然后按排序结果依次填充超了就截断。注入的格式我用的是结构化列表[项目记忆] - [技术决策] 使用 PostgreSQL 而非 MySQL理由是 JSON 字段查询性能更好 - [用户偏好] 代码注释用中文变量名用英文 - [约束条件] API 响应时间必须控制在 200ms 以内这种格式的好处是 Claude 能快速识别每条记忆的类型和内容不需要额外解释。方括号里的分类标签相当于给 Claude 一个提示告诉它这条信息的性质。如果记忆条目特别多可以按 category 分组每组内部按重要性排序。但要注意总量控制我实测下来超过 3000 token 的记忆注入边际收益就明显下降了反而挤占了正常对话的空间。还有一个技巧动态预算。简单问题给少一点记忆比如 500 token复杂问题给多一点2000 token。判断依据可以是用户消息的长度、是否包含多个问号、是否涉及具体代码等。4. 实操过程与核心环节实现4.1 环境准备与依赖安装先把基础环境搭起来。我用的技术栈是 Python SQLite sentence-transformers 做本地向量化。这套组合的好处是全部本地运行不依赖外部服务。pip install sqlite-utils sentence-transformers numpysentence-transformers 用来把文本转成向量模型选all-MiniLM-L6-v2就够了体积小约 80MB速度快语义效果对记忆检索这个场景完全够用。别一上来就上大模型检索场景不需要那么强的语义理解能力反而拖慢速度。数据库初始化import sqlite3 conn sqlite3.connect(claude_mem.db) conn.execute( CREATE TABLE IF NOT EXISTS memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, category TEXT NOT NULL, tags TEXT, importance INTEGER DEFAULT 3, embedding BLOB, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_used_at TIMESTAMP, source TEXT ) ) conn.execute(CREATE INDEX IF NOT EXISTS idx_category ON memories(category)) conn.commit()embedding 字段存的是向量序列化后的二进制检索时反序列化回来算余弦相似度。数据量小的时候几千条以内全量扫描完全没问题上万条再考虑加向量索引。4.2 记忆抽取的完整流程抽取流程分三步走规则粗筛、模型精炼、入库去重。规则粗筛用一个简单的函数实现import re TRIGGER_PATTERNS [ r(决定|选择|采用|放弃|改用), r(我喜欢|我习惯|我偏好|不要用|别用), r(必须|不能|限制|约束|要求), r(不对|应该是|改成|纠正), ] def rough_filter(text): for pattern in TRIGGER_PATTERNS: if re.search(pattern, text): return True return False这个函数把包含触发词的句子捞出来。注意是句子级别不是整段对话级别这样粒度更细。模型精炼这一步把粗筛出来的句子连同上下文一起送给模型让它输出标准化的记忆条目。提示词可以这样写从以下对话片段中提取值得长期记住的信息输出 JSON 数组每条包含 content、category、importance 三个字段。category 从 [项目背景, 技术决策, 用户偏好, 约束条件, 纠错记录] 中选择。importance 为 1-5 的整数。只提取真正有长期价值的信息临时性的、一次性的内容不要提取。 对话片段 {segment}入库去重很关键。同样的信息可能被多次抽取如果不去重数据库里会堆一堆重复条目检索时全冒出来。去重逻辑用向量相似度新条目跟已有条目相似度超过 0.9 就认为是重复跳过0.8 到 0.9 之间则更新已有条目的 last_used_at 和 importance取较高值。4.3 检索与注入的代码实现检索函数的核心逻辑import numpy as np from datetime import datetime def retrieve(query, top_k10, token_budget2000): query_vec model.encode(query) rows conn.execute(SELECT * FROM memories).fetchall() scored [] for row in rows: mem_vec np.frombuffer(row[embedding], dtypenp.float32) sim cosine_similarity(query_vec, mem_vec) imp_norm row[importance] / 5.0 days (datetime.now() - row[created_at]).days recency np.exp(-0.02 * days) score sim * 0.5 imp_norm * 0.3 recency * 0.2 scored.append((score, row)) scored.sort(keylambda x: x[0], reverseTrue) selected [] used_tokens 0 for score, row in scored[:top_k]: entry_tokens len(row[content]) // 2 # 粗略估算 if used_tokens entry_tokens token_budget: break selected.append(row) used_tokens entry_tokens conn.execute(UPDATE memories SET last_used_at ? WHERE id ?, (datetime.now(), row[id])) conn.commit() return selectedtoken 估算这里用了len(content) // 2这个粗略公式中文场景下大概 1 个 token 对应 1.5 到 2 个字符所以除以 2 是偏保守的估计宁可少算不多算。注入函数把选中的记忆拼成前面说的结构化列表格式然后作为系统提示的一部分或者首轮用户消息的前缀。4.4 与 Claude 对话流程的集成集成方式取决于你怎么用 Claude。如果是通过 API 调用那就在构造 messages 数组时把记忆注入到 system 字段或者第一条 user 消息里。如果是用网页版那就得手动把记忆粘贴进去或者用浏览器插件辅助。API 集成的伪代码def chat_with_memory(user_input): memories retrieve(user_input) memory_text format_memories(memories) messages [ {role: system, content: f你是一个有记忆的助手。以下是相关记忆\n{memory_text}}, {role: user, content: user_input} ] response call_claude_api(messages) # 对话结束后抽取新记忆 extract_and_store(user_input, response) return response这里有个细节抽取新记忆是在对话结束后异步做的不要阻塞主流程。可以用后台线程或者队列处理避免用户等回复的时候还要等记忆入库。5. 常见问题与排查技巧实录5.1 记忆召回不准的排查思路召回不准是最常见的问题表现是明明存了相关记忆但检索时没被选出来或者选出来的都是不相关的。排查第一步确认记忆确实存进去了。直接查数据库看 content 字段里有没有你要找的信息。有时候是抽取环节漏了根本没入库那检索再准也没用。排查第二步检查向量质量。把查询文本和记忆文本分别编码算一下余弦相似度。如果相似度低于 0.5说明语义匹配确实弱可能是模型不适合你的领域或者文本太短信息量不足。排查第三步看排序权重。有时候相似度最高的记忆因为 importance 低或者时间久被压下去了。临时把权重调成纯相似度排序看看结果对不对。如果对了说明是权重分配问题需要调整。排查第四步检查去重逻辑。如果去重阈值设得太低比如 0.7可能把相关但不重复的记忆误删了。把阈值调高到 0.95 试试。我遇到过一次典型问题项目初期存了一条数据库用 PostgreSQL后来项目迁移到 MySQL新记忆存进去了但旧记忆因为 importance 是 5 分检索时总是排在前面导致 Claude 一直以为在用 PostgreSQL。解决办法是给记忆加失效机制新记忆入库时检索同 category 下语义相似度高的旧记忆把它们的 importance 降权或者标记为 deprecated。5.2 常见问题速查表问题现象可能原因排查方法解决方案记忆完全没生效注入格式错误打印注入的完整 prompt检查格式是否符合预期召回内容不相关相似度阈值太低打印 Top-10 的相似度分数提高筛选阈值到 0.6重要记忆被忽略importance 权重太低检查排序公式权重提高 importance 权重旧记忆干扰新决策缺少失效机制查同 category 的冲突条目加 deprecated 标记响应变慢记忆条目过多统计数据库总条数加时间衰减清理老数据token 超限注入预算没控制统计注入文本长度设硬性 token 上限重复记忆堆积去重阈值太低查相似度 0.8-0.9 的条目提高去重阈值到 0.95.3 实操避坑心得坑一不要存原始对话。我一开始图省事把整段对话直接存进去结果检索时匹配到的是一大坨文本注入时又占大量 token还经常包含无关内容。后来改成结构化条目每条记忆精炼到一到三句话效果立竿见影。坑二importance 不要都打高分。如果所有记忆都是 5 分那 importance 这个维度就失效了排序退化成纯相似度。要敢于给临时性内容打低分让真正重要的东西凸显出来。坑三定期清理。我建议每个月做一次记忆库体检把 last_used_at 超过 90 天且 importance 低于 3 的条目归档或删除。记忆库不是越大越好精准才是关键。坑四分类标签要统一。category 字段的值必须从固定枚举里选不能今天写技术决策明天写技术选型。标签混乱会让按分类过滤的功能失效。坑五测试要用真实场景。别用你好今天天气这种测试数据要用你实际工作中的对话来测。只有真实场景才能暴露真实问题。提示记忆系统的调优是个持续过程不要指望一次配置就完美。建议每周花 10 分钟看看检索日志观察哪些记忆被召回了、哪些没被召回逐步调整权重和阈值。6. 进阶优化与扩展方向6.1 记忆的层级化组织当记忆条目超过几百条后扁平结构就不够用了。可以考虑引入项目维度和会话维度的层级。具体做法是加一个 projects 表记忆条目关联到具体项目。检索时先判断当前对话属于哪个项目然后只在该项目的记忆范围内检索。这样既缩小了检索范围提高了准确率又避免了跨项目记忆的干扰。再进一步可以给记忆加作用域字段全局记忆所有项目通用比如个人编码风格偏好、项目记忆特定项目专用、会话记忆当前对话临时用对话结束就丢弃。检索时按作用域优先级合并结果。6.2 记忆的自动摘要与压缩随着时间推移同一主题下可能积累了很多条细碎记忆。比如关于日志规范可能有五条不同时间存的记忆内容有重叠也有演进。这时候可以做主题聚类 摘要压缩把同一主题的记忆聚在一起让模型生成一条综合性的摘要记忆替换掉原来的多条。这样做的好处是减少条目数量降低检索噪音同时保留核心信息。压缩的触发条件可以是同一 tags 下的条目超过 10 条或者同一 category 下的条目总 token 超过某个阈值。6.3 与工作流的深度集成记忆系统单独用价值有限跟日常工作流结合才能发挥最大作用。几个集成点一是代码提交时自动抽取。从 commit message 和 diff 里提取技术决策和约束条件自动入库。这样记忆的更新跟代码演进同步不会脱节。二是文档变更时同步。项目 README 或设计文档更新后触发记忆刷新确保记忆里的项目背景跟最新文档一致。三是定期回顾提醒。每周生成一份本周新增记忆摘要让你快速回顾这周做了哪些决策、定了哪些规矩避免遗忘。这些集成的实现方式各不相同但核心思路一致让记忆的采集和更新嵌入到已有的工作流程里而不是依赖手动操作。手动操作一定会忘自动化才能持续。6.4 多模型适配的考虑虽然项目叫 claude-mem但记忆层的设计其实是模型无关的。存储、检索、注入这套逻辑换成别的模型一样能用。区别只在于注入格式和提示词措辞可能需要微调。如果你有多模型切换的需求建议把记忆层做成独立服务通过标准接口对外提供存记忆取记忆两个能力。上层应用不管是调 Claude 还是别的模型都走同一套记忆接口。这样记忆资产不会绑定在某个特定模型上迁移成本低。接口设计可以很简单POST /memory/store {content, category, tags, importance, source} GET /memory/retrieve?queryxxxtop_k10token_budget2000用 FastAPI 或 Flask 包一层本地跑个服务所有客户端都连这个服务。这样即使你换了电脑、换了编辑器记忆库还是同一份体验连贯。我在实际使用中最大的体会是记忆系统的价值不在于技术多复杂而在于持续使用和持续调优。一开始可能只有几十条记忆效果不明显坚持用两三个月积累到几百条你会发现 Claude 真的开始懂你了很多背景不用再重复交代协作效率的提升是实实在在的。最后分享一个小技巧每次纠正 Claude 的错误后顺手把纠正内容存成一条纠错记录类记忆这类记忆的 importance 给 4 分下次遇到类似场景它会主动避开同样的坑。