Hooks
Hook 是智能体运行时在指定生命周期事件调用的回调。工具执行前的 hook 可以否决请求,阻止文件变化;执行后的 hook 可以检查结果,却无法阻止已经发生的操作。事件和工具匹配器必须同时命中,回调才会运行。
把它看作检查点:放在工具前,可以拦截请求;放在工具后,可以记录结果。即使识别的是同一个问题,检查时机也决定了能阻止什么。
Hook 如何工作
要阻止操作,使用前置检查;需要已完成的结果来记录日志或触发格式化,则使用后置回调。负责分发事件和执行工具的运行框架称为 harness。
一项注册连接事件、匹配器、回调。本模拟把 h1 的 check_release 注册到 PreToolUse 或 PostToolUse,工具匹配器选择 Edit 或 Edit|Write。分发器先匹配事件,再匹配工具名;两项都匹配才调用回调。回调之后才检查目标文件。
两个请求预先给定,模型不参与这段分发,也不会再决定是否运行 check_release。分发和执行属于 harness。
回调保护 release.txt。在前置事件中,它记录调用尝试:目标受保护则返回 deny,其他文件返回 no-decision。harness 在执行前处理结果。no-decision 本身不授权,只把决定交回本例允许调用的基线策略。
在后置事件中,回调收到成功结果,记录已完成的调用;若目标受保护,则报告违规。报告随工具结果返回,但回调不执行补偿写入,文件保持工具写入后的内容。
事件名遵循 Claude Code 的 hooks 参考:前置 hook 在执行前运行,成功后的 hook 接收结果,匹配器筛选工具名。不同事件的输出格式不同,动画中的短标签只概括各自契约;后置阻止报告能追加反馈,不能撤销已发生的副作用。
Hooks 逐步演示
默认把 check_release 注册为 PreToolUse,匹配 Edit|Write。初始文件为 notes.txt = draft、release.txt = locked。locked 只是文本内容,不是操作系统文件锁;两个工具若被调用都能成功。
- c1 提议编辑。
Edit("notes.txt", "draft", "reviewed")请求替换文本。收到请求时,文件和工具调用计数都不变。 - 前置事件命中 h1。 事件与工具均匹配。回调记录 c1 的一次尝试,发现目标未受保护,返回
no-decision。回调运行不等于一定否决。 - harness 执行 c1。 基线策略允许调用,Edit 把
draft改为reviewed,工具计数为 1。成功结果触发PostToolUse,但 h1 注册在前置事件,此处不调用它。 - c2 提议受保护写入。
Write("release.txt", "published")到达前置事件。Write 在匹配范围内,回调记录第二次尝试,返回deny。 - harness 处理拒绝。 关闭执行路径,把 c2 标为阻止。Write 没有运行,因此没有成功结果,也没有对应的后置成功事件。
最终 notes.txt = reviewed、release.txt = locked,hook 调用 2 次,工具调用 1 次。两条回调记录都只是尝试记录;c1 是否执行,要看调用账本和真实工具结果。受保护文件未变、请求被阻止,两者一起证明前置检查生效。
检查太晚或漏掉工具时
只把事件改为 PostToolUse,两次调用都会通过前置阶段,由基线策略允许执行。c2 先写入 published,回调随后发现违规。报告说明操作已发生,最终文件仍为 published。
改回 PreToolUse,再把匹配器缩为 Edit。c1 会调用回调;c2 也有前置事件,但 Write 不匹配,回调根本没机会检查 release.txt。工具仍执行。时机提前不能弥补覆盖范围缺失。
| 注册方式 | Hook 次数 | 工具次数 | 受保护写入 |
|---|---|---|---|
| PreToolUse + Edit|Write | 2 | 1 | 被阻止;文件保持 locked |
| PreToolUse + Edit | 1 | 2 | 未覆盖;文件变为 published |
| PostToolUse + Edit|Write | 2 | 2 | 执行后报告;文件保持 published |
| PostToolUse + Edit | 1 | 2 | 未覆盖;文件变为 published |
各场景都暂停在 c2 提议请求时:c1 已完成,release.txt 仍为 locked。先判断能否拦截,再播放,依次观察事件、匹配和文件变化。比较 Prevent the write 与 Check after execution 只改变时机;与 Miss the Write tool 比较只改变覆盖范围。请求和文件相同。
后置 hook 适合记录成功结果或格式化已编辑文件。自动格式化示例 展示了后一种用途。先按目的选择事件,再检查哪些工具能进入处理器。
边界情况
- 工具匹配,事件不符。 匹配 Write 不会让注册在后置事件的回调在前置事件运行。
- 事件匹配,工具不符。 c2 的前置事件仍发生,只是
Edit不覆盖 Write。“回调未运行”和“回调返回 no-decision”不同。 - 匹配但目标无害。 c1 调用回调后仍执行。工具匹配器筛选候选,回调检查具体目标。
- 执行前阻止。 c2 是被拒绝的请求,不是运行后失败的工具,也不会产生成功结果或后置成功事件。
- 执行后全部命中。 完整匹配可以审计这两个成功调用,但不能让写入变成未发生。
常见错误
- 把事件发生当成回调已运行。还要检查注册匹配和调用计数。
- 把尝试记录当成完成记录。默认回调运行时,工具尚无结果。
- 把
no-decision当成允许规则。后续由基线权限策略决定。 - 认为后置警告会恢复文件。恢复需要另一项操作,这个回调只报告。
- 把文件名写进工具名匹配器。本例的
Edit|Write选择工具,回调再检查release.txt。
模型的简化
本课使用两个预设请求、两个内存文件和一个同步回调,结果确定。匹配、回调和文件操作均在本地执行,没有记录或编造模型输出。基线策略允许两个工具,以便观察 hook 的影响。
真实运行时还涉及路径处理、其他执行入口、多个回调、超时、异步任务、输入改写和更多生命周期事件,有些 hook 自身会调用模型。本课不是生产权限防护实现,也不能替代系统训练。