
写完上一轮折腾脚本的总结我接着把claude-mem这个项目单独拎出来说。理由很简单——如果你长期使用 Claude 命令行工具做代码开发、写作或者资料整理很快就会撞上一个墙Claude CLI 的每一个会话都是全新的它对昨天你俩聊定的技术方案、项目约定、踩过的坑毫无印象。claude-mem就是为解决这件事而生的一个开源方案它的思路很直白在 Claude CLI 外面套一层记忆层每次对话结束后自动把对话内容整理、存储、建立索引等新一轮对话开始再把相关的历史记忆注入回去。这篇文章我会从使用者的角度把它的工作原理、搭建过程、实际效果和踩坑经验完整写出来希望对那些已经受够了反复解释上下文的人有帮助。1. Claude CLI 的金鱼脑问题为什么反复解释成了日常1.1 无状态会话的真实代价先说说我遇到的具体场景。我有一个中长期维护的项目目录结构、代码风格、接口规范这些信息零散地分布在文档和聊天记录里。用 Claude CLI 写代码时每次打开新会话都得重新自我介绍一遍我们项目用的是 pnpm不是 npm后端接口统一返回{ code, data, msg }这种格式测试文件放在__tests__目录下。头一两次还有耐心十次以后就变成一种隐性的时间黑洞。问题还不只是效率。人脑记不住模型更记不住会话一旦切断连带的风险是一致性崩塌。前一个会话里刚敲定的技术决策第二个会话因为缺乏记忆很可能给出完全相反的方案。比如昨天刚确认某模块要用 Promise 而不是回调今天新会话里它又给你写了一套 Callback 风格。这种漂移在大型项目里是会累积成技术债的。1.2 现有缓解方案为什么都不够彻底我试过几种常见的土办法。一种是手动维护项目笔记每次开会话前复制粘贴到输入框里。能解决一部分问题但完全依赖人工会话一多就懒得维护而且笔记内容大多是零散的摘要缺少可搜索的结构。另一种是把项目文档塞进 system prompt 或者做成CLAUDE.md文件。这个方案的问题是文档是静态的它只记录了你主动写进去的内容而对话中临时产生的约定、排除掉的方案、最终拍板的理由全部丢失。文档写不完也不应该承担这个任务。还有一种是用 IDE 插件或第三方客户端自带会话管理功能。它们解决的是保留历史窗口的问题但本质上还是让你手动翻找旧对话没做到自动提取—按需检索—主动注入这样的闭环。需求很明确我需要一个东西它像私人助理一样在后台记录每次沟通的结论下一次开口之前它已经默默把该记起的东西放在模型眼前了。2. claude-mem 的核心机制捕获、检索、注入三段式2.1 整体架构一句话描述claude-mem采用的是非常清晰的流水线架构。它不修改 Claude 本身而是在 Claude CLI 的外围挂了两套触点一套负责对话结束后的捕获一套负责对话开始前的注入。捕获到的内容经过清洗、结构化、向量化之后落进本地 SQLite 存储注入阶段则根据当前对话的上下文从存储里做语义相似度检索把最相关的历史片段带出来拼进这次对话的上下文里。用类比来说Claude CLI 是一个每天失忆的雇员claude-mem是一本会自动写日记的笔记本。雇员每天上班前笔记本会翻出这个人昨天交代过哪些事递给他。雇员永远不记得但笔记本记得。2.2 捕获阶段从原始会话日志到结构化记忆第一次看源码的时候我比较意外它没有通过代理或者 hook 消息流来捕获内容而是直接监听 Claude CLI 自己落盘的会话日志。Claude CLI 每次会话都会在本地日志目录写 JSONL 格式的对话记录里面每行是一个事件对象包含 type、timestamp、message 等字段。claude-mem做的就是增量监听这个目录读到新文件就把内容做一次处理。处理流程分四步。第一步去重同一个会话可能因为断线重连产生重复记录。第二步分块长对话不能整段存按轮次和 token 数切成块。第三步摘要每个块生成一个简短的摘要方便快速浏览。第四步提取关键信息用一次额外的 Claude 调用来抽取对话中的事实性记忆比如约定、决策、偏好、排除项格式化成结构化的 JSON 条目。这一步很关键因为原始对话里大量寒暄和过程性描述对长期记忆没有价值。真正值得记住的是那些具有持久意义的断言。比如我们把 npm 切换成 pnpm 了因为 monorepo 场景下安装速度更快这个就应该作为一条强记忆而今天天气不错就不需要。2.3 检索阶段语义相似度而不是字符串匹配记忆存好了怎么召回是决定体验的核心。如果用关键词匹配效果会很差。比如历史上说过我们用 pnpm 管理依赖新会话里用词换成包管理器关键词就完全对不上。claude-mem的做法是给每条记忆生成向量表示embedding新对话开始前把当前上下文也向量化然后做余弦相似度计算取出 TopK 条记忆。向量化这一步本地跑还是调 API取决于你的配置。我默认用的本地模型速度快、不产生额外费用缺点是对中文支持比商业 API 略弱后面踩坑部分会展开讲。本地模型跑完的向量存在 SQLite 里和原始 JSON 条目关联在一起。检索时还有一个相似度阈值参数低于阈值的直接丢弃避免无关记忆污染上下文。2.4 注入阶段让记忆无声无息地进入新会话注入方式有两条路径。一条是 hook 机制Claude CLI 支持配置 hooks在会话初始化事件里执行外部命令claude-mem把检索到的记忆通过 stdout 输出Claude CLI 会自动把它放进上下文。另一条是更朴素但兼容性更强的方案在新会话开头自动把检索结果追加到输入消息前面或者在CLAUDE.md里写入一条 prompt 指令让 Claude 在回答前先检查说明区。两条路径各有适用场景hook 方式更自动化文件方式更透明、更容易审查。注入的内容也不是简单把历史记录全丢进去。它有排序和压缩策略优先注入最近 24 小时内的记忆和相似度最高的 5 条每条记忆会被压缩成 2~3 行的自然语言摘要控制在 500 token 以内。这样不会给当前会话的上下文窗口造成太大负担。3. 动手搭建从零开始跑通 claude-mem3.1 安装与初始化我用的是 Go 写的二进制版本好处是单一文件、没有依赖地狱。安装入口可以根据你的系统选macOS 可以直接走 Homebrew tapLinux 和 Windows 下载 release 包解压就行。装好后先跑一次初始化命令它会在你的用户目录下创建记忆库目录和配置文件。初始化之后最重要的是确认三个路径Claude CLI 的会话日志目录claude-mem监听的数据源、claude-mem自己的存储目录默认在用户目录下的一个隐藏文件夹里面是 SQLite 数据库、日志、配置、以及 CLI 配置文件的路径。这三个路径不认对后面全白搭。3.2 注册 hooks让捕获和注入自动化如果你用的是新版 Claude CLI最省心的做法是在 CLI 配置文件里加两段 hooks 配置。一段挂在会话结束事件上触发记忆捕获另一段挂在会话开始事件上触发记忆注入。配置格式大致是{ hooks: { SessionEnd: [ { matcher: .*, command: claude-mem capture --from-last-session } ], SessionStart: [ { matcher: .*, command: claude-mem inject --session-context } ] } }这里有一个细节值得注意就是 hook 命令的执行是阻塞式的如果claude-mem跑得很慢会拖慢 Claude CLI 的启动和关闭。所以我建议把 capture 命令设置为后台异步执行并加上超时控制。比如claude-mem capture --async --timeout-ms 50003.3 第一次实测让 AI 记住项目约定跑通后的验证方式很有意思我建议新用户都按这个流程走一遍。第一步先在一个会话里明确说出一项约定比如以后所有数据库迁移文件都放在migrations/目录下命名格式是NNN_description.sql。正常结束会话。第二步等几秒让异步捕获任务完成。你可以查一下存储目录里的数据库文件大小确认有新数据写入。第三步另开一个新会话开头不主动提迁移文件的事只问一句类似我们项目里数据库迁移文件放哪这种依赖历史记忆的问题。如果配置正确Claude 应该能给出migrations/目录的正确回答而不是答非所问或者老实承认不记得。我第一次跑通的时候Claude 不仅答对了目录还补了一句按照之前的约定命名格式是三位数字开头加描述那一刻确实有一种它终于记住事情了的错觉。4. 记忆的数据结构、向量检索与清理策略4.1 本地 SQLite 里的记忆表claude-mem的数据存储没有采用复杂的分布式系统就是一个 SQLite 文件。我看到核心表有三张sessions、memories、embeddings。sessions记录每一次捕获的会话 ID、时间范围、token 数memories是提取出来的事实条目包含原始文本、摘要、来源会话 ID、创建时间embeddings表存储每条记忆对应的向量表示主键关联到记忆 ID。表结构大致是这个样子CREATE TABLE memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, content TEXT NOT NULL, summary TEXT, created_at TEXT DEFAULT (datetime(now)), updated_at TEXT, access_count INTEGER DEFAULT 0, last_accessed_at TEXT ); CREATE TABLE embeddings ( id INTEGER PRIMARY KEY AUTOINCREMENT, memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE, model TEXT NOT NULL, vector BLOB NOT NULL, updated_at TEXT DEFAULT (datetime(now)) );结构简单但够用。access_count这个字段很值得留意它是后面记忆衰减机制的底层数据。4.2 embedding 生成与相似度计算逻辑每次新捕获的记忆都要生成向量。默认使用本地模型运行时会加载一个小型的 embedding 模型大概几百 MB 内存速度在毫秒级。生成的向量以二进制形式存进 BLOB 字段。检索的时候先把当前上下文的文本也向量化然后遍历数据库里所有向量做余弦相似度计算。这听起来很笨但实际性能可接受因为个人使用的记忆量级通常只有几千条全量扫描也就几毫秒到几十毫秒。如果记忆量特别大还可以开启 SQLite 的向量检索扩展也能支撑十万级数量的场景。相似度有个阈值默认 0.3 左右低于这个值就认为无关不会出现在注入结果里。不同模型对相似度的分布有影响实测本地小模型打出的分数普遍偏低校准阈值的时候可以直接看claude-mem list --scores输出。4.3 记忆衰减旧记忆不等于要永久保留记忆会过时。半年前的项目决策现在很可能是一条误导性信息。claude-mem引入了冷热记忆的概念被经常访问的记忆热度和权重会上升长期不被访问的会慢慢降冷最后进入可清理的候选列表。用claude-mem cleanup --dry-run可以预览将被清除的记忆里面有明确的判断依据访问次数为 0、年龄超过 180 天、相似度历史命中率低于阈值。确认无误后去掉--dry-run真正执行。我个人的建议是每季度跑一次避免冷记忆积累成噪音。4.4 隐私边界设计隐私是我在考虑是否采用这个工具时最关心的问题。claude-mem的关键优势在于所有数据都落在本地 SQLite 文件里只有捕获阶段生成摘要和提取事实时需要调用一次 Claude API 来做文本归纳。如果你希望连这一步也留在本地可以用支持本地模型的配置项替换默认的 API 调用。存储目录建议手动加密macOS 可以直接用 APFS 原生加密卷Linux 可以用 LUKS这算是最稳妥的保险措施。还有一个小技巧如果某个会话包含特别敏感的内容在会话结束前发一条本次会话内容不需要记忆claude-mem会在捕获阶段识别到类似指令并跳过整个会话的存储。5. 实测与调优不同场景下的参数建议5.1 写代码场景要快、要准、要克制日常开发里记忆注入过多反而有害。试过一次把 TopK 调到 10上下文被各种历史片段塞得满满当当Claude 的回答反而变得琐碎。写代码时我的推荐配置是每次注入记忆不超过 5 条单条记忆不超过 200 token检索阈值调高一点只保留强相关的约定和决策。这种场景下最有价值的记忆类型是依赖选择我们用 pnpm、目录约定migrations 文件放哪、已知的坑这个 SDK 的 v3 版本 API 变了。过程性讨论、调试日志、情绪化的吐槽一一过滤掉不要留下。5.2 写作与研究场景需要背景连续性写长文或者做资料梳理时场景需求完全不同。文章的主题脉络、已经写过的段落结构、引用的数据来源都需要跨会话保留。这时我会把注入条数提高到 8~10 条阈值降低允许匹配到更泛化的记忆。有一个实用的用法是每天结束前让claude-mem对当天所有的对话生成一份今日结论速览第二天新会话开始前主动跑一次claude-mem recall --topic...。这相当于给自己做了一份可搜索的工作日志比翻聊天记录高效得多。5.3 核心参数速查表参数默认值推荐场景说明CLAUDE_MEM_SEARCH_LIMIT5代码开发场景 3~5写作场景 8~10控制注入记忆条数上限CLAUDE_MEM_MIN_SCORE0.30代码场景 0.35~0.40写作场景 0.25相似度阈值低于阈值丢弃CLAUDE_MEM_MAX_TOKEN_PER_ITEM200代码场景 150写作场景 300每条记忆压缩后的最大 token 数CLAUDE_MEM_ACCESS_DECAY_DAYS30保持不变超过该天数未访问的记忆热度开始下降5.4 多项目隔离别让项目 A 的记忆污染项目 B如果你同时维护多个项目记忆交叉污染是很恶心的问题。比如甲项目用的是 Express乙项目用的是 Fastify两个项目的对话都被同一个记忆库吸收新会话里 Claude 就可能拿到错误的技术栈上下文。claude-mem支持按工作目录自动分库默认逻辑是根据当前目录路径生成一个项目 ID不同的项目使用不同的记忆库文件。我建议在初始化时开启这个功能并且在每个项目的根目录放一个.claude-mem的配置文件显式声明项目名和历史窗口长度。这不是可选项是长期使用者的必需品。6. 踩坑记录排查链路与避坑心得6.1 记忆完全没生效从 hook 到日志的排查顺序有一次升级 CLI 后Claude 突然失忆了新会话里怎么问都答不上来。我先直接跑claude-mem recall命令发现数据库里有数据说明捕获没坏再跑注入命令也能输出结果。那问题就锁定在 hook 注册上。打开 CLI 配置文件检查新版 CLI 对 hook 的事件名做了变更SessionStart 改成了新的命名空间旧的配置被静默忽略了。这个过程给我的教训是永远先确认数据层是好的再排查配置层。顺序不能反不然很容易陷入是不是模型变笨了这种错误判断。6.2 语义误召回看似相关、实则无关的记忆语义检索不是万能的它经常被看似关联的文字欺骗。我有一次在新会话里提到分布式锁结果注入了一堆关于 Redis 过期策略的历史记录因为两者都涉及 Redis。原因在于嵌入层面对专业术语的区分度不够本地小模型尤其明显。一个有效的矫正手段是在对话结束后的摘要阶段显式要求摘要中包含领域标签然后在注入前过滤标签。claude-mem支持给记忆打标签比如#go#redis#锁检索时可以指定--require-tags redis。这能明显减少误召回。6.3 中文语境下的 embedding 相对劣势实测下来本地 embedding 模型在中文上的相似度区分度比英文差一截具体表现就是阈值不太好调。调低了掺入大量弱相关记忆调高了真正相关的中文记忆又被过滤掉。我的妥协方案是中文场景额外开启摘要改写把长句压缩成更结构化的短语再向量化同时对中文记忆的标签要求更严格尽量做到每条核心记忆都带项目级前缀。如果你对中文检索质量要求很高可以考虑换用更强的大型 embedding API但代价是每条新记忆都要多一步远程调用速度和隐私都会受影响看个人取舍。6.4 上下文窗口与 token 开销注入不是免费的每一次注入都会占用当前会话的上下文窗口。如果当前会话本身很长注入的记忆又偏多上下文就有被挤爆的风险这时候唯一的选择是提升上下文窗口档位成本随之上升。我的控制策略很简单把 CLAUDE_MEM_SEARCH_LIMIT 设小同时开启首次注入只注入摘要、用户追问时才展开详情的模式。这个功能用下来很实用相当于给记忆加了一个缩略层。一开始上下文里只有 5 行摘要需要细节时再让 Claude 去读完整的记忆块。最后再分享一个小技巧不要只在会话结束才想到捕获遇到重大决策时手动跑一次claude-mem capture --immediate把关键结论固化到记忆库里。终有一天你会感谢当时那个手快的自己。