claude-mem:为Claude构建跨会话长期记忆系统
发布时间:2026/10/26 9:29:33 锦皓数字建站

1. 项目概述与核心定位1.1 这个项目到底在解决什么问题claude-mem这个名字第一次看到的时候我以为是某个 Claude 的周边小工具实际用下来才发现它解决的是一个非常具体、非常痛的场景让 Claude 拥有跨会话的长期记忆能力。用过 Claude 的人都知道它的上下文窗口虽然大但每次开新对话之前聊过的东西就全没了。你昨天跟它讨论的项目架构、上周让它记住的代码规范、上个月梳理的业务逻辑新开一个会话全部归零。对于一次性问答这没什么但如果你把 Claude 当成一个长期协作的助手这个失忆问题就非常致命。claude-mem做的事情本质上是给 Claude 外挂了一套记忆的存储、检索和注入机制。它把你在对话中产生的关键信息持久化下来在后续会话中按需召回再以合适的方式塞回上下文里。听起来简单但要做好涉及的问题一大堆存什么、怎么存、什么时候召回、召回多少、怎么避免污染上下文每一个都是坑。1.2 适合谁来用这个项目不是给我就随便问问的轻度用户准备的。它真正能发挥价值的场景有这么几类长期项目开发者一个项目要跟 Claude 协作几周甚至几个月需要它记住架构决策、命名约定、踩过的坑。知识工作者把 Claude 当成第二大脑需要它记住你的偏好、工作习惯、常用术语。多会话工作流用户习惯把不同任务拆到不同会话里做但希望这些会话之间能共享背景信息。对上下文管理有洁癖的人不想每次都手动粘贴一堆背景资料希望有个自动化机制。如果你只是偶尔用 Claude 查个语法、写个正则那这个项目对你来说属于杀鸡用牛刀装了也是负担。1.3 核心能力速览在深入细节之前先把这个项目的核心能力列清楚方便你判断要不要继续往下看能力维度具体表现解决的痛点持久化存储对话关键信息落盘保存会话结束信息不丢失语义检索按相关性召回历史记忆不用手动翻历史记录上下文注入自动把相关记忆塞进新会话免去手动粘贴背景记忆管理增删改查、去重、过期清理避免记忆库膨胀失控多项目隔离不同项目记忆互不干扰防止上下文串味这张表里的每一行背后都有一堆设计取舍。接下来我会逐层拆开讲。2. 整体架构设计与选型思路2.1 为什么是外挂而不是改造一个很自然的疑问是为什么不直接改 Claude 本身让它自带记忆答案很简单——你改不了。Claude 是闭源服务你能控制的只有输入给它的上下文和它输出的内容。所以所有记忆方案本质上都是在输入侧做文章。claude-mem的架构思路我理解下来是这样的一个闭环对话产生信息 → 提取关键记忆 → 持久化存储 → 新会话时检索 → 注入上下文 → 对话继续这个闭环里最难的不是存储而是提取和注入这两个环节。提取决定了记忆的质量注入决定了记忆的可用性。存储和检索反而是相对成熟的技术用现成的向量数据库或者简单的文件存储都能做。2.2 存储层选型文件、数据库还是向量库这是第一个要做的关键决策。我实际对比过几种方案纯文件存储JSON/Markdown的优点是零依赖、可读性强、方便手动编辑。缺点是检索能力弱只能靠关键词匹配或者全量加载。适合记忆量小几百条以内、对检索精度要求不高的场景。关系型数据库SQLite的优点是结构化查询强、支持复杂条件过滤、单文件部署方便。缺点是语义检索能力弱只能做精确匹配或模糊匹配。适合记忆条目有明确结构化字段的场景。向量数据库Chroma、Qdrant 等的优点是语义检索能力强能按意思相近召回而不是字面相同。缺点是需要 embedding 模型、部署复杂度高、检索结果可解释性差。claude-mem这类项目通常会采用混合方案结构化字段时间、项目、类型用 SQLite 存语义向量用轻量向量库存检索时两路合并。这样既有精确过滤又有语义召回。提示如果你是自己复现这个项目初期建议先用 SQLite 关键词检索跑通流程等记忆量上来了再引入向量检索。一上来就上向量库调试成本会让你怀疑人生。2.3 记忆提取策略什么该记什么不该记这是整个项目里最考验判断力的地方。我的经验是不是所有对话内容都值得记。如果什么都记记忆库会迅速膨胀检索出来的全是噪音。我总结的提取优先级是这样的高优先级明确的偏好声明我喜欢用 TypeScript、项目决策这个模块用事件驱动、专有名词定义我们内部管这个叫 X。中优先级讨论过的方案对比、踩过的坑、修正过的错误认知。低优先级一次性的问答、临时的调试信息、闲聊内容。提取方式有两种主流做法一种是让 Claude 自己判断在对话中显式要求它输出值得记住的内容另一种是用规则或小模型做后处理提取。前者质量高但依赖模型配合后者自动化程度高但容易漏。2.4 注入策略塞多少怎么塞注入是另一个大坑。你把检索到的记忆一股脑塞进上下文结果就是上下文被历史信息占满当前任务反而没空间了。所以注入必须做预算控制。我的做法是给记忆注入设一个 token 预算比如总上下文的 15%。然后按相关性排序从高到低塞塞到预算用完为止。相关性排序可以用向量相似度也可以加权时间衰减越近的记忆权重越高。还有一个细节注入的位置。放在对话最前面system 附近还是紧贴用户当前问题我的实测是紧贴当前问题效果更好因为模型对靠近末尾的内容注意力更集中。但这也要看具体模型的行为不同版本可能不一样。3. 核心细节解析与实操要点3.1 记忆条目的数据结构设计一条记忆到底该长什么样这个设计直接决定了后续检索和注入的效果。我踩过的坑是一开始只存了一段纯文本结果检索出来没法过滤、没法排序、没法去重。后来我改成这样的结构{ id: mem_20240115_001, content: 用户偏好使用函数式编程风格避免 class 继承, type: preference, project: my-web-app, tags: [coding-style, typescript], created_at: 2024-01-15T10:30:00Z, last_accessed: 2024-01-20T14:00:00Z, access_count: 5, embedding: [0.123, -0.456, ...], source_session: sess_abc123 }几个字段的设计意图值得说明type字段用于分类检索时可以按类型过滤。比如当前在写代码就优先召回preference和decision类型。access_count和last_accessed用于热度排序。经常被召回的记忆说明它重要应该优先保留。source_session用于溯源。当记忆内容有疑问时可以回溯到原始会话查看上下文。embedding是向量检索的基础如果不用向量检索可以省略。3.2 去重与冲突处理记忆库用久了必然出现重复和冲突。比如你三个月前说喜欢用 React现在说项目迁移到 Vue 了这两条记忆是冲突的。如果不处理检索时可能同时召回让 Claude 无所适从。我的处理策略分两层第一层是写入时去重。新记忆写入前先做一次相似度检索如果发现已有高度相似的记忆就不新增而是更新已有记忆的last_accessed和时间戳。第二层是冲突标记。如果新记忆和旧记忆语义相反这个判断可以交给模型做就把旧记忆标记为superseded检索时默认不召回但保留在库里以备追溯。注意冲突处理不要做自动删除。我吃过亏有一次自动清理把一条重要决策记录删了后来想追溯都找不到。宁可留着标记状态也不要物理删除。3.3 检索的相关性算法检索质量直接决定注入质量。我试过几种相关性计算方式效果差异很大检索方式优点缺点适用场景关键词匹配快、可解释无法处理同义表达记忆量小、术语明确向量相似度语义理解强需要 embedding、可能召回无关记忆量大、表达多样混合检索兼顾两者实现复杂、需要调权重生产环境推荐时间衰减加权突出近期记忆可能忽略长期重要记忆项目快速迭代期我最终采用的是混合检索 时间衰减向量相似度占 70% 权重关键词匹配占 30%然后乘以一个时间衰减因子。衰减因子用指数衰减半衰期设成 30 天左右。这个参数不是拍脑袋定的是根据我自己的使用频率调的——我大概每两三天用一次30 天半衰期能保证一个月前的记忆还有一定权重但不会压过近期记忆。3.4 上下文注入的格式设计记忆检索出来了怎么塞给 Claude 也有讲究。我试过几种格式纯文本拼接最简单但模型可能分不清哪些是记忆、哪些是当前对话。带标记的 XML 标签比如memory.../memory效果好很多模型能明确识别这是背景信息。结构化 JSON信息密度高但模型解析起来可能出错。我现在的做法是用简洁的标记格式[相关记忆] - (偏好) 用户喜欢函数式编程风格 - (决策) 项目使用事件驱动架构 - (坑) 之前用 X 方案导致性能问题已改用 Y [记忆结束]这种格式的好处是模型一眼能看出这是记忆每条记忆有类型标签内容简洁不占太多 token。4. 实操过程与核心环节实现4.1 环境准备与依赖安装假设你要从零复现这个项目我按我的实操顺序讲。首先是环境准备这部分不复杂但容易出小问题。基础依赖就三样Python 3.10低于这个版本有些库不兼容、一个 SQLitePython 自带、一个 embedding 模型可以用本地的 sentence-transformers也可以用 API。pip install sentence-transformers sqlite-utils numpy如果你要用 API 做 embedding还需要装对应的 SDK。我建议初期用本地模型all-MiniLM-L6-v2这个模型体积小约 80MB、速度快、效果够用适合快速验证。4.2 记忆存储模块的实现存储模块是整个项目的地基。我用 SQLite 建了两张表一张存记忆主体一张存向量如果向量维度固定也可以直接存在主表里。import sqlite3 import json from datetime import datetime def init_db(db_pathmemory.db): conn sqlite3.connect(db_path) conn.execute( CREATE TABLE IF NOT EXISTS memories ( id TEXT PRIMARY KEY, content TEXT NOT NULL, type TEXT, project TEXT, tags TEXT, created_at TEXT, last_accessed TEXT, access_count INTEGER DEFAULT 0, status TEXT DEFAULT active, embedding BLOB ) ) conn.execute(CREATE INDEX IF NOT EXISTS idx_project ON memories(project)) conn.execute(CREATE INDEX IF NOT EXISTS idx_status ON memories(status)) conn.commit() return conn这里有几个细节值得说status字段用于标记active、superseded、archived三种状态检索时只查active。embedding存成 BLOB用 numpy 的tobytes()序列化读的时候再frombuffer()反序列化比存 JSON 数组省空间也快。4.3 记忆提取的触发时机提取什么时候触发这个设计影响体验。我试过三种时机实时提取每轮对话结束就提取一次。优点是及时缺点是频繁调用模型成本高、延迟明显。会话结束提取整个会话结束时批量提取。优点是成本低缺点是如果会话中途崩溃记忆就丢了。手动触发提取用户显式说记住这个才提取。优点是精准缺点是依赖用户习惯容易漏。我最终采用的是混合触发会话中如果检测到明确的偏好声明或决策语句实时提取会话结束时再做一次批量扫描补充遗漏。这样兼顾了及时性和完整性。4.4 检索与注入的完整流程这是整个项目的核心链路我把它拆成五步接收当前查询用户发来新消息先拿到这条消息的文本。生成查询向量用同一个 embedding 模型把查询转成向量。多路检索向量检索召回 top 20关键词检索召回 top 20合并去重。重排序与过滤按混合相关性分数排序过滤掉superseded状态的记忆取 top 5。格式化注入把 top 5 记忆按前面说的标记格式拼好插入到用户消息前面。def retrieve_and_inject(query, project, top_k5, budget_tokens800): query_vec embed(query) candidates vector_search(query_vec, project, limit20) candidates keyword_search(query, project, limit20) scored [] for mem in dedupe(candidates): if mem[status] ! active: continue score 0.7 * cosine_sim(query_vec, mem[embedding]) \ 0.3 * keyword_score(query, mem[content]) score * time_decay(mem[created_at]) scored.append((score, mem)) scored.sort(reverseTrue, keylambda x: x[0]) selected [] used 0 for score, mem in scored[:top_k]: tokens estimate_tokens(mem[content]) if used tokens budget_tokens: break selected.append(mem) used tokens update_access(mem[id]) return format_memories(selected)这段代码里time_decay和estimate_tokens是两个需要自己实现的辅助函数。time_decay用指数衰减estimate_tokens粗略按字符数除以 3 估算中文场景。4.5 记忆库的维护与清理记忆库不能只增不减否则检索质量会越来越差。我设了几条清理规则长期未访问超过 90 天没被召回、且access_count低于 3 的记忆标记为archived。低价值记忆type为misc且从未被召回的30 天后归档。冲突记忆被标记superseded超过 60 天的物理删除。这些规则我做成一个定时任务每周跑一次。归档和删除的比例我控制在每周不超过总记忆量的 5%避免清理过猛。5. 常见问题与排查技巧实录5.1 记忆召回不相关怎么办这是最常见的问题。你问一个前端问题结果召回了一堆后端架构的记忆。排查思路是这样的先看embedding 模型是否合适。有些通用 embedding 模型在技术领域表现一般可以考虑换成领域微调过的模型。我实测下来all-MiniLM-L6-v2在技术文本上够用但如果你的记忆涉及大量专业术语可能需要换更大的模型。再看检索权重是否合理。如果向量权重过高语义相近但主题无关的记忆会被召回。这时候提高关键词匹配的权重或者加一层项目过滤能明显改善。最后看记忆本身的质量。如果记忆内容写得太笼统比如讨论了架构问题那检索不准是必然的。记忆内容要具体包含足够的区分性信息。5.2 上下文被记忆占满怎么办这个问题我遇到过好几次。表现是注入记忆后Claude 对当前问题的回答质量反而下降了因为它被历史信息干扰了。解决办法有三个层次第一压缩记忆预算。把注入预算从 15% 降到 8%只保留最相关的 2-3 条。第二提高检索阈值。相关性分数低于某个阈值的记忆直接不注入宁缺毋滥。第三记忆内容精简。存储时就把记忆压缩成一句话而不是存一整段。我现在的记忆条目平均长度控制在 50 字以内。5.3 记忆冲突导致回答矛盾当新旧记忆冲突时Claude 可能会给出自相矛盾的回答。这个问题的根源是冲突记忆没有被正确处理。我的排查清单是这样的现象可能原因排查方法回答前后矛盾冲突记忆同时被召回检查是否有 superseded 记忆未过滤回答过时旧记忆权重过高检查时间衰减参数回答遗漏新记忆未被召回检查新记忆是否成功写入回答混乱记忆格式不清晰检查注入格式是否规范提示我建议在记忆写入时就做冲突检测而不是等到检索时才发现。写入时检测的成本低而且能保证库里的记忆始终是自洽的。5.4 性能瓶颈与优化记忆量上来之后检索会变慢。我实测下来1000 条记忆时向量检索大概 50ms10000 条时涨到 500ms 以上这个延迟在交互场景里已经能感知到了。优化手段有几个加缓存把高频查询的检索结果缓存起来分层检索先用关键词粗筛再对候选集做向量精排索引优化向量库用 HNSW 索引代替暴力检索。我用的是分层检索先关键词筛出 100 条候选再对这 100 条做向量排序。这样即使记忆量到 10 万条检索也能控制在 100ms 以内。5.5 记忆隐私与数据安全这个必须单独说。记忆库里存的是你的对话内容可能包含敏感信息。我的做法是本地存储优先能用本地 SQLite 就不用云服务。敏感信息过滤写入前扫描一遍检测到密钥、密码、个人信息就跳过或脱敏。加密存储如果记忆库要同步或备份用 SQLCipher 加密。定期审计每月导出一次记忆库人工过一遍删掉不该留的。这几条不是可选项是必选项。我见过有人把 API key 不小心记进记忆库后来同步到云端差点出事。6. 进阶玩法与扩展方向6.1 记忆的分层组织基础版记忆是扁平的所有记忆平铺在一起。进阶玩法是分层组织把记忆分成全局偏好、项目背景、任务上下文三层检索时按层分配预算。全局偏好层存的是跨项目通用的信息比如你的编码风格、沟通习惯。项目背景层存的是特定项目的架构决策、术语定义。任务上下文层存的是当前任务的临时信息任务结束就清理。这样分层的好处是检索时可以先加载全局偏好几乎总是相关再按项目过滤最后按任务匹配。命中率比扁平检索高不少。6.2 记忆的主动整理除了被动检索还可以让 Claude 主动整理记忆。比如每周让它读一遍最近的记忆合并重复的、更新过时的、提炼新的洞察。我试过这个玩法效果不错。具体做法是把最近一周的新增记忆喂给 Claude让它输出一份记忆整理报告包括合并建议、冲突标记、重要度重排。然后我人工确认后批量更新。这个流程把记忆库的维护从被动清理变成了主动进化记忆质量会随时间提升而不是下降。6.3 与其他工具的联动claude-mem的记忆库其实可以和其他工具打通。比如和笔记软件联动把重要记忆同步到 Obsidian 或 Notion形成知识库。和代码仓库联动把项目相关的记忆关联到具体文件或 commit检索时能定位到代码。和任务管理联动把任务相关的记忆关联到 issue做任务时自动加载背景。这些联动的核心思路是记忆不应该孤立存在而应该嵌入到你的工作流里。孤立记忆库的价值有限嵌入工作流后价值会放大好几倍。6.4 多模型共享记忆如果你同时用多个模型比如 Claude 和本地模型可以让它们共享同一个记忆库。这样你在 Claude 里建立的偏好本地模型也能用上。实现上需要注意格式兼容不同模型对上下文的格式偏好不同注入时要按模型适配。另外embedding 模型要统一否则向量空间不一致检索会失效。我现在的做法是记忆库统一但注入格式按模型分两套模板。Claude 用 XML 标记本地模型用纯文本前缀。这样两边都能正常工作。7. 我踩过的坑与实操心得7.1 不要一开始就追求完美我最初设计这个项目时想一次性把所有功能都做全向量检索、冲突检测、分层组织、自动整理。结果做了两周一个能跑的版本都没有。后来我砍掉所有花哨功能只做最核心的存储 关键词检索 注入一天就跑通了。跑通之后再逐步加功能每加一个都验证效果。这个顺序很重要先跑通再优化不要反过来。7.2 记忆质量比数量重要我一度觉得记忆越多越好什么都往里塞。结果检索出来的全是噪音Claude 的回答质量反而下降了。后来我定了个规矩每条记忆写入前都要问一句这条信息三个月后还有用吗。如果答案是可能没用就不写。这个规矩让我的记忆库从 5000 条精简到 800 条但检索质量提升了一大截。7.3 参数要按自己的使用习惯调网上很多教程会给一套推荐参数但那些参数是别人的使用习惯调出来的。我一开始照搬效果一般。后来我记录了自己两周的使用数据发现我的会话间隔平均是 2.5 天于是把时间衰减半衰期从默认的 7 天调到 30 天效果立刻好了。参数没有最优解只有最适合你的解。花点时间记录自己的使用数据比照搬别人的参数有用得多。7.4 一定要有回滚机制记忆库出问题的时候如果没有备份你会很痛苦。我有一次批量更新记忆脚本写错了把 200 条记忆的type字段全改成了同一个值检索直接废了。从那以后我加了两个机制每次批量操作前自动备份操作后做一致性校验。备份就是复制一份 db 文件校验就是检查关键字段的分布是否正常。这两个机制救过我好几次。7.5 定期人工审查不可省自动化再好也需要人工兜底。我每月会花半小时导出记忆库快速扫一遍。这个过程能发现很多自动检测发现不了的问题语义重复但字面不同的记忆、已经过时但没被标记的记忆、写得太模糊没法用的记忆。这半小时的投入换来的是下个月检索质量的稳定。我觉得非常值。8. 快速上手的配置清单如果你看到这里想动手试试我把最小可用配置列一下照着做能少走弯路。第一步环境python -m venv venv source venv/bin/activate pip install sentence-transformers sqlite-utils numpy第二步初始化数据库用前面 4.2 节的init_db函数建表确认memories表和索引都创建成功。第三步配置 embedding 模型from sentence_transformers import SentenceTransformer model SentenceTransformer(all-MiniLM-L6-v2) def embed(text): return model.encode(text, normalize_embeddingsTrue)第四步跑通最小闭环先不接 Claude用命令行手动测试写入一条记忆然后查询看能不能召回。这一步跑通了再接 Claude 的对话流程。第五步接入对话流程在每次调用 Claude 前先执行检索和注入在每次收到回复后执行记忆提取。这两个钩子接上整个闭环就通了。关键参数初始值按我的经验这些值适合大多数场景参数初始值调整方向注入 token 预算800上下文紧张就调小检索 top_k5召回不准就调小漏召回就调大向量权重0.7语义召回差就调低时间衰减半衰期30 天使用频繁就调短相似度去重阈值0.85重复多就调低这套配置我用了大半年中间只微调过时间衰减和 top_k 两个参数。对于大多数个人使用场景这个起点足够用了。最后分享一个我最近才想明白的点记忆系统的价值不在于记住多少而在于在对的时候想起对的事。我早期追求记忆库的规模后来发现真正有用的记忆可能就那几十条关键是它们能在正确的时机被召回。所以与其花精力扩充记忆不如花精力优化检索和注入。这个认知转变之后我的整个项目方向都清晰了很多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。