先讲个场景。如果你也是 Claude Code 用户,一定经历过这种挫败感:连续几个晚上改同一个项目,每次新开会话,它都像失忆一样,完全不记得你昨天已经排掉了哪些坑、定下了哪个方案。我踩过几次之后,干脆把 claude-mem 挂上去当它的“第二大脑”。
claude-mem 是一个专门为 Claude Code 设计的持久记忆层工具,原理并不玄乎:把会话里值得留下的关键事实、决策和偏好写进本地数据库,之后新会话按需把相关记忆自动取回来,让对话从“每次都从零开始”变成“接着上次往下干”。这篇文章适合所有被上下文丢失折腾过的开发者,我会从设计原理讲到实操落地,把安装配置、数据管理、调优和排错完整过一遍,尽量做到看完就能直接上手。
1. 先想明白:Claude 为什么需要一层“记忆”
1.1 无状态会话是 AI 编程工具的天然短板
Claude Code 本身能力很强,但它本质上是一个无状态的会话系统。每一个新会话开始时,上下文窗口里只有系统提示、工具定义和当前对话内容,上一会话里讨论过的技术选型、验证过的结论、踩过的坑,全部都会在会话结束后消失。这不是产品缺陷,而是大模型应用的架构常态——上下文就是一个受限于窗口大小的临时缓冲区,窗口一清,什么都不剩。
但编程恰恰是一项强连贯性的工程活动。今天的决策直接影响明天的工作,这周排除掉的错误方案不应该下星期再被提出来。很多人会用 CLAUDE.md 或者项目 README 手动维护背景信息,这确实能解决一部分问题,但静态文档有几个明显的毛病:信息更新滞后,今天刚确认的细节很难立刻写进文档;颗粒度太粗,不适合记录“这个模块的测试命令是 xxx”这种零散但关键的信息;查找成本高,文档一长,模型反而不知道该优先读哪一段。
1.2 记忆真正要解决的不是“存储”,而是“找回”
把信息存起来是最简单的一部分,难点在于三个问题:保存什么、什么时候保存、什么时候取出来用。如果一股脑把整个会话历史都存下来,下次检索时只会淹没在无关信息里;如果所有消息都注入回上下文,token 很快就会被撑爆,模型也会被大量噪音干扰。claude-mem 这类工具的核心设计目标,就是在这三个问题之间找到平衡点。
按照我的使用经验,真正值得长期保存的信息可以分成几类:项目级硬约束(比如“必须用 Node 18 构建”)、用户偏好(比如“测试文件放在 tests/ 目录,不要用tests/”)、技术决策及其理由(比如“把构建工具换成了 esbuild,因为旧配置无法处理 ESM 依赖”),以及会话摘要(这个会话主要完成了什么、还遗留了什么)。这些信息有一个共同特点:它们不依赖具体对话上下文,单拿出来依然有明确的参考价值。
1.3 和 CLAUDE.md 的定位差异
把 claude-mem 和 CLAUDE.md 放在一起对比会更清楚:
| 维度 | CLAUDE.md / 项目文档 | claude-mem |
|---|---|---|
| 更新方式 | 手动维护,容易滞后 | 自动捕获,会话中实时写入 |
| 信息粒度 | 偏宏观约定和规范 | 可以细到单条命令、单个文件路径 |
| 检索机制 | 模型自行阅读全文 | 按相关性自动检索并注入 |
| 维护成本 | 需要人工整理 | 需要定期清理和审核 |
| 典型场景 | 项目启动说明、编码规范 | 动态积累的决策、偏好、坑位记录 |
两者其实是互补关系:CLAUDE.md 相当于入职手册,claude-mem 相当于工作笔记。手册要精炼稳定,笔记可以琐碎且持续增长。我最开始只靠 CLAUDE.md,后来发现很多运行时才确认的信息根本来不及写进去,而 claude-mem 恰好补上了这块空缺。
2. claude-mem 的核心设计拆解:它凭什么能“记住”
2.1 存储层:SQLite 是一个相当务实的选择
claude-mem 的存储后端选型很值得聊。它没有用复杂的向量数据库,也没有引入独立服务,而是选择了 SQLite 这种嵌入式数据库。这个选择不是偷懒,而是充分考虑到了工具的定位:数据量不大,一个普通项目的有效记忆撑死也就几千条;使用场景是本地单机,完全不需要网络服务;写入频率不高,但对事务一致性有要求,不能出现写到一半崩溃导致数据损坏的情况。
SQLite 恰好满足所有这些条件。一个单文件数据库,备份就是复制文件,查询用标准 SQL,像 localStorage 一样简单可靠。我在实际使用中更喜欢把它放在独立的目录下管理,比如~/.claude-mem/,这样既不会污染项目目录,也方便统一备份。数据库表结构大致会包含记忆内容、类型、来源会话、时间戳、标签、项目标识这几个核心字段,通过项目标识隔离不同项目的记忆,保证互不干扰。
2.2 事件采集:hooks 是记忆的入口
claude-mem 能实现“无感记录”的关键,在于 Claude Code 的 hooks 机制。hooks 允许用户在特定事件发生时执行外部命令,例如工具调用前、工具调用后、会话开始、会话结束等。claude-mem 的思路很直接:在合适的 hook 点位上挂上自己的命令,让外部进程替它观察对话过程。
我通常会在两个位置配置 hooks。一个是PostToolUse,在 Claude 执行完 Read、Write、Edit 等工具之后,把这次操作涉及的关键信息写入数据库;另一个是PreToolUse,在 Claude 准备调用工具之前,先基于当前任务关键词去数据库里检索相关记忆,把命中的内容注入到后续上下文中。这样做的好处是:采集和注入都发生在工具调用层,而不是对话文本层,抓取的信息更结构化,注入的位置也更精准。
2.3 检索与注入:只给模型“够用”的记忆
记忆工具最怕的事情是把所有记忆一股脑塞回上下文。所以我特别关注 claude-mem 的检索策略,它一般会组合使用几种手段:关键词匹配(从当前任务里提取关键实体和路径)、标签匹配(如果记忆打了标签,就按标签过滤)、时间加权(近期的记忆优先级更高)。这些手段综合起来,能保证拿出来的都是与当前任务最相关的条目。
注入的位置同样有讲究。记忆内容不能随便混进对话历史里,那样会破坏上下文的连贯性。更稳妥的做法是作为环境信息或系统级提示的一部分,放在会话最前面,让模型把记忆当作背景知识。同时要设置单次注入的上限,我习惯限制在 5 到 10 条以内,宁可少给也不要给一堆无关内容把模型带偏。
3. 实操:把 claude-mem 接到本地 Claude Code 上
3.1 安装与初始化
先说安装。不同版本的 claude-mem 安装方式可能略有差异,我用的这套流程适用于当前主流分支,你操作时以项目 README 为准。最简单的安装方式是通过 npm 全局安装:
npm install -g claude-mem如果你不想用 npm,也可以直接把仓库 clone 到本地,然后用项目自带的安装脚本编译。安装完成后,执行初始化命令:
claude-mem init --storage ~/.claude-mem这条命令会做什么呢?它会在指定目录下创建 SQLite 数据库文件、初始化表结构、生成默认配置文件。初始化过程不需要交互式问答,跑完以后你可以检查~/.claude-mem/目录,正常情况下应该能看到一个.db文件和一个config.json。我习惯把数据库放在用户目录而不是项目目录,这样切换项目时记忆库不会丢失,也方便统一做备份。
3.2 配置 hooks:让工具在后台自动工作
安装只是第一步,真正让 claude-mem 跑起来的是 hooks 配置。Claude Code 的配置文件在~/.claude/settings.json,如果是团队项目,也可以放在项目的.claude/settings.json里。我在配置文件中加入 hook 注册:
{ "hooks": { "PreToolUse": [ { "matcher": "Read|Glob|Grep|LS", "hooks": [ { "type": "command", "command": "claude-mem recall" } ] } ], "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "claude-mem store" } ] } ] } }这段配置的意思是:当 Claude 准备读取文件、搜索代码时,先执行claude-mem recall把相关记忆注入;当 Claude 写完文件后,执行claude-mem store把这次操作的关键信息存下来。我特意只在Write|Edit之后做存储,而不是监听所有事件,这样能显著减少噪声,避免把无关操作也写进记忆库。
3.3 验证闭环:手把手造一条记忆
配置完成后,我建议先做一个闭环验证,确认整条链路是通的。验证方法很简单,分四步走:
第一步,打开一个会话,让 Claude 做一件可以被记住的事。比如告诉它:“本项目生产构建命令是npm run build:prod,以后提到构建就默认指这条命令。”会话中 Claude 如果执行了写操作,PostToolUsehook 就会触发,claude-mem store会尝试落盘。
第二步,正常结束会话。
第三步,新开一个会话,输入一句话:“我们项目的生产构建命令是什么?”注意不要给更多提示。
第四步,观察 Claude 的回复。如果它能准确说出npm run build:prod,说明记忆检索和注入都生效了;如果它答不上来,就要检查 hook 有没有触发、数据库里有没有写入记录。我可以通过一条命令直接查数据库:
sqlite3 ~/.claude-mem/claude-mem.db "SELECT * FROM memories ORDER BY created_at DESC LIMIT 10;"这条命令能最快确认问题出在采集端还是检索端。
3.4 多项目隔离与备份策略
我同时维护三四个项目,每个项目都使用 claude-mem,如果所有记忆混在一起会非常混乱。claude-mem 一般会通过项目目录或项目名做隔离,数据库里有一个类似project的字段用来区分来源。你可以给每个项目一个独立的数据库文件,也可以共享一个库但按 project 字段过滤。我采用的是后者,好处是备份只需要处理一个文件,坏处是数据量大了以后需要定期清理。
备份这块我用最简单的方案:写一个 cron 任务,每周把~/.claude-mem/目录打包一次扔到 NAS 上。因为数据库是单文件,打包特别方便,恢复也就是解压覆盖的事。我建议你至少保留最近一个月的备份,因为你永远不知道哪条记忆才是最重要的。我遇到过数据库文件被误删的情况,当时距离上次备份只有一天,但刚好丢了一条关于某模块历史决策的关键记录,那之后我就把备份频率提到了每天。
4. 调优:让记忆既“全”又“不吵”
4.1 控制单次注入的条数与 token 占用
记忆注入和上下文窗口之间的矛盾,是实际使用中最需要调优的地方。注入太少,模型记不住关键信息;注入太多,模型会被无关记忆干扰,还白白消耗 token。我一般把单次注入上限设在 5 到 10 条,每条约 50 到 100 个 token,这样单次注入最多占用 1000 个 token,对上下文窗口的影响可以忽略。
如果发现模型频繁被旧记忆带偏,优先检查是不是注入条数设得太高。有一个很典型的例子:我在一个项目里记录了十几条关于测试框架的偏好,结果某次会话里 Claude 同时读到了几条互相冲突的旧记忆,处理异步测试时反而犹豫了。后来我把同类记忆做了合并,并归一化为“统一使用 pytest + asyncio 模式”一条,冲突就消失了。合并同类记忆比单纯控制条数更有效。
4.2 标签体系:给记忆打上可检索的索引
原始的记忆条目如果没有标签,检索就只能依赖全文匹配,效果不稳定。我建议从第一天就给记忆设计标签体系。项目的标签不会太多,常见的几类就够用:
| 标签 | 适用内容 | 示例 |
|---|---|---|
stack | 技术栈相关的硬约束 | “后端不要引入 Django ORM” |
build | 构建、部署、脚本相关 | “CI 里必须使用 pnpm 而非 yarn” |
architecture | 架构决策与模块边界 | “支付模块不允许直接访问用户表” |
testing | 测试策略与命令 | “跑单元测试用 pytest -m unit” |
preference | 用户编码偏好 | “文件命名统一用 kebab-case” |
标签怎么进数据库呢?一般来说,claude-mem 提供在写入时附带标签的方式,或者你也可以通过配置文件维护一套自动打标签规则。我的做法是在记忆内容本里约束格式,例如在陈述句后面加[tag: build]标注,这样既不影响模型阅读理解,又能让检索时精确过滤。养成这个习惯之后,你会发现记忆的命中率明显提升,因为关键词匹配可以叠加标签条件,而不是全靠运气。
4.3 清理与遗忘机制
记忆是会过期的。半年前记录的“当前使用 Python 3.9”很可能已经失效,如果不清理,这些过期记忆会持续干扰判断。我建议每隔一段时间,或者每个大版本迭代结束之后,花几分钟审查一遍记忆库。
最简单的审查方法是导出全部记忆逐条过目。可以用 sqlite3 直接查询,也可以借助工具导出 JSON,然后按标签分组阅读。看到仍然有效的记录保留,看到已经失效或错误的记录删掉。claude-mem 一般也会提供删除命令,底层就是执行一句 SQL。不要高估自己的自律能力,我一开始也懒得清,后来把清理和版本发布绑定在一起,每次发新版本前必须过一遍,才慢慢养成习惯。
对于特别活跃的项目,还可以考虑时间衰减规则。也就是设置记忆有效期的上限,比如 90 天之前的某种类型记忆不再自动注入,除非手动标记为“长期有效”。这种机制能避免非常陈旧的记忆和新事实打架。我在几个长期项目里试过,效果比单纯靠人工清理稳定得多。
5. 实战排错:我从坑里爬出来的记录
5.1 最常遇到的坑:hook 命令没生效
刚开始配置 claude-mem 时,我遇到最多的问题就是 hook 命令没有执行。表现是记忆库一直是空的,Claude 的行为和没装工具时一模一样。排查思路其实很简单,三步走。
第一步,确认 hook 注册文件被正确加载。Claude Code 的配置文件路径容易搞混,~/.claude/settings.json是全局配置,项目.claude/settings.json是项目级配置,如果两边都配置了,项目级会覆盖全局级,要留意 matcher 是否冲突。
第二步,确认命令本身能在 shell 里正常执行。有时候 npm 全局安装的 bin 文件路径不在 PATH 里,hook 调用时找不到命令。我建议在配置里写绝对路径,比如把claude-mem换成~/.npm-global/bin/claude-mem,这一步能省掉很多麻烦。
第三步,看日志。Claude Code 遇到 hook 执行失败一般会在日志里记录错误,找到日志里最近的 hook 事件,基本就能看到报错原因。我用这个方法定位过 90% 以上的配置类问题。
5.2 记忆注入太杂,把主任务带偏
还有一个我踩得比较深的坑:记忆注入太杂,导致 Claude 在做主任务时分心。有一次我正在修路由 bug,结果 Claude 突然建议我重构整个目录结构,原因很简单——我注入了一条一周前写的“目录结构需要优化”的旧笔记,模型把它当成了当前任务的优先项。
这个问题的根源在于注入时没有做足够的上下文过滤。解决方法是双管齐下:一是收紧检索条件,把记忆中加preference标签的条目升级为默认不注入,只响应主动查询;二是降低注入条数上限,并调整时间加权参数,把长期未命中的记忆权重调低。经过这几项调整,Claude 的注意力终于回到当前任务上了。
5.3 脏数据导致的幻觉:记住错误信息
这个坑最隐蔽,也最容易让人怀疑工具没用。某次我在会话里随口让 Claude 记了一句“服务器地址是 192.168.1.10”,结果下一周新会话里,Claude 在处理部署任务时自动引用了这条旧记忆,但那时服务器地址已经改了,导致部署失败。问题不在 claude-mem 的代码,而在我自己——把临时信息当成长期记忆写进去了。
临时信息和长期记忆要分清。像“本次调试用的临时端口”“当前登录的会话 IP”这类一次性数据,根本不应该进记忆库。我后来在写记忆指令时养成了一个习惯:明确标注“这只是本次会话用到的临时信息,不要长期保存”。同时定期审核记忆库,遇到这种类型的脏数据立刻删除。毕竟 claude-mem 记录的每一句话,都会成为模型后续决策的依据,输入垃圾,输出也必然是垃圾。
5.4 数据库锁冲突与并发问题
当多个会话同时读写同一个 SQLite 数据库时,偶尔会遇到database is locked错误。SQLite 并发写的能力有限,而 hook 触发频率又高,冲突不可避免。我的解决办法是给 claude-mem 的写操作加一个简单的重试机制,遇到锁冲突时等待几十毫秒再重试,绝大多数情况下都能解决。
如果问题频繁,就要考虑是不是有写操作长时间占用事务。我记得有一次是 Claude 在编辑超大文件,PostToolUse触发后存储逻辑尝试读取整个文件内容做分析,把事务拖得很长,其他会话的写入全部排队。后来我把存储逻辑改成采样读取,只保留文件头部、尾部以及关键函数签名,锁冲突立刻就少了很多。
5.5 常见问题速查
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| 记忆库一直为空 | hook 未触发 / 命令路径错误 | 检查 settings.json 和 PATH,改用绝对路径 |
| Claude 答不出已记忆的信息 | 检索条件太严 / 注入条数为 0 | 加大注入条数上限,放宽匹配条件 |
| 上下文被无用记忆撑爆 | 注入量过大 / 标签太少 | 降低注入上限,建立标签体系 |
| 模型频繁提起过期结论 | 过期记忆未清理 | 定期审核,设置时间衰减 |
| 数据库锁错误 | 并发写冲突 / 长事务 | 加重试机制,缩短事务时间 |
| 不同项目记忆混乱 | 缺少项目隔离 | 按 project 字段过滤,或拆分数据库文件 |
最后再分享几个我自己的习惯
用 claude-mem 大半年,最大的感受是:工具本身不复杂,复杂的是怎么让记忆质量保持在高水位。我个人的体会是,它真正改变的是工作流的连续性——以前切换会话像“换人重聊”,现在更像“跨天续聊”。我越来越习惯在对话里明确说出哪些信息需要长期保存,哪些只是临时上下文,这比事后清理省力得多。
如果你也准备接入 claude-mem,我的建议是从小处开始:先在一个不紧急的项目里跑一周,观察它记录了什么、漏了什么、注入了什么,再逐步扩大使用范围。最后分享一个小技巧:把记忆审核做成每周的固定动作,哪怕只是花五分钟扫一眼新增记录,也比攒一个月再集中处理要轻松得多。