M9 · Harness 治理框架与 Hook Pipeline#
简历 Bullet Point: 抽象 Agent 全生命周期 Hook Pipeline(6 Hook 点 / 15+ Hook 模块),从 400 行意大利面适配器重构为声明式装饰器注册,新增检查只改一个文件一行装饰器;所有控制面(截断/熔断/阶段门/漂移检测/终结纪律/预算闸/输出审核)统一 Hook 化
开场钩子#
M9 之前,所有运行时管控(截断、循环检测、检索预算、fork 预算、终结纪律)散在 middleware.py 的一个 400 行类里——ToolGuardMiddleware。每加一道检查就往这个类里塞代码,相互耦合、难以测试、改一处崩一片。
重构成 Hook Pipeline 后:6 个 Hook 点,每个检查是独立函数 + @harness_hook 一行装饰器注册。新增检查 = 加一个文件,不碰任何现有逻辑。
一、模块运作流程#
1.1 六个 Hook 点#
| Hook 点 | 触发时机 | 典型 Hook |
|---|---|---|
on_session_start | 会话开始 | 阶段机复位、偏好注入 |
pre_think | 模型调用前 | 压缩、预算路由、reasoning_boost |
pre_tool_call | 工具执行前 | 阶段门、安全白名单、熔断器、深度闸 |
post_tool_call | 工具执行后 | 截断、循环检测、收尾催促、断言 |
post_reflect | 模型输出后 | 漂移检测、终结纪律、阶段转移 |
on_session_end | 会话结束 | 输出审核、脱敏 |
1.2 注册机制#
@harness_hook("pre_tool_call", priority=10)
async def phase_check(ctx, tool_name, tool_input):
if not phase_machine.allows(tool_name):
raise HookRejectSignal(sentinel_message)pythonpriority越小越先跑,保证执行顺序(如熔断闸最后、search_authority 早于 retrieval_charge)HookRejectSignal阻止工具执行,返回哨兵消息给模型- 异常不中断主流程(单个 Hook 崩不影响其他 Hook 和 Agent)
1.3 当前注册的 Hook 模块#
context_compress / drift_detector / phase_check / phase_transition / preference_inject / reasoning_boost / result_guard / security / session_hooks / step_validator / terminal_enforce / tool_breaker / tool_gates / tool_memo / assertion_handler
1.4 与中间件的分工#
Hook 能改(拦截工具、注入消息、修改状态)。LangChain Callback 只能看(AGUI 事件上报、工具 RT metrics、token 记账)。中间件栈只剩唯一适配器 HarnessAgentMiddleware。
二、踩坑实录#
坑 1:Hook 空转——注册了但从未被调用#
- Harness 断言/漂移检测在 M12 之前曾完全空转。原因是适配器没有调用对应 Hook 点。加端到端测试确认各闸真实开火。
坑 2:priority 契约易碎#
- 熔断闸必须在阶段门之后(先确认工具合法再看熔断状态)。priority 数字约定没有测试保护。加测试钉死关键 priority 顺序。
坑 3:删除旧中间件时兼容层取舍#
- 删
ToolGuardMiddleware/ContextCompressionMiddleware时不留兼容层。因为留了只会让新旧并存、行为不可预测。
三、面试问答#
Q1: 这种 Hook 模式和 Web 中间件有什么区别?#
粒度更细(6 个 Hook 点 vs request/response 两个)、context 更丰富(能看到工具参数/返回值/历史轨迹/阶段状态)。本质是 Web middleware 模式在 AI Agent 领域的领域特化。
Q2: 为什么不用 LangGraph 的 StateGraph 做控制?#
StateGraph 适合”确定性条件分支”(if A then B)。Harness Hook 适合”横切关注点”(每个工具调用都要检查的通用逻辑)。两者正交:StateGraph 管流程图拓扑,Hook 管运行时行为约束。
Q3: Hook 之间如何通信?#
通过 HarnessState(共享状态对象)。Hook 不直接依赖其他 Hook,但可以读写同一个 state(如 terminal_reached、phase)。
四、诚实边界#
| 维度 | 做了 | 没做 |
|---|---|---|
| Hook Pipeline | 6 点 / 15+ 模块 | 运行时动态启停单个 Hook |
| 测试 | priority 契约测试 | 全组合交互测试 |
| 可观测 | 日志记录各 Hook 触发 | Hook 执行时间 metrics |