Skip to content
时钟与叶

Hook 与 Middleware

说明 MiddlewareChain 如何承载运行期能力,Hook 如何映射生命周期节点,以及规则执行、失败放行和配置顺序边界。

编写于 2026-08-16

Peri 的运行期行为由两条线组成,一条是中间件链(MiddlewareChain)——按固定顺序执行的 Rust 组件,负责上下文注入、工具提供、权限审批等能力;另一条是用户 hook 配置——JSON 规则,绑定到 Agent 生命周期的具体节点。两者通过 HookMiddleware 衔接,关系可以一句话概括,

中间件链是承载者,Hook 是链在生命周期节点上的触发面。

本文是概述,以 Middleware 为重点,先讲中间件链如何装配与执行,再讲 HookMiddleware 如何把生命周期方法映射为 hook 事件、HookDispatcher 如何执行用户规则。机制描述与当前 peri-middlewares / peri-acp 源码一致;事件集会随实现变化,具体清单应以当前代码为准。用户侧的配置方法见 Hooks 文档

中间件承载运行期能力

每个会话创建时,装配器(peri-middlewares/src/assembly.rs)按 Agent 层 session 工厂(peri-agent/src/session/factory.rs)的 production_blueprint 蓝本构造 MiddlewareChain。链序是行为契约,源码注释明确禁止重排。当前蓝本分七组,

槽位职责
DefaultSystemPromptLangAgentsMdAgentDefinePluginSkillsSkillPreloadAtMentionImage上下文注入(system prompt 段落、agent 定义、插件、技能)
FilesystemGitAttributionTerminalWeb文件 / 终端 / Web 工具提供器
TodoCron待办与定时工具
Hook每个非空 hook 组展开一个 HookMiddleware 实例
PermissionAskUserSubAgent审批、提问通道、子 Agent
McpWorkflowPtcToolSearchArtifact条件工具提供器
LspGoal诊断与目标引导

MiddlewareChain 执行时按注册顺序遍历,run_before_agent 依次调用每个中间件的 before_agent,任一返回错误即中断整条链;run_before_tool 让每个中间件依次改写 ToolCall,前一个的输出是后一个的输入。每个中间件还可以通过 collect_tools 向会话登记自己的工具。

Middleware 之所以是承载者,在于几乎所有能力都以中间件为承载单元,上下文是中间件注入的,工具是中间件提供的,审批是中间件执行的。Hook 能做什么、在什么时候做,都受链上节点约束——它只在这些节点上获得发言权,节点之间的推进由 Agent Loop 完成(见 Agent Loop)。

生命周期节点开放配置入口

三层分工可以沿《Hook 系统》时期的结论概括,但视角放在链上,

职责何时扩展
Agent Loop在固定生命周期节点调用链上中间件的方法,决定什么时候触发改动需要 Rust 代码
HookMiddleware把生命周期方法映射为用户可见事件,决定触发什么事件新增事件需要 Rust 代码
hook 规则 + HookDispatcher决定怎么响应事件,返回 Allow / Block / ModifyInput纯配置驱动,无需改代码

生命周期映射为事件

HookMiddlewareperi-middlewares/src/hooks/middleware.rs)实现 Middleware trait 的六类生命周期方法,每类触发一组事件,

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

SessionStart 只在会话首次进入时触发,source 取 startup / resume / clear / compact 之一。

另有一组 Standalone 事件不经过 Middleware trait,由具体模块直接调用 fire_standalone_lifecycle_hooks() 触发,SubagentStart / SubagentStop(SubAgent 工具)、PreCompact / PostCompact(压缩阶段)、SessionEnd(TUI 关闭)。它们共享同一个分发引擎。

一个 turn 的事件时序大致如下,

sequenceDiagram
    participant Loop as Agent Loop
    participant Chain as MiddlewareChain
    participant MW as HookMiddleware
    participant Disp as HookDispatcher
    participant Hook as 用户 hook

    Loop->>Chain: before_agent()
    Chain->>MW: before_agent()
    MW->>Disp: SessionStart / UserPromptSubmit / InstructionsLoaded
    Disp->>Hook: 执行规则(command 等)
    Hook-->>Disp: Allow / Block / ModifyInput
    Disp-->>MW: 归约后的动作

    Loop->>Chain: before_tool() → after_tool()(每个工具)
    Chain->>MW: PreToolUse / PostToolUse
    Loop->>Chain: after_tools_batch()
    Chain->>MW: PostToolBatch

    Loop->>Chain: after_agent()
    Chain->>MW: Stop → Notification

规则按顺序归约

HookDispatcherperi-middlewares/src/hooks/dispatcher.rs)是执行引擎。fire_event() 按事件名查表,对每条注册规则依次做四步判断,一次性标记(once)→ matcher 粗过滤(工具名匹配)→ if 细条件 → 执行。执行结果归约为三种动作,

  • Allow,允许继续
  • Block,短路——不再执行后续 hook
  • ModifyInput,累积修改输入,继续执行后续 hook

支持四种执行类型,通过 JSON 的 type 字段区分,

Shell 命令执行,最常用的类型。stdin 接收完整 HookInput,退出码 0 解析 stdout 为结构化决策,1 为 Allow(warn),2 为 Block。

几点可靠性设计,

  • fail-open,hook 超时或进程异常按 Allow 处理,hook 不应成为 Agent 的单点故障。
  • Stop 阻断Stop 是唯一能阻止 Agent 继续执行的事件,支持 continue: false 彻底停止,或 decision: block 软阻断重试;连续 Block 超过 8 次强制放行,防止规则过严导致 Agent 永远无法结束。软阻断反馈以 Human + <system-reminder> 注入,不使用 BaseMessage::system(),以免破坏冻结的 system prompt 前缀。
  • 双重过滤,matcher 在锁外快速跳过无关工具,if 在锁内精确匹配。

配置进入中间件链

hook 规则从多个来源加载,由装配端(peri-acp/src/host/assemble.rs)聚成 hook 组,

  1. 插件 hooks,hooks/hooks.json(优先)或 plugin.jsonhooks 字段
  2. 全局 ~/.claude/settings.json
  3. 项目 .claude/settings.json
  4. 项目 .claude/settings.local.json

每个非空组生成一个 HookMiddleware 实例,按组顺序挂到链的第四组槽位(Todo / Cron 之后、Permission 之前)。这意味着,同一事件下,配置靠后的组在链上执行得也更靠后;PreToolUse 类 hook 先于权限审批运行,可以在弹窗出现前改写或阻断工具调用。

配置结构沿用 Claude 风格——事件名 → matcher 组 → hook 列表,

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Write|Edit",
"hooks": [
{ "type": "command", "command": "python3 audit.py" }
]
}
]
}
}

限制与边界