Agent 工程对照
← 返回框架对照

结构 · 第 3 章

生命周期挂载点

第三方代码能在循环的哪些时刻插进来?

Codex 的挂载点面向用户配置,openJiuwen 与 Pi 面向进程内代码,DeepSeek 两者都有。

挂载点面向谁

面向用户配置

面向进程内代码

两者都有

原生拦截点 + 桥接别家的 hooks 配置

场景建议

编辑判断

按你的需求,倾向哪种做法

借鉴的是那一家的设计,不等于选用那个框架。

场景

让不写代码的人也能加规则

它的 11 个钩子事件写在 hooks JSON 或配置里,以外部命令或 MCP 调用执行,9 个事件支持 matcher。

场景

要兼容现有的 hooks.json

hooks-codex 与 hooks-claude-code 两个桥接包把现成的钩子配置翻译到自己的类型化拦截点上。

场景

扩展要能改写上下文和请求

Pi 带返回值的事件(上下文、工具调用、厂商请求前)可以改写行为;openJiuwen 的 rail 能改写输入、拦截执行并注册工具。

容易踩的坑

  • 多个钩子同时挂在一个点上时,先决定顺序怎么定:openJiuwen 用一个 priority 同时管初始化与回调顺序,DeepSeek 用中间件的 next() 链。

四家怎么做

差异与源码

openJiuwen

13 个回调事件,rail 以类为单位挂载、按优先级排序

挂载点在 core 里定义为 AgentCallbackEvent 枚举,覆盖调用前后、外层任务迭代前后、ReAct 迭代结束、用户消息准入、插话队列消费、模型调用与工具调用的前后及异常,共 13 个。第三方代码以 AgentRail 子类的形式挂上来:一个 rail 可以同时注册工具与提示词片段、改写输入、拦截执行,priority 一个数字同时决定 init 顺序和回调顺序(同文件 672 行起)。harness/rails/ 下已有安全、中断确认、任务完成、技能、子代理等十几种 rail。注意 AFTER_REACT_ITERATION 只在完整成功的迭代后触发,任何提前退出的路径都不会触发。

适合
要把安全、审批、任务完成等横切能力打包成可复用组件的团队。
代价
事件要在内外两层 Agent 之间正确路由,漏了不会报错、只会静默失效。

此后的变化

  • 2026-W38 →

    新增 AgentRail.callback_priority(event),允许同一个 rail 为不同事件声明不同的钩子优先级,而不必更改其整体注册顺序,修复了可观测性 rail(优先级 10)结束工具 span 早于 evolution rail(优先级 60+)排空订阅、导致 AFTER_TOOL_CALL 触发条件从未真正触发的问题。本格此前只描述 rail 是按优先级排序的类,未涉及「同一个 rail 内不同事件可有不同优先级」这层细节。

Codex

11 个钩子事件,由配置声明、以外部命令或 MCP 调用执行

钩子写在 hooks JSON 或配置里,事件名就是这 11 个:工具调用前后、权限请求、压缩前后、会话开始与结束、用户提交提示、子代理开始与停止、Stop。其中 9 个支持 matcher,可以只对某个工具、某种压缩触发或某个会话来源生效。执行端在 engine/ 下有 command_runner 与 mcp_runner 两种——钩子是进程外的命令或 MCP 调用,而不是进程内回调;插件也可以通过 declarations 声明自带的钩子。「生命周期挂载点」一行里,Codex 是唯一把挂载点做成面向最终用户配置、而不是面向开发者编程的实现。

适合
让最终用户或运维用配置文件接入团队规范,而不改代码。
代价
钩子是外部命令,每次调用都有进程开销,能做的事受限于事件载荷。

此后的变化

  • 2026-W42 →

    钩子事件由 11 个增加到 13 个:新增 PreModelRequest 与 PostModelRequest,包住每次模型请求(含重试),只观察不控制,输出不能否决请求或改写上下文。同周插件钩子的审批改为绑定插件包内容哈希,脚本变更后需重新审批。

  • 2026-W37 →

    钩子拿到的设置口径变了:#45248 之后,前置/后置工具钩子与权限请求钩子接收的是实际发出该次调用那一步的模型、推理强度与审查标志,而非回合开始时的设置,hook_runtime.rs 与 turn_metadata.rs 同步改写。本格此前只描述挂载点在哪里,未涉及挂载点看到的设置归属于哪一层。

DeepSeek Harness

原生挂载点是类型化的拦截点,钩子包只是把别家的 hooks.json 翻译过来

DeepSeek 真正的扩展面是一组类型化拦截点(agent/session-start、agent/pre-step、tools/pre-execute、tools/post-execute、agent/turn-stopping 等),任何插件都能以中间件方式挂上去,调用 next() 把决定交给下游。hooks 目录下的包不是另一套机制,而是桥:hooks-codex 与 hooks-claude-code 把现成的 hooks.json 翻译到这些拦截点上,按各自方言映射事件与返回值——例如 Codex 的 PreToolUse 只支持拦截,不支持放行或询问。代码里留着一条坦白的 TODO:它传给 Stop 钩子的 stop_hook_active 永远是 false,一个无条件阻止的 Stop 钩子会让每一步都被迫继续,直到钩子自己收手。

适合
既要自己的强类型扩展面,又想兼容用户现有的 Claude Code / Codex hooks.json。
代价
桥接要逐条对齐各家方言,语义差异(如只支持拦截不支持放行)需要文档说明。
Pi

34 个进程内事件,带返回值的事件可以改写行为

扩展是一个拿到 ExtensionAPI 的工厂函数,用 on 订阅事件:项目信任、资源发现、会话的开始切换分叉压缩与结束、上下文组装、厂商请求前后、Agent 与回合的开始结束、消息与工具执行的流式更新、模型与思考强度切换、工具调用与结果、用户输入等共 34 个。签名里第二个类型参数就是返回值:ContextEvent、ToolCallEvent、BeforeProviderRequestEvent 这类事件的处理器可以返回结果来改写上下文、拦截工具或修改请求,其余只是观察。同一个 API 还提供 registerTool、registerCommand、registerShortcut——沙箱、子代理这些在别家内置的能力,在 Pi 都是从这里挂进来的。

适合
扩展要深度改写行为(上下文、请求、工具)并注册工具与命令。
代价
扩展与宿主同进程运行,写坏了会直接影响主进程。
横向读这一行

挂载点面向的人不同,形态就不同。Codex 面向最终用户:11 个事件写在配置里,钩子是外部命令或 MCP 调用;openJiuwen 与 Pi 面向开发者:前者是按优先级排序的 rail 类,后者是 34 个进程内事件,带返回值的事件可以改写行为;DeepSeek 两头都要——原生扩展面是类型化拦截点,再用桥接包把 Codex 与 Claude Code 的 hooks.json 翻译过来。挂载点越接近用户配置,越安全也越弱;越接近代码,越强也越容易静默出错。

这个问题为什么存在

第 3 章

Harness 是什么

围绕模型的编排框架:它编码了「模型独立完成不了什么」