Skip to content
时钟与叶

Hook 系统

Peri 的 Hook 系统——Agent Loop → Hook Middleware → Claude Code Hook 三层管道,28 个生命周期事件的拦截、评估与决策管线。

完整架构:三层管道

Peri 的 Hook 系统是一条三层管道——每层职责清晰,数据单向流动:

flowchart TD
    RC["Receive"] --> CP["Compact"] --> RS["Reason"] --> AC["Act\n(工具分发)"]
    AC -->|"有 tool_calls"| RC

    RC -.->|"turn 开始"| BAG["before_agent"]
    CP -.->|"压缩前 / 压缩后"| PCC["PreCompact / PostCompact"]
    RS -.->|"无工具调用 → 最终回答"| AA["after_agent"]
    RS -.->|"LLM 调用失败"| OE["on_error"]
    AC -.->|"工具执行前"| BT["before_tool"]
    AC -.->|"每个工具后"| AT["after_tool"]
    AC -.->|"批次完成"| ATB["after_tools_batch"]
    AC -.->|"子 Agent 派发 / 完成"| SAS["SubagentStart / SubagentStop"]

    SE["TUI 关闭"] -.->|"会话结束"| SES["SessionEnd"]

    BAG ==>|"SessionStart / UserPromptSubmit / InstructionsLoaded"| CMD["command"]
    BT ==>|"PreToolUse / PermissionRequest"| PMT["prompt"]
    AT ==>|"PostToolUse / PostToolUseFailure"| HTTP["http"]
    ATB ==>|"PostToolBatch"| CMD
    AA ==>|"Stop / Notification"| PMT
    OE ==>|"StopFailure"| HTTP
    PCC -->|"仅 command / http"| CMD
    SAS -->|"仅 command / http"| HTTP
    SES -->|"仅 command / http"| CMD

三层职责一句话:

职责可扩展性
Layer 0:Agent LoopRCRA 循环在固定位置调用 Middleware trait 方法,决定”什么时候触发改动需修改 Rust 代码
Layer 1:Hook Middleware将 Agent Loop 的 6 个生命周期方法映射到 15 个 Claude Code 事件,决定”触发什么事件新增事件需修改 Rust 代码
Layer 2:Claude Code Hook 分发加载用户配置的 hook 规则,执行 command/prompt/http/agent,返回 Allow/Block/ModifyInput,决定”怎么响应事件纯配置驱动,无需改代码

Layer 0:Agent Loop 如何驱动 Hook

Agent Loop 是 RCRA 四阶段循环(详见 Agent Loop 文档)。每次循环在固定的生命周期节点调用中间件链的对应方法:

flowchart LR
    R["Receive"] --> C["Compact"] --> S["Reason"] --> A["Act"]
    A -->|"有 tool_calls"| R

    R -.->|"首次进入\nrun_before_agent()"| BAG["before_agent"]
    A -.->|"工具审批\nrun_before_tool()"| BT["before_tool"]
    A -.->|"工具执行后\nrun_after_tool()"| AT["after_tool"]
    A -.->|"批次完成\nrun_after_tools_batch()"| ATB["after_tools_batch"]
    A -.->|"推理完成\nrun_after_agent()"| AA["after_agent"]
    A -.->|"LLM 异常\nrun_on_error()"| OE["on_error"]

MiddlewareChain 按注册顺序遍历链上所有中间件(HookMiddleware 是其中之一),依次调用对应方法。Hook 的返回结果(Allow/Block/ModifyInput/PreventContinuation)会反向传递,影响 Agent Loop 的后续行为。

Layer 1:Hook Middleware — 6 个 trait 方法 → 15 个事件

HookMiddlewareperi-middlewares/src/hooks/middleware.rs)是 Layer 1 的唯一实现,通过 #[async_trait] impl Middleware 提供 6 个生命周期方法。每个方法内部调用 HookDispatcher::fire_event(),将内部生命周期映射为用户可见的 Claude Code 事件。

逐方法映射表

Middleware 方法Loop 触发时机触发的 Claude Code 事件
before_agent每轮循环首次进入SessionStart(仅首次有 source)→ UserPromptSubmitInstructionsLoaded
before_tool每个工具执行前PreToolUsePermissionRequest(仅敏感工具+弹窗模式)
after_tool每个工具执行后PostToolUse(成功)/ PostToolUseFailure(失败)
after_tools_batch一批并行工具全部完成PostToolBatch
after_agentAgent 输出最终回答StopNotification
on_errorLLM 调用失败StopFailure

Standalone 事件:不走 Middleware trait 的触发路径

除了上述 15 个通过 Middleware trait 触发的事件外,还有一组 Standalone 事件——它们在 Agent Loop 之外由特定模块直接调用 fire_standalone_lifecycle_hooks() 触发:

事件触发模块触发时机
SubagentStartSubAgent 工具子 Agent 派发时
SubagentStopSubAgent 工具子 Agent 完成时
PreCompactCompact 阶段上下文压缩前
PostCompactCompact 阶段上下文压缩后
SessionEndTUI 关闭/clear 等会话重置时

Layer 2:Claude Code Hook 分发 — HookDispatcher

HookDispatcherperi-middlewares/src/hooks/dispatcher.rs)是 Layer 2 的核心引擎。fire_event() 的执行流程如下:

flowchart TD
    A["fire_event(HookEvent, HookInput)"] --> B["从 HashMap 查找\n匹配的 RegisteredHook 列表"]
    B --> C{遍历每个 hook}
    C --> D{once check?\n一次性 hook 已触发过?}
    D -->|是| E[跳过]
    D -->|否| F{matcher 匹配?\n工具名粗粒度过滤}
    F -->|不匹配| E
    F -->|匹配| G{if 条件匹配?\n细粒度条件过滤}
    G -->|不匹配| E
    G -->|匹配| H{async hook?}
    H -->|是| I["tokio::spawn 后台执行\n直接返回 Allow"]
    H -->|否| J{执行类型}
    J -->|command| K[execute_command_hook]
    J -->|prompt| L[execute_prompt_hook]
    J -->|http| M[execute_http_hook]
    J -->|agent| N[execute_agent_hook]
    K --> O["解析退出码\n0→解析 stdout JSON\n1→Allow(warn)\n2→Block"]
    L --> O
    M --> O
    N --> O
    O --> P{归约结果}
    P -->|Block| Q["短路返回 Block\n不再执行后续 hook"]
    P -->|ModifyInput| R["累积修改\n继续后续 hook"]
    P -->|Allow| E

过滤机制:matcher + if 两级

层级字段语法示例
粗粒度matcher* / A|B / 正则"Bash|Write|Edit"
细粒度if权限规则语法"Bash(git commit)"

两条都匹配时才执行 hook。matcher 在锁外快速跳过不相关工具,if 在锁内做精确匹配。

一个完整 turn 的 Hook 事件时序

sequenceDiagram
    participant Loop as Agent Loop
    participant Chain as MiddlewareChain
    participant MW as HookMiddleware
(Layer 1) participant CC as HookDispatcher
(Layer 2 → 用户 hook) Loop->>Chain: 进入循环 Chain->>MW: before_agent() MW->>CC: fire_event(SessionStart) MW->>CC: fire_event(UserPromptSubmit) MW->>CC: fire_event(InstructionsLoaded) Loop->>Chain: Act(工具执行开始) Chain->>MW: before_tool() MW->>CC: fire_event(PreToolUse) MW->>CC: fire_event(PermissionRequest) Note over MW,CC: 用户 hook 可返回
Allow / Block / ModifyInput Loop->>Loop: 执行单个工具 Chain->>MW: after_tool() MW->>CC: fire_event(PostToolUse / PostToolUseFailure) Note over Loop,CC: 重复 before_tool → after_tool 直到所有工具完成 Chain->>MW: after_tools_batch() MW->>CC: fire_event(PostToolBatch) Note over Loop,CC: 或无工具调用,直接进入回答 Chain->>MW: after_agent() MW->>CC: fire_event(Stop) Note over MW,CC: Stop hook 可返回
PreventContinuation 阻断 Agent MW->>CC: fire_event(Notification)

完整事件体系

Peri 实现了 15 个核心事件(已生产验证,通过 Middleware trait 触发)和 13 个扩展事件(基础设施就绪,含 5 个 Standalone 已接入事件 + 8 个待接入触发点),共计 28 个事件。

核心事件表(Middleware trait 触发)

事件触发阶段说明
SessionStartbefore_agent会话首次启动(source: startup / resume / clear / compact)
UserPromptSubmitbefore_agent用户每次发送消息
InstructionsLoadedbefore_agent系统提示词加载完成
PreToolUsebefore_tool工具执行前,可改写输入
PermissionRequestbefore_tool权限弹窗前(仅敏感工具 + 弹窗模式)
PostToolUseafter_tool工具执行成功
PostToolUseFailureafter_tool工具执行失败
PostToolBatchafter_tools_batch批次完成
Stopafter_agent推理完成,可阻断续跑
StopFailureon_errorLLM 调用失败
Notificationafter_agent / before_toolAgent 等待用户时

