06 — 记忆如何在对话中体现
核心问题:claude-mem 装好后,我在正常对话时"记忆"究竟是怎么发挥作用的?它怎么知道何时搜索、搜什么、如何注入到上下文里?
两条完全独立的路径
记忆在对话中通过两条机制体现,分工明确:
| 路径一:自动注入 | 路径二:主动搜索 | |
|---|---|---|
| 触发者 | Claude Code Hook 机制(机器触发) | Claude 自己(LLM 判断后调用 MCP 工具) |
| 时机 | 会话开始时,一次性 | 对话中途,按需多次 |
| 内容 | 近期会话摘要 + 项目相关 observations | 精准历史记录(由搜索词决定) |
| Claude 的角色 | 被动接收(醒来时记忆已在上下文里) | 主动调用 MCP 工具 |
| 数据密度 | 有限(避免撑爆 context window) | 3 层递进:索引 → 时间线 → 全文 |
路径一:自动注入(你不感知,Claude 也不选择)
触发时机:每次 Claude Code 开始新会话(claude 启动),或 /clear、/compact 之后。
关键点:Claude 不需要"决定"搜不搜,记忆已经在那里了。你看到 Claude 说"上次我们在做 X",就是这条路径的效果。
注入的内容来自 src/services/context/ContextBuilder.ts 的 generateContext() 函数,它从 ContextConfigLoader 读取限额配置,然后调用 ObservationCompiler 拼装输出。
路径二:主动搜索(Claude 被"教会"的行为)
触发时机:你问了涉及历史的问题(“上周那个 bug 怎么解决的”、“之前的方案是什么”)。
为什么 Claude 会主动搜索:
安装时注册了 mem-search MCP 技能。其中的 __IMPORTANT 元工具描述本身就是一段指令文本,注入进 Claude 的工具列表说明里:
|
|
💡 Tip 这是一个"prompt injection via tool description"技巧 MCP 工具描述字段对 Claude 来说和系统提示的效力等同。
__IMPORTANT工具的 description 就是工作流规范本身——架构设计即使用规范,API 即文档,无需额外指导。
搜索结果进入上下文的方式,是 MCP 工具调用的标准返回值路径,不走 hook 注入。
记忆里存的是什么
三类数据,层级递进:
Observations — 工具调用的语义提炼
每次 PostToolUse 后,原始的 {tool_name, tool_input, tool_output} 被发给 AI 压缩,产出结构化 XML:
|
|
存的不是原始对话,是 AI 从工具调用里提炼的语义摘要。代码见 src/sdk/prompts.ts 的 buildObservationPrompt()。
Session Summaries — 会话级五段式摘要
Stop Hook 触发后,buildSummaryPrompt() 把最后一条 assistant 消息喂给 AI,产出五段式摘要,存入 session_summaries:
| 字段 | 含义 |
|---|---|
request |
这次会话用户要做什么 |
investigated |
调查了哪些模块/文件 |
learned |
关键发现(这是下次最有价值的字段) |
completed |
完成了什么 |
next_steps |
下次应该继续做什么 |
没有完整的 user/assistant 对话历史,只有这五个字段的提炼。
User Prompts — 轻量问题记录
只存用户每次输入的问题文本,用于 timeline 展示,不存 assistant 回答。
记忆会不会失控膨胀?
注入时有硬上限,从 src/shared/SettingsDefaultsManager.ts 的默认值读出:
|
|
ObservationCompiler.ts 里的查询永远带 LIMIT:
|
|
结论:注入到 Claude 上下文里的记忆是有界窗口,不随使用时间线性增长。
⚠️ Warning 数据库本身不会自动清理 SQLite 里的历史数据会一直积累(磁盘慢涨),但这不影响注入效率——每次只取最近 N 条。目前代码里没有自动 TTL 或定期清理机制。这是一个有意为之的取舍:存储廉价,查询有上限,永久保留而不主动清理。如果在意磁盘,需要手动管理
~/.claude-mem/claude-mem.db。
不想被记忆的内容怎么处理
方法一:<private> 标签
|
|
在 hook 层(最靠近用户输入处)就被剥离,不进 Worker,不进 DB。见 src/utils/tag-stripping.ts。
方法二:项目排除
summarize.ts 开头有项目过滤判断:
|
|
某些目录(个人文档、敏感项目)可以配置排除,Stop hook 遇到这些目录直接跳过,不生成任何摘要。
核心收获
-
记忆体现有两套机制,不要混淆:自动注入(session start 时塞进去,被动接收)和主动搜索(MCP 工具调用,Claude 主动发起)。前者无感,后者可观测(你能看到 Claude 在调用 search/timeline/get_observations)。
-
存的不是聊天记录,是语义提炼:observations 是工具调用的 AI 摘要,summaries 是会话的五段式结构化总结。原始对话内容不被持久化。
-
上下文窗口里的记忆是有界的:默认最多 50 observations + 10 session summaries,不会随使用时间膨胀。注入的 token 量由配置控制,可以调整。
-
<private>标签是边缘防线:在数据产生的最源头就被切断,是隐私保护的正确姿势。