Hooks architecture 摘要
文档概览
这篇来源文档说明 claude-mem 如何依赖 Hook 生命周期构建 memory 系统:
- 从 Claude Code 主会话“外部”观察事件,不直接修改主程序行为。
- 快速 Hook 只负责捕获、排队、注入,不在前台做重处理。
- 慢操作,例如 AI 压缩与摘要生成,交给后台 Worker Service Architecture 处理。
- 在合适的生命周期阶段,把来自过往会话的上下文重新注入到当前会话。
原文给出的核心原则是:Observe the main Claude Code session from the outside, process observations in the background, inject context at the right time.
文档同时强调一个关键认识:claude-mem 不会中断也不会修改 Claude Code 的原有行为,它通过生命周期 Hook 在外围提供附加价值。
关键事实
核心原则:memory 是增强层,不是运行前提
原文明确把 Progressive Enhancement 作为设计模式之一,并给出原则表述:Core functionality works without memory, memory enhances it。
这一定义可以展开为 3 个必须同时成立的事实:
- 没有 memory 时,Claude Code 仍可正常工作。
- 有 memory 时,Claude Code 可以获得来自过去会话的上下文,并据此增强当前交互。
- memory 损坏、数据库异常、worker 崩溃或其他 memory 相关故障发生时,系统会回退到“仍然正常工作”的无记忆模式,而不是把 Claude Code 一起拖垮。
原文还用失败处理伪代码说明这一点:当捕获 observation 失败时,Hook 记录错误后继续返回成功结果,而不是抛出异常阻塞主流程。
为什么必须使用 Hooks
文档列出几个架构约束:
- 不能修改 Claude Code,因为它是闭源二进制。
- 必须足够快,不能拖慢主会话。
- 必须可靠,claude-mem 自身失败时不能破坏 Claude Code。
- 必须可移植,能在任意项目工作而不需要额外项目级配置。
对应解决方案是:使用通过 settings.json 配置的外部命令 Hooks。
Hook 系统带来的能力
原文总结了 Hook 系统的几个直接优势:
- 可挂接生命周期事件:
SessionStart、UserPromptSubmit、PreToolUse (Read)、PostToolUse、Stop、SessionEnd。 - 非阻塞:Hooks 并行执行,不等待彼此完成。
- 可注入上下文:
SessionStart与UserPromptSubmit都能向上下文添加内容。 - 可观察工具行为:PostToolUse 能看到所有工具输入与输出。
不过本文实际重点展开的是 5 个生命周期事件上的脚本,以及一个额外的 Setup 阶段版本检查。
重要细节
生命周期总览
文档把 Claude Code 的主会话描述为一个按阶段推进的生命周期,主链路包含:
- SessionStart
- UserPromptSubmit
- Tool Use
- Stop
这些事件向下触发 claude-mem 的能力,包括:
- 安装状态检查
- worker 启动
- 上下文注入
- 新会话初始化
- observation 捕获
- 摘要生成
- 会话清理
其中一个重要版本行为变化是:自 Claude Code 2.1.0(ultrathink update)起,SessionStart hooks 不再显示用户可见消息,而是通过 hookSpecificOutput.additionalContext 静默注入上下文。
这条变更意味着 SessionStart 的上下文能力仍然存在,但呈现方式从“可见输出”变成了“隐式附加上下文”。
Setup 阶段:version-check.js 只做版本陈旧检查
运行时安装并不由 Setup Hook 完成。文档明确指出:
- 真正的安装与修复由
npx claude-mem install和npx claude-mem repair在带可见 spinner 的会话外流程中完成。 - Setup Hook 本身只运行一个 小于 100ms 的 version-check.js,目的是发现插件外部升级后产生的陈旧安装状态。
其行为细节是:
- 读取由 npx 安装器写入的
.install-version标记文件。 - 将该标记与当前加载的插件版本比较。
- 如果不匹配,就向
stderr输出:run: npx claude-mem repair。 - 始终以 0 退出,不会阻塞会话。
原文给出的关键特征包括:
- 只做版本标记检查,几乎无额外 I/O。
- 永远 exit 0,保证非阻塞。
- 当插件曾被外部升级(例如
claude plugin update)时,给出清晰 repair 指令。
文档还特别强调:Setup Hook 不会执行安装,不会跑 npm install,不会启动重型子进程。Bun、uv 的安装以及插件缓存内的 bun install 都发生在 npx claude-mem install / repair 中。
SessionStart:上下文注入 Hook
在 5 个主要生命周期事件中,SessionStart 阶段会顺序运行两个 Hook 条目:
- worker-service 启动
- context-hook 注入上下文
其中上下文注入 Hook 的目的,是把相关历史会话信息带入当前会话。其步骤是:
- 从当前工作目录提取项目名。
- 查询 SQLite 中最近的 session summaries,数量为最近 10 条。
- 查询 SQLite 中最近 observations,数量可配置,默认 50 条。
- 将结果格式化为一种“渐进披露”的索引格式,而不是一开始塞入所有细节。
- 输出到 stdout,随后自动被注入上下文。
原文强调的设计决策有:
- 在启动时运行,尽早提供记忆。
- 输出保持清晰、紧凑。
- 使用渐进披露格式,只先给索引,不直接给全部详情。
- observation 数量可通过
CLAUDE_MEM_CONTEXT_OBSERVATIONS调整。
文档举例的输出中会包含日期分组、图例、表格化索引,以及类似“Use MCP search tools to access full details”这种引导语,说明它优先提供可检索索引,而不是把所有历史内容直接灌入提示上下文。
UserPromptSubmit:新会话初始化 Hook
这个阶段发生在 Claude 处理用户消息之前,对应脚本是 new-hook.js。它的职责包括:
- 从 stdin 读取用户 prompt 和 session ID。
- 在 SQLite 中创建新的 session 记录。
- 自 v4.2.0+ 起,保存原始用户 prompt 以支持全文搜索。
- 如果 worker service 尚未运行,则启动它。
- 立即返回,不等待后续处理。
文档给出的关键决策:
- 不设置 matcher,因此对所有 prompt 都运行。
- 会话记录立即创建,避免后续 observation 找不到会话。
- 原始 prompt 会入库,但隐私边界是“仅本地 SQLite”。
- 自动启动 worker。
suppressOutput: true,避免污染用户交互。
来源里还给出数据库写入示例,涉及:
- 向
sdk_sessions插入claude_session_id、project、user_prompt等字段。 - 向
user_prompts插入session_id、prompt、prompt_number等字段。
PostToolUse:observation 捕获 Hook
PostToolUse 对应 save-hook.js,在任意工具成功完成后立即触发。它的目标不是处理 observation,而是快速捕获并入队。
具体步骤:
- 从 stdin 接收工具名、输入和输出。
- 找到当前项目对应的活动 session。
- 将 observation 插入
observation_queue表。 - 立即返回;真正处理由 worker 完成。
这里的几个边界特别重要:
- matcher 使用
*,表示捕获所有工具。 - 它是非阻塞设计,只排队不处理。
- worker 会异步处理 observation。
- 并行执行是安全的,因为每个 Hook 各自拥有自己的 stdin。
文档中给出的入队示例对象包含:
session_idtool_name,例如Edittool_input,例如文件路径、旧字符串、新字符串tool_output,例如success: true与linesChanged: 5created_at_epoch时间戳
这也解释了为什么 memory 能在后续形成“做了什么、改了哪些文件、出现过什么问题”的结构化回忆。
Stop 阶段:summary-hook.js 生成摘要
Stop 生命周期事件对应 summary-hook.js。它在用户停止提问时触发,用于在会话进行过程中生成 AI 摘要,而不是只在最终结束时做一次总结。
文档列出的执行步骤是:
- 从数据库收集该 session 的 observations。
- 把 observations 发送给 Claude Agent SDK 做摘要。
- 处理响应并提取结构化 summary。
- 存入
session_summaries表。
关键决策包括:
- 由 Stop 生命周期触发。
- 自 v4.2.0+ 起,同一 session 允许有多个 summaries。
- 这些 summaries 是“checkpoint”,不是“session 已结束”的标志。
- 使用 Claude Agent SDK 做 AI 压缩。
文档还给出了结构化摘要格式,字段包括:
<request>:用户原始请求<investigated>:调查了什么<learned>:发现了什么<completed>:完成了什么<next_steps>:剩余任务<files_read>:读取了哪些文件<files_modified>:修改了哪些文件<notes>:补充说明
这说明 claude-mem 的 summary 不是自由散文,而是可解析、可检索的结构化压缩结果。
SessionEnd:cleanup-hook.js 的优雅收尾
SessionEnd 对应 cleanup-hook.js,在 Claude Code 会话结束时触发,但不会在 /clear 命令时运行。
其职责是: