说实话我第一次意识到必须给AI助手做“记忆”这件事是被逼的。某天我开着十几个会话窗口做同一个项目那边刚在A窗口告诉助手“接口用REST风格别用GraphQL”转头在B窗口问代码方案它又给我推了一版GraphQL的设计——那一刻我盯着屏幕愣了三秒这到底是助手太笨还是我太天真后来我反应过来问题不在对话窗口而在于这些会话天生没有“跨窗口的长期记忆”。于是我花了两周时间给Claude做了一个叫“claude-mem”的持久化记忆模块让它能在不同会话间记住我的项目偏好、技术选型、甚至一些工作习惯。今天这篇就是把整个设计思路、核心代码、以及我在实测中踩过的坑完整拆给你看。如果你也遇到过类似的重复交代背景、反复纠正同一件事、或者觉得“它明明之前知道怎么换了个窗口就失忆”的情况那这篇文章就是给你准备的。我会从最底层的记忆分类讲起到存储选型、到检索注入、再到隐私和遗忘机制顺带附上可以直接抄的配置和代码。1. 记忆不是聊天记录claude-mem要解决的真实痛点1.1 为什么AI助手“聊过就忘”让人抓狂很多人以为AI助手有上下文窗口就等于有记忆。这是个非常普遍的误解。上下文窗口更像是一张“临时便签纸”它只保证你在当前这一段对话里它能想起之前聊过什么。一旦你关闭会话或者新建一个对话窗口这张便签纸就被清空了一切回到原点。这种“失忆”在短对话里没什么但在持续数周甚至数月的项目里就是灾难级的体验。举个例子我在维护一个内部工具库里面有三套不同的配置规范。每当我开新会话去问代码问题都得重新打一遍“我们用的构建工具是XX、代码风格是YY、发布流程是ZZ”。打字倒是小事问题是每次重新解释都可能产生信息偏差——漏一条、错一条AI给出的方案就完全走样。后来我干脆把项目背景写在一个固定的文本文件里每次手动粘贴。但手动粘贴也很蠢我明明可以一劳永逸地解决它。claude-mem的核心出发点就是把“临时便签纸”变成“可长期翻阅的笔记本”。它不是简单地把聊天记录存下来再翻出来而是要在合适的时候、用合适的方式、把合适的记忆重新送到AI面前。这里面每一项拆开来看都有不少门道。1.2 从“上下文窗口”到“持久记忆”的思维转变理解这个转变你只需要记住一个比喻上下文窗口是短期记忆claude-mem是长期记忆的“外接硬盘”。短期记忆的特点是容量小、速度快、随对话推进不断被新内容覆盖。你聊了一百轮之后最早几轮的内容基本就被挤出了窗口AI想“回忆”也回忆不起来了。长期记忆则不同它可以存几百条、几千条关键信息并且在你需要的时候把最相关的几条“调取”出来重新放回短期记忆里。所以这里的关键不在于“存了多少”而在于“取出的那一刻”。你存一万条笔记如果AI每次只能看到其中五条那五条选得准不准直接决定了它的表现。这就引出了我设计claude-mem时最重要的三条原则记忆必须结构化不能是一坨不可检索的日志文本检索必须带相关性排序不该把所有记忆都无脑塞给AI记忆必须能演化迭代过时的、冲突的要能覆盖或删除。这三条原则听起来简单真正落地时每一步都有坑。后面我会逐个展开。1.3 三类可记忆信息事实、流程与偏好在动手写代码前我先给自己做了一个“记忆分类学”。因为不是所有东西都值得长期记住存错了东西比不存更糟。我把记忆分成三大类这个分类后来成了整个系统的骨架第一类是事实型记忆包括具体的命令、配置、邮箱地址、API端点、项目目录结构等。这类记忆的特点是客观、准确、可验证比如“测试环境地址是192.168.1.88:8080”。它像你的通讯录改动频率低但记错了危害极大。第二类是流程型记忆描述“这件事应该怎么做”比如“发布前需要先跑shellcheck再打tag”“上线走灰度每次20%流量观察10分钟”。这类记忆本质是工作流和SOP它的价值在于让你不用每次重新解释规范。第三类是偏好型记忆包括你对代码风格、命名习惯、工具选择的个人倾向比如“配置用YAML不用JSON”“注释用中文但代码里的log用英文”。这类记忆最主观也最容易引发误判因为它往往是AI在对话中“猜”出来的一旦猜错就会持续输出你不想要的东西。我对这三类记忆的写入策略完全不同事实型记忆高置信度直接存流程型记忆需要二次确认再存偏好型记忆则设置更严格的触发条件避免把“随口一提”当“长期规则”。这个分类直接影响了我代码里的数据结构设计。2. 核心设计定稿记什么、存哪里、怎么取2.1 记忆条目统一抽象一条记录的完整字段想清楚分类之后我开始设计记忆条目的数据结构。最初我想弄得很复杂分了七八个字段结果越写越别扭——复杂的数据结构意味着后续的维护和迁移都痛苦。最后我砍到六个核心字段足够覆盖绝大多数场景{ id: m_3f9a2b, type: fact, content: 测试环境地址是192.168.1.88:8080, tags: [测试环境, 地址, 接口], importance: 0.8, expires_at: 2025-12-31T00:00:00Z, source_session: sess_8x2k9q }字段说明type标记这条记忆的类型fact / workflow / preference不同类型在检索时权重不同。content实际要记住的内容。tags关键词标签用于快速过滤。它和下面的向量检索互为补充标签负责精确向量负责模糊。importance重要度打分0到1。这个值会参与注入时的排序重要性越高的记忆越优先被送入上下文。expires_at过期时间。有些记忆是有时效的比如“临时调试用的token三天后失效”这类到期就该被清走。source_session记录来自哪个会话方便回溯查证。这个结构里最难定的是importance和expires_at。importance一开始我打算让AI自己判断后来发现它会莫名自信经常给“今天要喝美式”这种废话打0.99的高分。后来我改成“AI打分规则修正”凡是content里含有时效性词汇比如“这周”“临时”自动降低初始重要性并设置expires_at凡是含有关键词“永远”“必须”“不要”的提高重要性且不设过期时间。2.2 存储选型对比SQLite、JSON文件与向量库的取舍存储层我纠结了挺久。主流方案就那几种纯JSON文件、SQLite、以及专门的向量数据库。我做了个对比表方案优点缺点适合场景JSON文件零依赖读写简单人类可读数据量大了之后检索慢没有结构化查询能力个人轻量使用几百条以内SQLite单文件、无需独立服务支持SQL查询稳定可靠需要写SQL对文本相似检索无能为力中量级记忆管理配合代码做标签过滤向量数据库内置embedding检索语义匹配能力强依赖较重需要额外管理index和embedding模型大规模记忆上千条以上最终我选了“SQLite Python代码内做embedding检索”的组合。为什么没有上专门的向量数据库因为claude-mem的定位是“个人记忆模块”绝大多数人的记忆规模在几百到几千条之间这个量级用不着一套独立的向量库服务。相比之下SQLite单文件方案安装简单、备份方便——整个记忆库就是磁盘上的一个文件拷贝走就是备份省心得多。embedding检索这块我在每条记忆写入时调用文本嵌入模型把content转成向量存进同一个SQLite表里作为BLOB字段。检索时把用户当前的问题也转成向量然后做余弦相似度计算回到Python层排序取top-k。这样既享受了语义检索的好处又避开了引入重型组件的麻烦。2.3 记忆写入链路的三个关键判断存储结构定下来之后我梳理了“一条记忆从出现到入库”的完整链路。这不是简单地“听到就记”而是要让每一跳都有判断。我在代码里实现了三个关键判断第一个判断是“该不该记”。触发写入的时机有两种系统主动提取和用户主动命令。系统主动提取是指在对话中检测到关键信息比如出现了“密码是”“端点地址”“记住了”等信号时自动抓取用户主动命令则是用户对AI说“记住……”。前者会有误报所以我对自动写入的记忆都默认降低初始重要度宁可不够高不要瞎记。第二个判断是“是不是重复”。两条内容极其相似甚至冲突的记忆直接入库会让检索结果变乱。所以写入前我会先对全库做一次相似度检查如果已有的记忆与新内容相似度超过0.92就视为重复直接跳过如果相似度介于0.7到0.92之间则进入“记忆仲裁”逻辑——拿新内容去覆盖旧内容但要在旧记录上打一个“superseded_by”标记。这一步非常关键不然你会发现同一个事实被存了五六遍检索时AI会看到互相矛盾的版本。第三个判断是“该记在哪个分类”。这一步我交给了规则AI协作type字段由AI判断同时用关键词规则做兜底。例如content里出现“步骤”“流程”“应该”等词规则层会把type强制改成workflow出现“我喜欢”“我习惯”“不要”则判为preference。兜底规则的存在是为了防止AI抽风分类错误。3. 从零搭建可运行的记忆服务代码与配置3.1 最小目录结构与依赖清单到这你估计已经有点手痒了。我直接给出我当时搭建的最小可运行版本先把骨架跑通再谈优化。我的目录结构长这样非常朴素claude-mem/ ├── mem/ │ ├── __init__.py │ ├── store.py # SQLite存储层 │ ├── embedder.py # 文本向量化 │ ├── manager.py # 记忆管理主逻辑 │ └── mcp_server.py # MCP工具接入层 ├── config.toml # 全局配置 └── requirements.txt依赖方面用到的东西不多sqlite3Python内置负责持久化toml读取配置文件httpx调用本地或远程embedding服务的HTTP客户端mcpModel Context Protocol如果你要让Claude真正调用这个记忆服务这一步逃不掉。不想用MCP也可以退一步直接用标准输入输出的脚本方式但那样体验会差不少——Claude无法“实时”调你的记忆工具。后面我会细说。3.2 实现记忆管理器memo_add与memo_search核心逻辑我封装在manager.py里最核心的是两个方法memo_add和memo_search。我把关键代码贴出来并逐段解释。先看memo_adddef add(self, content: str, memo_type: str, importance: float): # 1. 生成embedding向量 vec self.embedder.embed(content) # 2. 重复性检查 similar self.find_similar(vec, threshold0.7) for hit in similar: if hit.similarity 0.92: logging.info(f重复记忆已忽略: {content}) return elif hit.similarity 0.7: self.mark_superseded(hit.id) # 3. 写入新记录 rec MemoryRecord( contentcontent, typememo_type, importanceimportance, embeddingvec ) self.store.insert(rec)这段代码的意图很明确每条记忆入库前先做相似度检查高于0.92直接丢弃0.7到0.92之间则标记旧记忆为“已被替代”。这里说句实在话0.7这个阈值是我反复调出来的太低了会把不同内容误判成重复太高了又拦不住轻微改写过的重复记忆。你在自己的项目里应该根据实际数据分布微调。再看memo_searchdef search(self, query: str, top_k: int 5) - List[MemoryRecord]: qvec self.embedder.embed(query) hits self.store.vector_search(qvec, top_ktop_k) # 过滤掉已过期和被替代的记录 alive [h for h in hits if not h.is_expired() and not h.superseded_by] # 按重要度加权排序 alive.sort(keylambda h: h.similarity * 0.6 h.importance * 0.4, reverseTrue) return alive[:top_k]检索这里我做了一个很多人会忽略的细节相关性和重要度的加权融合。纯粹按相似度排序有个问题——高相关但低价值的碎碎念会霸占前列。比如你问“怎么部署”一条相似度0.91的“我讨厌下雨天”不可能出现但一条相似度0.89的“上次部署的中间件版本是v2.1”和一条相似度0.85的“生产环境部署必须双人复核”会打架。前者更相关后者更重要。0.6比0.4这个比例体现了我更看重“是不是正在问的事”同时又不会让重要规则被淹没。3.3 接入MCP工具让AI会话自动读写模块有了接下来得让Claude真的能用它。MCPModel Context Protocol是当前比较通用的接入方式它把记忆模块包装成几个工具AI在对话中按需调用。我注册了三个工具memo_add(type, content, importance)写入记忆memo_search(query, top_k)检索记忆memo_forget(id)删除指定记忆。MCP server的核心代码骨架如下from mcp.server import Server, Tool server Server(claude-mem) manager MemManager() server.tool() def memo_add(memo_type: str, content: str, importance: float 0.5): 记住一条新信息 manager.add(contentcontent, memo_typememo_type, importanceimportance) return {status: ok, id: last_inserted_id} server.tool() def memo_search(query: str, top_k: int 5): 检索与当前问题最相关的记忆 results manager.search(query, top_ktop_k) return [{id: r.id, content: r.content, type: r.type} for r in results]接入MCP之后Claude在会话中会自动决定什么时候调用这些工具。它有时会在任务开始前主动查记忆有时会在对话中突然提取信息。我的实测是主动调用率大概在70%左右剩下30%需要你在对话里提一句“查一下记忆里的XX信息”它才会去检索。所以我在系统提示词里加了一条引导规则“处理任何涉及项目配置、代码规范、历史决策的问题前先调用memo_search。”这一句话让整体调用率提升到了90%以上。3.4 记忆注入模板的参数设计工具调用只是把记忆“拿”出来真正让它发挥作用还需要把它注入到Claude的上下文中。这一步我设计了一个记忆注入模板它不直接拼原始字符串而是先把检索结果格式化再放到系统提示词的一个专门的“记忆区段”。实际的模板长这样{ role: system, content: 以下是关于用户的长期记忆参考它们来回答当前问题。\n 如果记忆与当前对话冲突以记忆内容为准除非用户明确表示记忆已过时。\n 【记忆区段开始】\n - [事实] 测试环境地址是192.168.1.88:8080重要度0.8\n - [流程] 发布前必须执行shellcheck重要度0.9\n 【记忆区段结束】\n 请开始本轮对话。 }注意两个细节模板里明确写了“冲突时以记忆为准”这是为了避免AI在已有明确记忆的情况下还按对话里的临时说法走每条记忆后面都带着重要度给AI一个轻重参考。注入区段安排在系统提示词的最后部尽量靠近用户当前提问的位置——离得越近被上下文窗口“遗忘”的概率越低。关于记忆区段的token预算我的经验是控制在800个token以内。超过这个值AI反而会被一堆历史信息干扰生成变得僵硬、保守。你在调参时可以从600开始试根据效果微调。4. 检索比存储更考验设计注入优先级与上下文预算4.1 top-k检索与相似度阈值记忆库建好之后最难的反而不是“存”而是“取”。如果每次只取3条90%的关键信息都取不到如果每次取50条上下文直接爆炸。我最后把top_k默认定为5同时维护了一条动态调整规则当问题的复杂度上升比如用户问的是跨模块架构问题自动把top_k提到8而针对简单事实查询top_k降到3就够。有一个非常容易踩的坑是相似度阈值。embedding检索看似智能实际比较死板。同一个意思换一种说法余弦相似度可能只有0.6。我就遇到过我存了“登录接口走graphql”后来问“用户身份验证服务在哪个入口”两者字面几乎不重叠相似度只有0.55直接被过滤掉了。后来我把兜底方案设成“标签命中优先”——如果query里的关键词命中了某条记忆的tags字段即使嵌入相似度不够也强行放进候选集。4.2 上下文预算怎么算一个可以抄的公式关于上下文预算我后来总结出一个可以直接抄的计算公式记忆注入预算 min( fixed_cap, total_context_window * injection_ratio )fixed_cap是硬上限我取800tokeninjection_ratio是注入比例我取5%。为什么是5%如果你用Claude的长上下文模型比如200k窗口5%就是10000token理论上是够的但我不建议真用这么多。因为记忆区段是每轮对话都要加载的它会挤占实际回复和推理的空间。实测下来记忆区段占整个上下文窗口的3%-5%是安全区间下限是800token二者取小值。这套预算要做成动态计算而不是写死。因为不同模型上下文窗口不同同一模型不同配置也可能不同。我在代码里维护了一个budget_provider每次对话开始时读一次上下文窗口大小再乘上比例得到本次session的预算。4.3 新旧冲突时的记忆仲裁规则记忆的更新迭代是整个系统里最容易翻车的地方。我举个例子某个接口的地址从v1升级到了v2AI在旧会话里记住了v1。新会话里你告诉它地址变了那么旧记录还在记忆库里。下次检索时新旧两条记忆同时被命中AI就会陷入“到底听谁的”的困惑。我的解决方案是“三态仲裁”新记忆与旧记忆的相似度高且用户在新记忆里明确说“不用之前的”“换掉”“改成”则旧记录直接标记superseded新记忆重要度高于旧记录但内容没有明确冲突则两条都保留注入时优先展示重要度高的新旧内容只是平行补充互不冲突则两条都保留分别入库。这套仲裁逻辑在代码里就是一堆条件判断但规则写得越明确AI在混乱场景下的表现就越稳。你不能指望AI自己“悟”出仲裁规则必须写死。5. 实测效果与两个最容易翻车的坑5.1 几轮实测的效果对比光说不练假把式。我把claude-mem接入到自己的日常AI工作流里连续测了两周。我做的对照很简单同一系列任务一周不开记忆模块另一周开启对比两者的平均纠正次数和成果质量。结果挺明显的。开记忆前我每次开新会话都要花3到5轮“背景交代规范重申”偶尔还会因为漏交代导致方案返工开记忆后首轮回复就准确包含了我长期强调的几条规则。比如我常用的一个配置场景之前每次要单独说“不要用单引号缩进”“模板引擎要开严格模式”现在AI上来就懂。整体算下来单次任务平均节省了约40%的“复述时间”而且是越复杂的任务省得越多。不过时间节省只是表象更值钱的是决策一致性。跨会话的代码风格和接口规范保持一致之后我不用再靠人去记住“当时到底定了哪个方案”事实本身就存在记忆库里。5.2 坑一AI把“随口一提”当成“永久偏好”这个坑我在前文提过几次但实际踩的时候感受更深刻。有次我们在讨论临时工具有个选项“方便起见这次直接跳过校验”我随口说了一嘴。结果下一周我在写正式流程时AI居然引用记忆区里的“跳过校验”作为既定规范直接给我产出了一份没有验证环节的方案。问题出在写入阶段这类带有时效性、随口性的话不应该进入长期记忆。我加的修正规则是内容中若出现“这次”“暂时”“先”“随便”等临时性副词禁止自动写入preference类型最多写成临时fact并设置短expires_at比如24小时。同时引入了“写入确认”机制——不确定的一条记忆默认以“待确认”状态存下只在AI对话中给它一个提示“有一条不确定的记忆是否确认记入长期偏好”这样长期偏好库里的内容可信度大幅提升。5.3 坑二记忆越攒越多对话反而变笨第二个坑更反直觉。跑了两三周后记忆库涨到了400多条。我以为越多越好但实际测试时发现AI回复开始慢慢变得“啰嗦而保守”——它总想把自己的回答往记忆里的旧内容上靠反而对当前问题的新颖解法不够敏感。典型症状是你问一个全新的问题它非要先复述一遍“根据你之前的偏好……”然后才给答案。根因是检索层放入了太多“命中”的记忆挤占了上下文空间。解决办法有两条路一是收紧检索过滤条件把top_k从5降到4同时把阈值从0.7提到0.75减少边缘相关性记忆的混入二是定期清理过期和低重要度记录。我写了一个每七天跑一次的记忆瘦身脚本将所有importance低于0.5、且超过30天未被检索命中的记录归档到一个备份表而不是直接删——万一哪天要找旧信息还能翻出来。这样主记忆库保持在一个“精益”状态AI的回复也恢复到了干净敏捷的状态。6. 隐私边界与后续演进方向6.1 数据边界本地存储、敏感信息隔离与遗忘机制聊到最后必须说隐私。claude-mem本质上是在收集你与AI对话里的信息如果边界没定好就是给自己埋雷。我在设计时立了三条硬规矩第一条默认全本地存储。记忆库就是磁盘上的一个SQLite文件不主动上传任何第三方服务。接MCP时AI会话会临时读取记忆注入上下文但记忆本体始终留在本地。第二条敏感信息隔离。凡是content里命中密码、token、私钥等敏感模式一律不进正常记忆库而是进一个加密的隔离表并且这些记忆默认不参与检索注入。检索代码里加了个硬过滤if secret_flag: skip。这玩意儿宁可有漏网之鱼也不能拿密码去冒险。第三条遗忘机制不是可选项。每条记忆都有expires_at过期就自动降级。用户也可以主动用memo_forget命令删除。我还在配置里加了一个“记忆总条数阈值”比如超过1000条时强制触发一次重要度重排把低重要度、长期未命中的记录归档。这套边界做下来我对它的信任度才高到敢在真实工作流里长期开着。你也别嫌麻烦作为开发者先想想“如果这条记忆泄露出去我会不会慌”再决定要不要把那条记忆写进去。6.2 几个可以继续深入的扩展方向目前这个版本的claude-mem已经够我用但它还远不算完满。我自己觉得有三条值得继续走下去的路一是记忆的分层管理。现在所有记忆都在一个大池子里未来可以按项目、按主题拆分成多个记忆空间。比如“工作项目A记忆库”“个人学习记忆库”检索时带上空间限定隔离性会更好也不会出现工作规范混入闲聊场景的尴尬。二是记忆摘要的自动提炼。当某类记忆积累到一定数量可以定期用AI对它们做一次聚合摘要把零散细节提炼成一条高层次的规则。比如“测试环境地址”有三四处历史版本摘要后只剩一条当前版本既压缩了存储也减少了冲突。三是多模态记忆。现阶段只能记文本信息未来如果能把对话中的图片、代码片段、甚至终端输出的关键部分都结构化入库记忆的价值会大得多。但难度也会指数级上升——想想怎么给代码片段做embedding并保持可执行性就知道不是小事。最后如果你准备动手做类似的东西我能给的最实在的建议就一句话别一开始就追求大而全先把“记住一句话、查出一句话、忘掉一句话”跑通再慢慢往上加东西。我自己从有这个念头到做出第一个能用的版本只花了四个晚上。第二周接了MCP第三周开始日常使用现在它已经成了我工作流里离不开的一部分。这个项目的价值不在于代码有多精巧而在于它把“AI和你一起工作”这件事从“每次从零开始”变成了“越用越懂你”。这种体验一旦用上就回不去了。
阅读完成 · 觉得有帮助?