claude-mem:用一个Claude监视另一个Claude,值得吗?
最近在调研AI编程助手的记忆持久化方案时,看到了claude-mem这个项目。它试图解决一个真实的痛点:Claude Code每次会话结束后丢失所有上下文。但仔细研究源码后,我发现它的架构设计存在一个根本性的矛盾——为了记住东西,它付出的代价可能比遗忘本身更贵。
claude-mem做了什么
一句话概括:它在你用Claude Code编码的同时,启动一个并行的Claude子进程作为”观察者”,实时监视你的每一步操作,将观察结果压缩成结构化记录,存入SQLite和ChromaDB,下次开会话时自动注入相关历史上下文。
架构长这样:
1 | 用户编码会话(主Claude进程) |
亮点在哪
公平地说,这个项目有几个设计是不错的:
1. Observer-Only的安全边界
观察者Agent显式禁用了全部12个工具(Bash、Read、Write、Edit、Grep、Glob等),确保记忆Agent不会对你的项目产生任何副作用。这是防止”Agent套娃”失控的关键:
1 | // SDKAgent.ts:55-68 |
2. 渐进式信息披露
查询端的3层搜索设计确实精巧:
search→ 紧凑索引(~50-100 tokens/条)timeline→ 时间线上下文get_observations→ 完整详情(~500-1000 tokens/条)
先过滤再获取细节,查询时能省约10倍token。
3. 优雅降级
所有Hook在Worker不可用时返回exit 0,永远不阻塞用户的编码流程。隐私标签(<private>、<system-reminder>等)在Hook层就被剥离,数据进入Worker之前已经过滤。
4. 多IDE适配器模式
通过PlatformAdapter抽象,用一个适配器接口支持了Claude Code、Cursor、Gemini CLI、Windsurf、Codex CLI等六个平台。新增IDE只需要写一个适配器。
核心问题:写入端是个token黑洞
claude-mem宣传的”10x token节省”只是查询端的故事。但写入端呢?
看SDKAgent.ts的核心流程:每次你在Claude Code里执行一个工具调用(Bash、Read、Write、Edit、Grep……),PostToolUse Hook就会把tool_name、tool_input、tool_response全部捕获,JSON序列化后发给观察者Claude子进程:
1 | // prompts.ts:114-123 |
每一次工具调用都是一次API请求。一个正常的编码会话可能有几十上百次工具调用。
更要命的是,SDK Agent保持会话上下文,这意味着:
- 第1次观察:input tokens = 系统prompt + 观察1
- 第2次观察:input tokens = 系统prompt + 观察1 + 响应1 + 观察2
- 第N次观察:input tokens = 系统prompt + 全部历史 + 观察N
Input tokens线性增长。它自己也知道这是个问题,所以代码里做了token追踪:
1 | // SDKAgent.ts:210-232 |
还把discoveryTokens存进了每条observation——说明作者知道这是个ROI问题。
具体成本估算
假设一个中等活跃的编码会话:80次工具调用,每次观察prompt约2000 tokens,观察者响应约500 tokens。
| 阶段 | Token消耗 |
|---|---|
| Init prompt | ~1500 |
| 80次观察(累积上下文) | ~1500 + 2500×1 + 2500×2 + … + 2500×79 ≈ 7.9M input |
| 80次响应 | ~40K output |
| Summary | ~3K |
| 总计 | ~8M tokens |
这还没算ChromaDB那边的embedding成本(虽然chroma-mcp用本地模型,但也要CPU/内存)。
为了”记住”这个会话做了什么,你可能额外花了8M tokens。而下次查询时省的那点token,可能只有几千。
对比:graphify的做法
graphify试图解决类似的”理解代码库”问题,但写入端的设计思路完全不同:
| 维度 | claude-mem | graphify |
|---|---|---|
| 代码提取 | 全靠Claude做语义压缩 | tree-sitter AST解析23种语言(免费) |
| 写入触发 | 每次工具调用实时触发 | 一次性扫描,增量更新 |
| 音视频 | 不支持 | faster-whisper本地转录(免费) |
| 向量存储 | ChromaDB + 额外embedding | 不用向量,Leiden拓扑聚类 |
| 写入频率 | O(n),n=工具调用次数 | O(1),首次构建+增量更新 |
| 缓存策略 | 无(每次都调API) | SHA256缓存,只处理变更文件 |
graphify的核心洞察是:代码的结构信息可以完全在本地提取。函数名、类定义、import关系、调用图——这些都是确定性的,tree-sitter解析就够了,不需要LLM。只有文档、图片这些非结构化内容才需要调Claude。
本质区别:graphify是”花一次钱建索引”,claude-mem是”每秒都在烧钱做笔记”。
如果让我重新设计
claude-mem的核心问题不是功能设计,而是在错误的层面使用了LLM。工具调用的输入输出本身就是结构化数据,用Claude去”理解”JSON格式的tool_input和tool_output,这是最大的浪费。
第一原则:能本地做的绝不调API
1 | 用户编码会话 |
本地提取:工具调用本来就是结构化的
这是关键洞察。Claude Code的hook给你的数据已经是JSON了——tool_name、tool_input、tool_response都有明确的schema。用Claude去”理解”这些数据,就像用ChatGPT去解析一个已经格式化好的CSV。
1 | function extractEvent(hookData: PostToolUseData): RawEvent { |
80次工具调用?80次字符串解析,0 token,< 10ms。
会话结束时的批量摘要:把200个事件压成一次API调用
1 | async function summarizeSession(sessionId: string): Promise<Summary> { |
同样80次工具调用的会话:
- claude-mem当前设计:~8M tokens(80次API调用 + 上下文累积)
- 我的设计:~3K tokens(1次API调用,输入是本地预压缩的几百token)
成本差距:约2600倍。
存储层:砍掉ChromaDB,SQLite一把梭
1 | SQLite(唯一存储) |
为什么不需要ChromaDB:
- “我上次改了哪个文件”→ 文件路径精确查询
- “上次处理CORS问题时做了什么”→ FTS5全文搜索
- “这个项目最近的工作进展”→ 按时间排序取最近N条
这三类查询覆盖了跨会话记忆90%的使用场景,全部是SQLite原生能力。
如果真遇到需要语义理解的模糊查询,在查询时用Claude做rerank就够了——按需付费,不是预付费。一次查询花几百token做rerank,远比每次写入都做embedding划算。
查询时的按需处理
1 | 用户开始新会话 |
设计哲学的本质差异
1 | claude-mem的哲学: 实时观察,持续理解,先花钱后省钱 |
对于编码助手的记忆场景,大部分事件根本不需要语义理解。”改了哪个文件第几行”这种信息,字符串解析就够了。”这次会话整体在做什么”这种高层语义,会话结束时总结一次就够了。
不是每一步都值得被”理解”,但每一步都值得被”记录”。记录是廉价的,理解是昂贵的。好的架构应该把廉价的事情做到极致,把昂贵的事情推迟到不得不做的时候。