资讯详情

资讯详情

拆解 claude-mem:为 Claude Code 构建 AI 长期记忆层

你有没有遇到过这种场景昨天刚和 AI 助手讨论完数据库选型今天开个新会话想继续结果它一脸茫然连你用的是 PostgreSQL 都不记得还要从头解释一遍。我在过去一周里被这个问题折磨得够呛所以才认真把一个叫 claude-mem 的项目拆了个底朝天。它不是一个花哨的聊天插件而是一个典型的AI 长期记忆层在后台把会话沉淀成结构化记忆等下次对话时按需检索回来让 Claude Code 这类工具真正记住你。这篇文章我尽量不写官腔直接从架构设计、数据模型、接入方式和踩坑经验讲清楚适合正在做 Agent 记忆系统的人也适合被上下文窗口逼疯的重度用户。1. 先认清问题的本质对话窗口不负责记住你先说一个很容易混的概念上下文窗口不是记忆它只是工作台。LLM 能看到的只有你当前塞进 prompt 窗口里的内容一旦窗口滚动、会话关闭它对刚才说过的话就毫无印象。这就像一个人手里能同时拿的牌是有限的但牌桌旁边的抽屉才是真正的记忆库——而大多数 AI 应用目前只有牌没有抽屉。我在实际使用时被这个问题坑得特别明显。Claude Code 这类工具效率很高但我往往同一个任务要分好几天完成比如先调研技术方案第二天写代码第三天调 bug。每天新开会话后它不认识我之前的结论我得重新复述项目背景、技术约束、甚至自己的口味偏好。这个过程中浪费的 token 其实是小头真正难受的是决策断裂头天晚上确定用方案 A 的理由第二天它忘了又给我推荐方案 B。claude-mem 解决问题的思路非常朴素把记忆外置。它监听会话过程在合适的时间对对话做提炼把值得长期保存的信息抽取成结构化记忆存进本地数据库下一次对话时又通过检索把相关记忆注入回 prompt。这个外置记忆跟会话日志有本质区别我列个表维度原始会话日志claude-mem 式长期记忆存储内容逐字保存全部消息提炼后的事实、偏好、实体关系检索方式按时间翻页按语义相似度召回注入策略需要手动复制自动挑选 Top K 事实注入隐私控制难以细粒度管理可标记实体私密、过滤敏感信息适用场景审计、回溯让 AI 在后续对话中像老朋友一样工作换句话说它关心的不是上次完整说了什么而是哪些事值得一直记住。这是一条截然不同的技术路径也是我觉得这个项目值得拆解的原因。顺着这个思路往下走真正要回答的问题是会话进来之后怎么变成可检索的记忆2. 拆解 claude-mem 的核心数据流从原始会话到结构化记忆整个系统可以看成一条流水线原始会话 → 实体识别 → 事实/偏好抽取 → 向量嵌入 → 语义检索 → 上下文注入。每个环节都不复杂但串起来之后效果非常稳。2.1 四个核心对象实体、事实、偏好、会话我拆解这个项目的数据模型时发现它划分得非常干净。先有实体entity再挂事实fact和偏好preference会话conversation则是辅助溯源的东西。实体指的是记忆的主人公可以是一个用户、一个 GitHub 账号、一个 Slack 成员也可以是一个团队或一个项目。事实是关于这个实体的客观陈述比如用户的生产环境使用 PostgreSQL 15偏好是用户明确表达的做事倾向比如希望所有 SQL 变更先给 EXPLAIN 分析。会话则承担了出处的角色每条记忆都尽量保留 source_message_id一旦记忆内容有争议可以直接回到原始对话里去核对。这个拆分很有讲究。如果只存整段聊天文本虽然数据完整但后面检索时常常把一堆噪音捞出来如果只存一句话总结又丢失了这句话是谁说的、什么时候说的这些关键信息。实体事实偏好的结构让记忆具备图式化的特征后续做去重、过期、隐私控制都有抓手。我按这个思路仿写过一张最简表结构供参考CREATE TABLE entities ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, entity_type TEXT NOT NULL, -- user / github / slack / project is_private INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE facts ( id INTEGER PRIMARY KEY AUTOINCREMENT, entity_id INTEGER REFERENCES entities(id) ON DELETE CASCADE, content TEXT NOT NULL, source_message_id TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE preferences ( id INTEGER PRIMARY KEY AUTOINCREMENT, entity_id INTEGER REFERENCES entities(id) ON DELETE CASCADE, content TEXT NOT NULL, source_message_id TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE messages ( id TEXT PRIMARY KEY, session_id TEXT NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );实际项目里字段会比这复杂比如会加 metadata 存来源渠道、有效时间、更新次数等但骨架就是这个。有了这套结构记忆才能像数据一样被查询、修改、删除而不是一段躺在日志里等着发霉的文本。2.2 抽取记忆不是记流水账而是有损压缩如果直接把整段对话丢进 SQLite那不叫记忆库叫日志库。claude-mem 的做法是在后台调用一次 LLM让模型担任提炼者的角色。这个提炼过程本质上是一种有损压缩但损失的是废话保留的是语义。我在实现同类功能时用的 prompt 长这样你的目标是从对话中提取值得长期记住的信息。 只输出三类内容 1. FACT关于用户/团队的客观事实 2. PREF用户明确表达的偏好、规则或习惯 3. NONE无需长期记忆的内容。 输出格式 - 实体用户John - [FACT] 使用PostgreSQL 15作为生产数据库 - [PREF] 希望所有SQL变更先给EXPLAIN分析我刻意在 prompt 里加了两个约束一是每条内容必须有来源依据禁止模型脑补二是只输出对未来对话有用的信息而不是复述本次任务。这两个约束直接决定了记忆库的含金量如果模型把今天改了三个 bug这种一次性信息也存进去很快整个库都是噪音。这个抽取步骤通常是异步做的。用户在 Claude Code 里聊完一轮系统先把消息落盘然后后台任务慢慢读队列、调用小模型做抽取。因为不是每轮都要立刻出结果所以对交互延迟几乎没影响。我自己的经验是抽取模型用便宜的小参数模型就够没必要每次都调用旗舰模型省下的成本很可观。2.3 语义召回不靠关键词靠向量相似度记忆存好之后真正难的是怎么找回来。如果你是做后端开发的第一反应可能是 LIKE 查询或者干脆上一套全文搜索但事实类记忆没那么听话。用户今天说我用 Postgres明天问数据库连接池该怎么配这两个句子之间没有共享关键词靠 text search 根本匹配不上。claude-mem 在这里切向量路线。每条事实写入时会被嵌入模型转成一个向量存进专门的向量存储检索时把用户的当前问题同样转成向量按余弦相似度或者内积排序取出最相关的若干条。这个思路已经被知识库 RAG 验证过无数遍了关键在于工程实现上的取舍。向量存哪社区惯例是直接用向量数据库但 claude-mem 的定位是轻量级、本地优先所以它把向量存进了 SQLite 体系配合嵌入扩展做近邻查询。你可以把 SQLite 理解成一个自带文件柜的小仓库不需要额外起服务不容易丢备份就是复制一个文件。对于个人记忆这种量级SQLite 完全够用要是真到了百万条记忆的规模再把底下的存储替换成真正的向量数据库也不迟上层检索逻辑不需要大改。链路合起来就变成这样写路径是消息落盘 → 抽取记忆 → 向量入库读路径是用户提问 → 向量化 → 相似度 Top K → 拼进上下文。到这里记忆系统已经能转起来了接下来就是产品层面的接入方式问题。3. 三种打开方式CLI 安装、Docker 托管与 MCP 协议对接claude-mem 的部署形态比较灵活我用过的有三种CLI 直接装、Docker 跑独立服务、以 MCP 协议的方式嵌进各种支持 MCP 的客户端。三种方式本质上共享同一套记忆数据库区别只是接入路径不同。3.1 本地安装一条命令把插件注册进 Claude Code我当前用的版本是以 npx 方式分发的安装入口很轻大致是执行npx claude-memlatest install。注意这条命令做的事情不是下载一个二进制完事而是把 Claude Code 的生命周期钩子注册进去。简单说它会在 Claude Code 合适的位置写入配置让每次对话结束或特定节点触发时自动把会话内容交给后台处理。这有点像给编辑器装了个 on-save 钩子你正常写代码系统在后台帮你做格式化。装完之后重启一下 Claude Code就能在会话里感知到额外的记忆工具。如果你不想长期挂服务只是偶尔想看看记忆状态也可以直接跑带子命令的版本比如npx claude-memlatest run把 MCP 服务跑起来或者自己敲--help看当前版本支持哪些子命令。不同版本之间子命令名字略有变化这个倒不重要帮助文档永远是准的。3.2 Docker 部署把记忆服务隔离成一个独立进程我比较推荐在长时间使用时用 Docker 方式。官方镜像一直有维护拉下来的体积也不夸张关键是可以挂一个持久化数据目录把记忆库和开发环境彻底隔离开。docker run -d \ --name claude-mem \ -v claude-mem-data:/data \ -e CLAUDE_MEM_EMBEDDING_BACKENDlocal \ -p 3939:3939 \ ghcr.io/devxoul/claude-mem:latest这种方式的优势是干净。你不用在 Node 环境和虚拟环境里折腾依赖也不怕某个实验把记忆库搞崩记忆数据落在 volume 里想备份就复制 volume想迁移就换台机器重新挂载。我在公司内部给同事推广时也优先用 Docker因为每个人本机环境差异太大容器把变量都挡在外面了。唯一要注意的是Docker 容器如果要在本机外被其他机器访问需要自己控制好网络暴露范围。毕竟记忆数据是隐私敏感度比较高的东西不要让监听的端口随便暴露到公网。3.3 MCP 连接为什么协议化接入才是关键MCPModel Context Protocol值得单独拎出来说。因为 claude-mem 并没有把自己绑死在 Claude Code 一棵树上它对外提供的是一个 MCP 服务任何支持 MCP 的客户端都可以对接。你可以把 MCP 理解成 AI 应用里的 USB-C 接口。以前每个工具都要给客户端单独做适配现在大家统一用同样的一套协议服务端暴露一组工具客户端自动发现并调用。claude-mem 暴露出的工具大致包括记忆检索、记忆写入、实体列表、隐私标记等操作。客户端不需要理解背后的 SQLite 结构只需要按协议调用。这也意味着你在 Cursor、其他 MCP 客户端或者自己写的 Agent 框架里只要支持 MCP 客户端协议就能复用同一套记忆数据。这个设计思路特别值得学记忆能力应该是一种基础服务而不是某个产品独享的私货。4. 接入 Claude Code 后的真实体验Hook 归档、记忆检索与上下文预算控制工具讲完说说我把它接进 Claude Code 之后最真实的使用感受。很多项目演示起来很惊艳但实际用三天就会发现各种问题。claude-mem 属于那种第一天没感觉、第三天开始真香的类型。4.1 自动归档装了之后基本不用管我一开始最担心的是要我手动开开关关。结果它安装时注册的钩子把这块做完了对话进行过程中消息会被持久化一轮对话结束后后台异步做抽取和入库。整个过程对前台交互完全透明你继续正常聊天它默默在后台记。有一次我特意做了个实验开新会话不提任何背景直接问你还记得我那个项目的数据库选型吗大约半秒后模型通过工具检索返回了一段记忆内容然后基于那段记忆继续回答。那一刻确实有它终于长记性了的感觉。不过也要提醒一下异步归档意味着记忆入库存在延迟。我刚聊完一句话后立刻让新会话去检索偶像是空的但等个十几秒再看基本就齐了。对真实使用节奏来说完全可以接受。4.2 记忆注入不是把历史全塞进 prompt这是整个设计里最关键的一个点它没有把所有记忆都塞进上下文。很多初做记忆系统的人容易犯的毛病是反正存了干脆全注入给模型结果上下文窗口瞬间爆掉钱也烧得飞快。claude-mem 的做法是先由检索工具根据当前对话内容做语义匹配选出最相关的若干条事实再把它们作为工具返回结果注入。这个You ask, I recall的逻辑像极了一个文档管理员你要的是一份资料他绝不会把整个档案室给你搬过来。具体 Top K 是多少可以通过配置调实际使用中 K5 到 K10 都比较合理既够模型理解背景又不会喧宾夺主。4.3 手工维护一些信息才知道工具在哪真实使用中我也会定期手工检查记忆库。比如某条事实已经过期了项目已经从 PostgreSQL 换成 MySQL如果不删掉旧记忆模型反而会被误导。这时需要手动删除那条 fact 或者直接把对应实体标记成私密。我开始觉得这个遗忘功能才是记忆系统的魂魄。能记、能查、能改、能删才配叫记忆只能不断累加的话那只是回流的数据垃圾。玩了两周之后我的习惯是每周末花五分钟过一遍本周新增的实体和事实把过期或错误的一键清掉这个习惯让记忆库始终保持在比较清爽的状态。5. 自建一个浓缩版 claude-mem最小骨架也能跑通记忆闭环如果不想直接依赖现成项目自己写一个穷人版的 claude-mem 也只需要一个下午。核心就是三条代码路径抽取、存储、召回。这里我给出一个可复现的最小骨架原理上跟 claude-mem 同构。5.1 存储层SQLite 加一张向量表除了前面那四张基础表我还额外维护了一张 embeddings 表用来存事实对应的向量。向量在 SQLite 里的存法有两种一种是直接存成 JSON 文本另一种是用 sqlite-vec 这类插件建虚拟表。起点阶段我推荐直接存 BLOB 或者 JSON检索时在内存里算余弦相似度等数据量真的过万条了再考虑 sqlite-vec 的加速。5.2 三条核心代码路径路径 A抽取。拿一段对话调一次 LLM得到候选记忆列表。import json def extract_memories(messages: list[dict]) - list[dict]: prompt build_extract_prompt(messages) # 就是前面那段模板 response llm_complete(prompt, model便宜的小模型) return parse_memory_list(response)路径 B嵌入与入库。对每条记忆生成向量再写进 SQLite。def store_fact(entity_name: str, content: str, source_message_id: str): entity_id get_or_create_entity(entity_name) vector embed_text(content) # 调用嵌入接口 with db() as conn: conn.execute( INSERT INTO facts (entity_id, content, source_message_id) VALUES (?,?,?), (entity_id, content, source_message_id), ) fact_id conn.execute(SELECT last_insert_rowid()).fetchone()[0] conn.execute( INSERT INTO embeddings (fact_id, vector) VALUES (?,?), (fact_id, json.dumps(vector)), )路径 C检索。把当前问题嵌入和库里的向量做相似度排序挑出 Top K 事实。def recall(entity_name: str, question: str, top_k: int 5): query_vector embed_text(question) rows load_all_facts(entity_name) # 取该实体下所有 fact vector scored [ (cosine_similarity(query_vector, r[vector]), r[content]) for r in rows ] scored.sort(keylambda x: -x[0]) return [content for _, content in scored[:top_k]]第三步把它拼回 prompt 就行memories recall(John, 数据库连接池到底怎么配) augmented_prompt f 已知关于用户的长期记忆 {chr(10).join(- m for m in memories)} 请基于以上背景回答用户的当前问题。 这一段代码看起来简单但它就是一个记忆系统的全部骨架。真把它接进 Agent 后你会发现模型回答的稳定性明显提升很多背景不需要用户重复交代了。5.3 验证记忆闭环是否真的跑通联调的时候我用了一个很简单的验收标准第一天告诉模型我的生产库是 PostgreSQL第二天开新会话直接问我生产库用什么看它能不能答对。如果答对说明抽取、入库、检索、注入四大环节全部通畅。再用一个干扰问题验证相似度排序比如问我平时喝什么咖啡模型不应该把数据库选型那条事实捞出来。这个小验证你能跑通就说明你已经写出一个 mini 版 claude-mem 了。6. 实际使用中我的真实翻车记录与优化手段光看着架构完美是不够的真用一阵你就知道哪儿容易出问题。我觉得最典型的坑有三个记忆膨胀、隐私边界、成本失控。6.1 重复事实导致记忆膨胀第一个坑是同样的信息被反复抽取。比如用户连续三天都在调数据库连接池每次对话都会抽出用户在排查连接池问题这条事实三天下来库里存了三份近义词。这种重复不会让模型更聪明只会让检索结果里全是同质内容挤掉真正有用的背景。应对方法我在自己的骨架里加了去重逻辑每条 fact 写入前对实体内容做规范化可以用哈希比对也可以用一个小向量相似度阈值判断。如果新事实跟已有事实的相似度大于 0.95就选择更新原条目的时间戳而不是新建。本质上这向记忆合并服务——比无脑去重要高级也更符合真实记忆的沉淀过程。6.2 隐私边界默认本地存储但也要做好擦除既然这套系统会读取所有对话隐私就是一个绕不开的工程问题。claude-mem 的做法是默认本地存储把所有数据留在用户自己的机器上不从外部拿、不偷偷上传。这一点我在用之前就确认过否则不敢让它长期挂在工作环境里。但本地存储不等于绝对安全。抽取过程中如果对话里出现了 API Key、密码这类内容依然可能变成事实存进 SQLite被后续检索捞出来。我的习惯是在配置里开启敏感信息过滤把这类模式直接替换成占位符同时对特定实体标记为私密这样检索结果里也不会轻易带上它。另外提醒一点如果你在公司电脑上用或者打算把 Docker 服务暴露到局域网数据备份和访问控制一定要跟上。记忆库就是你的数字分身拼图丢了或泄了都不是小事。6.3 成本控制抽取模型选小的嵌入走批量第三个坑是成本。每次抽取都要调用一次 LLM初看单价不高但对话一多积少成多还是让人肉疼。我的优化方法是分三层第一层抽取用廉价小模型不让旗舰模型干这种粗活第二层做增量处理只有新增消息才进抽取队列历史消息不重复跑第三层嵌入调用尽量走批量接口把几十条文本一次发给嵌入服务减少请求次数。这样下来即使每天有两三百轮对话记忆层的成本也就占整体 API 消耗的一个零头。别让记忆系统变成吞金兽你好我好模型好。7. 想把它当第二大脑之前建议先想清楚的三件事最后聊几句我自己的思考。claude-mem 这类项目看起来很实用但AI 记忆这个方向有没有看起来那么美好其实取决于你对三件事的预期。第一件记忆不是状态是辅助上下文。哪怕记忆系统再完美也不该直接把检索结果当权威知识源。记忆只是给模型递小抄最终做判断的还是模型当前的推理能力。如果你遇到一个原本回答不好问题的模型不要指望加了记忆它就忽然变专家。第二件遗忘能力比记住能力更重要。一个只进不出的记忆库半年之后就是一团乱麻。我见过不少人在 AI 伴侣类应用里把所有对话全塞进去结果模型越聊越散因为太多旧记忆在互相干扰。给记忆设计过期、合并、删除机制甚至比设计存储和检索更考验工程素养。第三件数据归属权要想清楚。当记忆库变成你的外置大脑里面存的不只是事实还有偏好、习惯、人际关系。这些数据应该可以被你随时导出、完全删除而不是锁在某个服务商的数据库里。这也是我偏爱 SQLite 本地存储这类设计的原因它把控制权还给了用户让记忆工具回归工具本位。需要补充一句的是claude-mem 目前对 embedding 模型、检索阈值和上下文注入量的可配置性还比较开放你完全可以按照自己的使用习惯调参。我不建议一上来就贪多先把记住关键事实、忘记噪音这一个大目标做好。等这套机制成为习惯之后再逐步扩展实体维度、跨设备同步甚至多 Agent 共享记忆你会发现 Agent 真正开始像一个有连续经验的工作伙伴而不是每次见面都需要重新自我介绍。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →