W
AI-Wiki
SOURCE

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

文档列出几个架构约束:

  1. 不能修改 Claude Code,因为它是闭源二进制。
  2. 必须足够快,不能拖慢主会话。
  3. 必须可靠,claude-mem 自身失败时不能破坏 Claude Code
  4. 必须可移植,能在任意项目工作而不需要额外项目级配置。

对应解决方案是:使用通过 settings.json 配置的外部命令 Hooks。

Hook 系统带来的能力

原文总结了 Hook 系统的几个直接优势:

  • 可挂接生命周期事件:SessionStartUserPromptSubmitPreToolUse (Read)PostToolUseStopSessionEnd
  • 非阻塞:Hooks 并行执行,不等待彼此完成。
  • 可注入上下文:SessionStartUserPromptSubmit 都能向上下文添加内容。
  • 可观察工具行为:PostToolUse 能看到所有工具输入与输出。

不过本文实际重点展开的是 5 个生命周期事件上的脚本,以及一个额外的 Setup 阶段版本检查。

重要细节

生命周期总览

文档把 Claude Code 的主会话描述为一个按阶段推进的生命周期,主链路包含:

这些事件向下触发 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 installnpx claude-mem repair 在带可见 spinner 的会话外流程中完成。
  • Setup Hook 本身只运行一个 小于 100msversion-check.js,目的是发现插件外部升级后产生的陈旧安装状态。

其行为细节是:

  1. 读取由 npx 安装器写入的 .install-version 标记文件。
  2. 将该标记与当前加载的插件版本比较。
  3. 如果不匹配,就向 stderr 输出:run: npx claude-mem repair
  4. 始终以 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 条目:

  1. worker-service 启动
  2. context-hook 注入上下文

其中上下文注入 Hook 的目的,是把相关历史会话信息带入当前会话。其步骤是:

  1. 从当前工作目录提取项目名。
  2. 查询 SQLite 中最近的 session summaries,数量为最近 10 条。
  3. 查询 SQLite 中最近 observations,数量可配置,默认 50 条。
  4. 将结果格式化为一种“渐进披露”的索引格式,而不是一开始塞入所有细节。
  5. 输出到 stdout,随后自动被注入上下文。

原文强调的设计决策有:

  • 在启动时运行,尽早提供记忆。
  • 输出保持清晰、紧凑。
  • 使用渐进披露格式,只先给索引,不直接给全部详情。
  • observation 数量可通过 CLAUDE_MEM_CONTEXT_OBSERVATIONS 调整。

文档举例的输出中会包含日期分组、图例、表格化索引,以及类似“Use MCP search tools to access full details”这种引导语,说明它优先提供可检索索引,而不是把所有历史内容直接灌入提示上下文。

UserPromptSubmit:新会话初始化 Hook

这个阶段发生在 Claude 处理用户消息之前,对应脚本是 new-hook.js。它的职责包括:

  1. 从 stdin 读取用户 prompt 和 session ID。
  2. 在 SQLite 中创建新的 session 记录。
  3. 自 v4.2.0+ 起,保存原始用户 prompt 以支持全文搜索。
  4. 如果 worker service 尚未运行,则启动它。
  5. 立即返回,不等待后续处理。

文档给出的关键决策:

  • 不设置 matcher,因此对所有 prompt 都运行。
  • 会话记录立即创建,避免后续 observation 找不到会话。
  • 原始 prompt 会入库,但隐私边界是“仅本地 SQLite”。
  • 自动启动 worker。
  • suppressOutput: true,避免污染用户交互。

来源里还给出数据库写入示例,涉及:

  • sdk_sessions 插入 claude_session_idprojectuser_prompt 等字段。
  • user_prompts 插入 session_idpromptprompt_number 等字段。

PostToolUse:observation 捕获 Hook

PostToolUse 对应 save-hook.js,在任意工具成功完成后立即触发。它的目标不是处理 observation,而是快速捕获并入队。

具体步骤:

  1. 从 stdin 接收工具名、输入和输出。
  2. 找到当前项目对应的活动 session。
  3. 将 observation 插入 observation_queue 表。
  4. 立即返回;真正处理由 worker 完成。

这里的几个边界特别重要:

  • matcher 使用 *,表示捕获所有工具。
  • 它是非阻塞设计,只排队不处理。
  • worker 会异步处理 observation。
  • 并行执行是安全的,因为每个 Hook 各自拥有自己的 stdin。

文档中给出的入队示例对象包含:

  • session_id
  • tool_name,例如 Edit
  • tool_input,例如文件路径、旧字符串、新字符串
  • tool_output,例如 success: truelinesChanged: 5
  • created_at_epoch 时间戳

这也解释了为什么 memory 能在后续形成“做了什么、改了哪些文件、出现过什么问题”的结构化回忆。

Stop 阶段:summary-hook.js 生成摘要

Stop 生命周期事件对应 summary-hook.js。它在用户停止提问时触发,用于在会话进行过程中生成 AI 摘要,而不是只在最终结束时做一次总结。

文档列出的执行步骤是:

  1. 从数据库收集该 session 的 observations。
  2. 把 observations 发送给 Claude Agent SDK 做摘要。
  3. 处理响应并提取结构化 summary。
  4. 存入 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 不是自由散文,而是可解析、可检索的结构化压缩结果。

SessionEndcleanup-hook.js 的优雅收尾

SessionEnd 对应 cleanup-hook.js,在 Claude Code 会话结束时触发,但不会/clear 命令时运行。

其职责是: