给 Claude Code 加记忆

MCP 服务器、钩子,还有插件市场

Claude Code 会把每个会话都写到磁盘上,然后把下一个会话开成空的。你的那些工作还在,同一周里你在 Cursor、Codex,或任何当时开着的智能体里做的事,也都还在。

在这里,人们说的「记忆」其实是两件事

Claude Code 自己只有一种记忆面。包括你自己写的文件。你仓库里的 CLAUDE.md、对整台机器生效的 ~/.claude/CLAUDE.md、用来引入更多内容的 @path 导入、改它们用的 /memory,以及不用离开会话就能追加一行、以 # 开头的消息。它在每个会话开始时都会被整份读进去——这既让它适合放长期指令(构建命令、约定、不要动的目录),也正是它不适合放历史的原因。每一行都在每一轮里消耗上下文,而一条已经不成立的规则,和一条仍然成立的规则带着同样的权重抵达。CLAUDE.md 长起来之后讲的正是这个权衡。

人们说的另一件事,是你上周二那次会话:那个你早就诊断出来的报错、那条终于跑通的命令、那个你放弃某个思路的原因。没有任何东西会把它写进 CLAUDE.md,你也不会想让它待在那儿。它早就在磁盘上的转录里了,缺的是没有东西把它读回来。

Claude Code 已经有的部分

转录会以 JSONL 格式存放在 ${CLAUDE_CONFIG_DIR:-~/.claude}/projects/ 下,一个会话对应一个文件,细到每一次工具调用。没有任何东西读它,包括 Claude Code 自己的下一个会话。在Claude 的会话文件是怎么读的里有格式、坑,以及最后一次对着真实存储核对是哪一天。

接上召回

deja install claude-auto

这会写入三样东西:把 deja MCP 服务器写进 ~/.claude.json,在 ~/.claude/commands/deja.md 放一个 /deja 命令,以及在 ~/.claude/settings.json 里写入七个钩子:SessionStart,让新会话一开就带着前面几个会话已经定下来的东西;UserPromptSubmit,就着这次提问的内容去搜;PreToolUse,在动作发生之前说出某个文件或某条命令此前的结论,并把记忆放进它派生的子智能体的指令里;PostToolUse 和 PostToolUseFailure,用一个命令失败后紧接着跑过什么来回答这次的同一类失败;PreCompact;还有 SessionEnd,让刚关掉的那个会话能被下一个会话的 MCP 召回回答到。只装 MCP 服务器就运行 deja install claude-code。

这个仓库同时也是一个 Claude 插件市场——如果你更想用 Claude Code 装其他东西的方式来安装的话:

claude plugin marketplace add vshulcz/deja-vu
claude plugin install deja-vu@deja-vu

两种方法一起跑是安全的,不会变成两份:Claude 按名字索引 MCP 服务器,而我们交付的每份清单都把它叫作 deja,所以两边会合成同一个服务器,而不是把每个工具列两遍。

无论选择哪种方法都需要那个二进制,因为插件交付的是文件,不是运行时:

brew install deja-vu
# 或者
curl -fsSL https://raw.githubusercontent.com/vshulcz/deja-vu/main/install.sh | sh

那个钩子到底改变了什么

如果没有它的话,能不能召回取决于智能体自己决定去不去搜。钩子把这个决定提前了:会话一开就带着这个项目已经定下来的东西,于是第一轮不用花在重新推导上。Claude Code 还会在压缩之前把会话记录交给钩子,而那是唯一一道让工作上下文能被救回来的缝:deja 留下任务、结论、每条命令以及它是否通过、还有什么没结,并在摘要落地之后交还一次。压缩之后讲的是这一半。

和这个 harness 无关的部分

索引覆盖这台机器上每个智能体的转录——今天是四十二个,而且各有各的格式——所以在一个工具里找到的修法,能回答在另一个工具里提的问题,包括这一切装上来之前的会话。这就是选择索引、而不是选择边用边记的原因。

这里没有任何东西在写记忆,所以也就没有什么能凭空编进你的历史里;建立索引和检索都在本地,凭据在建索引时就被脱敏(隐私说明)。每次提问注入上限 1536 字节,每次工具调用约 4 KB——记忆要花多少 token 里有实测对比。

快速开始 · Claude 的会话文件是怎么读的 · CLAUDE.md 长起来之后