claude-mem:为Claude构建跨会话持久记忆系统 1. 项目概述与核心定位1.1 这个项目到底在解决什么问题claude-mem 这个标题从字面拆解来看核心落在两个词上claude和mem。mem 是 memory 的缩写也就是记忆。合在一起它指向的是一个非常具体且长期困扰大模型应用开发者的痛点——如何让 Claude 这类对话式 AI 拥有跨会话的持久记忆能力。我先把这个问题的背景说清楚。用过 Claude 或者其他对话式 AI 的人都有一个共同体验每次开启一个新对话模型就像失忆了一样完全不记得你上次跟它聊过什么。你上次告诉它你的项目架构、你的代码风格偏好、你正在排查的那个 bug 的上下文新开一个窗口全部归零。对于一次性问答这没什么但如果你把 Claude 当成一个长期的编程助手、写作搭档或者知识管理工具这种每次都要重新自我介绍的体验就非常割裂。claude-mem 要做的就是给 Claude 装上一个外挂大脑。它通过一套机制把对话中产生的关键信息持久化存储下来在后续对话中按需召回让 Claude 表现得像真的记得你。这不是简单的把聊天记录塞回上下文——那样 token 消耗爆炸且噪音极大——而是要有选择地提取、压缩、存储、检索。1.2 适合谁来研究和使用这个项目不是给纯聊天用户准备的。它的目标人群很明确重度 Claude 使用者每天用 Claude 写代码、做技术方案、管理项目的人跨会话记忆能省下大量重复沟通成本。AI 应用开发者想在自己的产品里集成记忆能力的工程师claude-mem 提供了一套可参考的架构思路。知识工作者用 Claude 做研究、写作、整理资料的人希望 AI 能记住自己的知识体系和偏好。对 RAG 和向量检索感兴趣的实践者claude-mem 本质上是 RAG 思路在个人记忆场景下的一个具体落地。如果你只是偶尔问问 Claude 天气或者让它翻译一段话那这个项目对你价值不大。但只要你有持续和同一个 AI 协作的需求它就值得你花时间研究。1.3 核心能力全景在深入细节之前我先把 claude-mem 这类项目通常具备的核心能力列出来让你有个整体认知。需要说明的是以下能力是基于一个成熟的对话记忆系统的常见实践总结出来的具体实现可能因版本而异能力模块作用关键技术点记忆提取从对话中识别值得记住的信息摘要生成、实体识别、重要性打分记忆存储把提取的信息持久化向量数据库、结构化存储、文件系统记忆检索在新对话中召回相关记忆语义相似度、关键词匹配、时间衰减记忆注入把召回的记忆喂给模型上下文拼接、prompt 工程、token 预算控制记忆管理增删改查和生命周期管理去重、合并、过期策略、隐私清理这五个模块构成了一个完整的记忆闭环。理解了这张表后面所有的技术细节你都能找到对应的位置。2. 记忆系统的整体架构设计思路2.1 为什么不能简单粗暴地存聊天记录很多人第一反应是记忆嘛把聊天记录存下来下次全塞回去不就行了这个方案在理论上可行实践中会立刻撞墙。第一个问题是token 成本。Claude 的上下文窗口虽然大但你把过去几十次对话全塞进去token 消耗是线性增长的费用会迅速失控。而且大部分历史信息对新对话是无关的属于纯噪音。第二个问题是注意力稀释。就算你不在乎钱把海量无关信息塞进上下文会显著降低模型对真正相关信息的注意力。这在业界被称为lost in the middle现象——上下文中间部分的信息容易被模型忽略。第三个问题是信息质量。原始聊天记录里充斥着好的嗯让我想想这类无价值内容真正有价值的信息比如我们的数据库用的是 PostgreSQL 15淹没在噪音里。所以 claude-mem 这类系统的核心设计哲学是记忆不是存储而是提炼。它要做的不是记住所有话而是记住关键事实和偏好并且在需要的时候精准召回。2.2 分层记忆架构一个设计良好的记忆系统通常采用分层结构我把它拆成三层来讲第一层工作记忆Working Memory这就是当前对话的上下文窗口。它容量有限但访问速度最快模型可以直接看到。claude-mem 需要管理的是哪些长期记忆应该被提升到这一层。第二层短期记忆Short-term Memory存储最近几次会话的摘要。比如你昨天和 Claude 讨论了一个 bug今天继续系统会把昨天的对话摘要作为短期记忆注入。这一层的特点是时效性强、细节适中。第三层长期记忆Long-term Memory存储经过提炼的、跨会话稳定的信息。比如你的技术栈偏好、项目背景、常用代码风格。这一层是持久化的通过向量检索按需召回。这种分层的好处是不同时效性、不同重要性的信息用不同的策略管理避免一刀切带来的成本和效果问题。2.3 存储选型向量数据库还是文件系统这是实操中必须做的第一个关键决策。我对比一下常见方案方案优势劣势适用场景纯文件JSON/Markdown简单、可读、易调试、零依赖检索能力弱、规模大了性能差个人使用、记忆量小向量数据库如 Chroma、Qdrant语义检索强、可扩展部署复杂、需要 embedding 模型记忆量大、需要精准召回混合方案兼顾可读性和检索能力实现复杂度高生产级应用我的经验是个人使用从纯文件起步遇到检索瓶颈再上向量库。很多人一上来就搭向量数据库结果发现自己总共就几百条记忆用 grep 都能搜出来纯属过度工程。claude-mem 如果面向个人用户大概率会提供轻量方案如果面向团队向量检索就是刚需。2.4 记忆的写入时机什么时候该把信息写入记忆这个决策点很关键。常见策略有三种实时写入每轮对话结束就提取一次。优点是及时缺点是频繁调用模型做摘要成本高。会话结束写入一次对话结束后统一提取。平衡了成本和及时性是主流做法。手动触发写入用户显式说记住这个。最省成本但依赖用户习惯。实际系统往往是组合策略会话结束自动提取同时支持用户手动标记重要信息。这样既保证了基础体验又给了用户精确控制的手段。3. 核心细节解析与实操要点3.1 记忆提取怎么判断什么值得记记忆提取是整个系统的入口也是最难做好的环节。提取质量直接决定了后续所有环节的上限。我常用的提取维度有这么几个事实性信息明确陈述的客观事实。比如我的项目用 TypeScript 5.3、部署环境是 Ubuntu 22.04。这类信息优先级最高因为它们是稳定的、可复用的。偏好性信息用户的习惯和喜好。比如我喜欢函数式写法、回复尽量简洁、代码注释用中文。这类信息决定了 AI 的交互风格价值极高。任务上下文正在进行的工作。比如正在重构用户认证模块、这个 bug 和缓存失效有关。这类信息时效性强需要标注时间。关系性信息实体之间的关系。比如项目 A 依赖库 B、同事张三负责后端。这类信息构建了知识图谱。提取的实现方式通常是让模型对对话做一次结构化摘要。prompt 大致长这样请从以下对话中提取值得长期记忆的信息按以下 JSON 格式输出 { facts: [客观事实列表], preferences: [用户偏好列表], context: [任务上下文列表], importance: 1-10 的重要性评分 } 只提取跨会话仍然有价值的信息忽略一次性的、临时的内容。 对话内容 {conversation}注意提取 prompt 里一定要强调跨会话仍然有价值否则模型会把大量临时信息也提取出来导致记忆库迅速膨胀且充满噪音。3.2 记忆去重与合并记忆库用久了必然出现重复和冲突。比如你三次对话都提到我用 PostgreSQL就会产生三条几乎一样的记忆。不去重的话检索时会召回一堆冗余信息浪费 token。去重的常见做法是写入前先做一次相似度检索如果发现已有高度相似的记忆就选择更新而不是新增。更新的策略可以是覆盖新信息直接替换旧的适合偏好类用户改主意了合并把新信息补充进旧记忆适合事实类信息更完整了并存都保留标注时间适合有冲突且无法判断哪个更新的情况冲突处理是个难点。比如你之前说用 MySQL后来说迁移到 PostgreSQL 了。系统需要识别这是状态变更而不是矛盾应该以最新的为准。实践中可以给记忆加时间戳检索时优先返回最新的。3.3 检索策略语义还是关键词检索环节决定了能不能在对的时候想起对的事。主流方案是语义检索也就是把记忆和当前对话都转成向量算余弦相似度取 top-k。但纯语义检索有短板。比如你搜数据库配置语义检索可能召回我用 PostgreSQL这条记忆但如果记忆里写的是数据存储层选型语义相似度可能不够高。这时候关键词检索BM25 之类就能补上。所以成熟方案通常是混合检索语义检索和关键词检索各取一批结果然后用 RRFReciprocal Rank Fusion之类的算法融合排序。这样既能抓住语义相关又不漏掉字面匹配。检索数量也要控制。top-k 取太大噪音多取太小可能漏掉关键信息。我的经验值是 k5 到 10 之间具体看记忆库规模。如果记忆库很大可以先粗排取 50 条再精排取 5 条。3.4 记忆注入的 token 预算召回的记忆最终要拼进 prompt 喂给模型。这里有个硬约束token 预算。你不能把所有召回的记忆都塞进去得给当前对话留足空间。我的做法是给记忆分配一个固定的 token 预算比如总上下文的 20%。然后按相关性排序从高到低填充填满为止。如果单条记忆太长就先做一次压缩再注入。注入的位置也有讲究。通常放在 system prompt 之后、用户消息之前用明确的分隔符标出来[相关记忆] - 用户项目使用 TypeScript 5.3 和 PostgreSQL 15 - 用户偏好函数式编程风格代码注释用中文 - 当前正在重构用户认证模块 [记忆结束] [用户消息] 帮我看看这段认证代码...提示记忆注入时一定要加清晰的分隔标记否则模型可能把记忆内容和当前指令混淆产生奇怪的行为。4. 实操过程与核心环节实现4.1 环境准备与依赖安装假设我们用 Python 来实现一个 claude-mem 的最小可用版本。先明确技术栈Python 3.10一个向量检索库轻量起步用sentence-transformersfaiss或者直接用chromadb一个本地存储SQLite 或 JSON 文件Claude API 的 SDK安装依赖pip install anthropic chromadb sentence-transformers如果你只是想快速验证chromadb是最省事的选择它自带持久化和检索不用自己搭向量索引。4.2 记忆存储的数据结构设计先定义记忆的数据结构。我用一个 dataclass 来表示from dataclasses import dataclass, field from datetime import datetime from typing import Literal dataclass class Memory: id: str content: str # 记忆正文 category: Literal[fact, preference, context] importance: int # 1-10 created_at: datetime updated_at: datetime source_session: str # 来源会话 ID embedding: list field(defaultNone) # 向量延迟计算这个结构里category决定了检索时的权重策略importance决定了记忆的保留优先级时间戳用于处理冲突和过期。4.3 写入流程的完整实现写入流程分四步提取、去重、存储、索引。第一步调用 Claude 做提取。这里要注意提取用的模型不需要太强用 Haiku 这类小模型就够了省钱又快import anthropic import json client anthropic.Anthropic() EXTRACT_PROMPT 从以下对话中提取值得长期记忆的信息。 只提取跨会话仍有价值的内容忽略临时性、一次性的信息。 按 JSON 输出 { memories: [ {content: ..., category: fact|preference|context, importance: 1-10} ] } 对话 {conversation} def extract_memories(conversation: str) - list[dict]: resp client.messages.create( modelclaude-3-haiku-20240307, max_tokens1024, messages[{ role: user, content: EXTRACT_PROMPT.format(conversationconversation) }] ) text resp.content[0].text # 容错处理模型可能带 markdown 代码块 text text.strip().removeprefix(json).removesuffix().strip() return json.loads(text)[memories]第二步去重。对每条新记忆先检索已有记忆如果相似度超过阈值比如 0.9就判定为重复走更新逻辑def dedupe_and_store(new_mem: dict, collection): results collection.query( query_texts[new_mem[content]], n_results3 ) # 检查是否有高度相似的 if results[distances] and results[distances][0]: top_dist results[distances][0][0] if top_dist 0.1: # 距离越小越相似阈值需按实际调整 # 更新已有记忆 existing_id results[ids][0][0] collection.update( ids[existing_id], documents[new_mem[content]], metadatas[{category: new_mem[category], importance: new_mem[importance]}] ) return updated # 新增 collection.add( ids[generate_id()], documents[new_mem[content]], metadatas[{category: new_mem[category], importance: new_mem[importance]}] ) return added注意相似度阈值这个参数非常依赖你的 embedding 模型和实际数据分布不要照搬别人的值。建议先跑一批真实数据观察相似记忆的距离分布再定阈值。第三步和第四步在 ChromaDB 里是合并的add和update会自动处理索引。4.4 检索与注入的实现检索环节我建议用混合策略。先做语义检索再做一次关键词过滤def retrieve_memories(query: str, collection, top_k: int 5): results collection.query( query_texts[query], n_resultstop_k * 2 # 多取一些后面重排 ) memories [] for i, doc in enumerate(results[documents][0]): meta results[metadatas][0][i] dist results[distances][0][i] # 综合打分相似度 重要性权重 score (1 - dist) * 0.7 (meta[importance] / 10) * 0.3 memories.append({content: doc, score: score, **meta}) # 按综合分排序取 top_k memories.sort(keylambda x: x[score], reverseTrue) return memories[:top_k]这里我加了一个重要性权重。原因是纯相似度检索会忽略这条记忆本身重不重要。一条 importance10 的偏好信息即使和当前 query 相似度中等也应该优先于一条 importance3 的临时上下文。这个权重比例0.7/0.3是我实测下来比较平衡的值你可以根据自己场景调整。注入环节把检索结果拼成文本def build_prompt_with_memory(user_message: str, memories: list) - str: if not memories: return user_message mem_text \n.join(f- {m[content]} for m in memories) return f[相关记忆] {mem_text} [记忆结束] [用户消息] {user_message}4.5 会话结束的触发逻辑最后一步是把整个流程串起来。会话结束时把完整对话传给提取函数然后逐条去重存储def on_session_end(conversation: str, collection): memories extract_memories(conversation) stats {added: 0, updated: 0} for mem in memories: result dedupe_and_store(mem, collection) stats[result] 1 print(f本次会话新增 {stats[added]} 条更新 {stats[updated]} 条) return stats这套流程跑通之后你就有了一个能用的记忆系统。实测下来几百条记忆的规模下检索延迟在几十毫秒级别完全不影响交互体验。5. 常见问题与排查技巧实录5.1 记忆库膨胀失控怎么办这是最常见的问题。跑了一两周发现记忆库里有几千条检索出来的全是噪音。原因通常是提取环节太宽松把临时信息也存了。排查思路先抽样看最近 50 条记忆统计有多少是一周后就没价值的。如果超过 30%说明提取 prompt 需要收紧。我的做法是在提取 prompt 里加一条硬规则如果这条信息只对当前这次对话有意义不要提取。另一个手段是加过期策略。给 context 类记忆设置 TTL比如 7 天到期自动归档。fact 和 preference 类不设过期因为它们长期有效。5.2 检索召回不相关怎么调如果发现召回的记忆和当前问题八竿子打不着按这个顺序排查排查项检查方法常见原因embedding 模型手动算两条相关文本的相似度模型不适合中文或领域不匹配相似度阈值看召回结果的距离分布阈值太松召回了低相关项记忆质量抽样看记忆正文记忆本身表述模糊语义不清query 构造看实际用于检索的文本直接把整段对话当 query噪音太大最后一条特别容易被忽略。很多人拿整个用户消息去检索但用户消息可能很长很杂。更好的做法是先用模型把用户消息压缩成一个检索意图再拿这个意图去检索。5.3 记忆冲突导致模型行为异常有时候模型会表现出矛盾行为比如一会儿用你旧的代码风格一会儿用新的。这通常是记忆库里同时存在新旧两条冲突记忆检索时都召回了。解决办法是引入冲突检测。写入时如果发现新记忆和旧记忆在同一主题上冲突比如都是关于数据库选型就标记旧记忆为已废弃检索时过滤掉。实现上可以给记忆加一个status字段检索时只查statusactive的。5.4 隐私与敏感信息处理记忆系统会长期保存用户信息隐私问题必须重视。我的建议是提取阶段就过滤掉明显的敏感信息密码、密钥、个人身份信息提供一键清空记忆的功能记忆存储加密尤其是云端存储时明确告知用户哪些信息会被记住提示在提取 prompt 里加一句不要提取任何密码、密钥、token 等敏感凭证信息能挡掉大部分意外泄露。5.5 性能优化经验记忆量大了之后检索会变慢。几个实测有效的优化分层检索先按 category 粗筛再在子集里做向量检索缓存 embedding同一条记忆的向量只算一次存起来复用批量写入会话结束时批量处理而不是每条都单独调 API异步提取提取不阻塞用户交互后台慢慢跑我在一个记忆量约 5000 条的项目里用分层检索把平均检索延迟从 200ms 降到了 40ms 左右效果很明显。6. 记忆系统的扩展方向6.1 从被动记忆到主动记忆现在的系统是用户问什么检索什么属于被动召回。更进一步的方向是主动记忆系统根据当前对话上下文主动判断这里可能需要某条记忆提前注入。比如用户提到上次那个 bug系统应该主动去检索相关的历史 bug 记录而不是等用户说清楚。这需要在对话过程中做意图识别判断哪些时刻需要触发记忆检索。实现复杂度更高但体验提升明显。6.2 记忆的可视化与管理界面纯命令行操作记忆库对普通用户不友好。做一个简单的 Web 界面能查看、搜索、编辑、删除记忆会大大提升可用性。技术上用 FastAPI 一个前端框架就能搞定核心是把记忆的增删改查暴露成 API。6.3 多用户记忆隔离如果要做成多人使用的工具记忆隔离是必须的。每个用户有独立的记忆空间检索时只查自己的。实现上就是在存储层加一个user_id维度所有查询都带上这个过滤条件。ChromaDB 支持 metadata 过滤做起来不难。6.4 记忆的导入导出用户可能想把自己的记忆迁移到别的工具或者备份。提供标准的导入导出格式比如 JSON是个好习惯。导出时注意脱敏导入时注意去重。我在实际使用中最大的体会是记忆系统的价值不在于技术多复杂而在于提取质量。同样一套检索和存储架构提取 prompt 写得好不好效果能差出好几倍。花时间打磨提取逻辑比折腾向量数据库选型划算得多。另外别一上来就追求大而全先用最小可用版本跑起来积累真实数据再根据实际问题迭代这样每一步优化都有据可依不会陷入过度设计的坑里。