Standalone 事件(外部触发)

事件触发源说明
SubagentStartSubAgent 工具子 Agent 启动
SubagentStopSubAgent 工具子 Agent 结束
PreCompactCompact 阶段上下文压缩前
PostCompactCompact 阶段上下文压缩后
SessionEndTUI 关闭/clear 等会话重置时

Hook 类型

Peri 支持 4 种 Hook 执行类型,通过 JSON 的 type 字段区分:

Shell 命令执行——最常用的类型。

{
"type": "command",
"command": "node scripts/validate.sh",
"timeout": 600,
"once": false
}
  • stdin 接收完整的 HookInput JSON
  • 退出码 0 → 解析 stdout 为结构化决策(Allow / Block / ModifyInput)
  • 退出码 1 → Allow(warn),退出码 2 → Block
  • 超时 → Allow(fail-open 原则)

配置方式

Hook 配置遵循 Claude Code 格式,从多个来源加载,按优先级从低到高覆盖:

flowchart TD
    A["插件 hooks/hooks.json"] --> B["插件 plugin.json hooks 字段"]
    B --> C["项目 .claude/settings.json"]
    C --> D["项目 .claude/settings.local.json"]
    D --> E["全局 ~/.claude/settings.json"]

配置结构

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 audit.py",
"timeout": 30,
"status_message": "审计中…"
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo '会话已启动'",
"once": true
}
]
}
]
}
}

每个事件名下是一个 hook 组数组。一个 hook 组由 matcher(工具名粗粒度过滤)、if(细粒度条件过滤)和 hooks(实际执行的 hook 列表)组成。同组内的 hook 串行执行,遇到 Block 时短路。

Stop Hook 与防死循环

Stop 事件是唯一可以阻断 Agent 继续执行的 hook——允许外部审查 Agent 的最终回答,决定是否通过或要求重试。

决策模型

flowchart TD
    A["Stop Hook 触发"] --> B{返回结果}
    B -->|"continue: false"| C["PreventContinuation\nAgent 彻底停止"]
    B -->|"decision: block"| D{StopBlockGuard 计数}
    D -->|"< 8 次"| E["软阻断\n注入 system-reminder\nAgent 再试一轮"]
    D -->|"≥ 8 次"| F["强制放行\n防死循环保护"]
    B -->|Allow| G["正常结束\n触发 Notification"]

连续 Block 上限为 8 次——超过后强制放行,防止 Stop hook 规则过严导致 Agent 永远无法结束。

Claude Code 兼容性

Peri Hook 系统从设计之初就以 Claude Code 兼容为目标。

维度兼容状态
事件命名完全对齐(PascalCase)
配置格式完全兼容(hooks.EventName[].hooks[] 结构)
Hook 类型command / prompt / http 完全兼容,额外支持 agent 类型
退出码语义0=解析 stdout JSON,1=Allow,2=Block——完全一致
输出 JSON 格式continue / decision / systemMessage / hookSpecificOutput 完全对齐
超时策略fail-open——完全一致
SSRF 防护私有网络阻止——完全一致
async hookfire-and-forget——完全一致
matcher / if 过滤工具名匹配 + 细粒度条件——完全对齐

关键设计决策

决策选择理由
三层管道Agent Loop → Hook Middleware → Claude Code Hook每层职责单一:时机、事件、响应各司其职
Fail-open超时 / 崩溃 → AllowHook 不应成为 Agent 的单点故障
防死循环Stop 8 次上限规则过严时 Agent 仍有退出路径
System 消息禁令重试反馈用 Human 注入保护 frozen_system_prompt 不被破坏
SSRF 防御HTTP hook 禁止私有网络防止内网探测,但允许 loopback
双重过滤matcher + if 两级粗粒度跳过不相关工具,细粒度精确匹配
独立事件泵Standalone 事件走独立通道不与主 Agent 的回转生命周期耦合
Middleware trait 承上启下上行接收 Agent Loop 信号,下行委托 HookDispatcher新增 hook 点只需在 Middleware trait 中实现,无需改动分发引擎

更多资源

  • Agent Loop — Hook Middleware 所处的中间件链与 RCRA 循环的集成细节
  • Multi Agent 架构 — SubAgent 的 SubagentStart / Stop hook 触发路径
  • Hooks 文档 — hooks 的用户级配置与用法