第 08 章 · 四层递进与确定性护栏
本篇目标:把「建议」升级成「强制」。 一句话:规则文件约 70% 被遵循,hook 是 100%。社区
8.1 四层递进:不要一次上齐
社区 单人 / 团队 Harness 的通行分层(aiworkflowpro、amux 等指南的共同骨架):
| 层 | 加什么 | 难度 | 解决的失败 | 前置条件 |
|---|---|---|---|---|
| L1 约束 | AGENTS.md / CLAUDE.md,3–5 条起步 |
低 | 最常见的高频错误 | 无 |
| L2 工具 | MCP、自定义工具、脚本化传感器 | 中 | Agent 看不见的信息 | L1 稳定 |
| L3 校验 | hooks:PreToolUse / PostToolUse / Stop | 中高 | 规则被静默跳过 | L1/L2 稳定 |
| L4 编排 | 子 Agent、多 Agent、worktree、任务板 | 高 | 单 Agent 上下文不够 | L3 稳定 |
顺序不可跳。每一层都依赖下层已经稳定;四层一起上,出问题时无法定位是哪层的锅。社区反复出现的教训是「过度设计控制流」——为当前模型弱点写的复杂 pipeline,下一次模型升级就报废。
社区 一条克制的工具原则(Cat Wu):除非一个工具被证明能提升效果或准确率,否则不要加。工具更少 = Harness 更可控。第 09 章给出成本量化。
8.2 Guides / Sensors 四象限
社区 Böckeler 的框架按两个轴切分:Guide 事前引导,Sensor 事后反馈;每类再分确定性 / 推断性。
| Computational(确定性) | Inferential(模型判断) | |
|---|---|---|
| Guide(行动前) | 脚手架脚本、文件模板、typecheck 配置 | AGENTS.md、Skill、系统提示词 |
| Sensor(行动后) | 测试、linter、行数限制、构建 | LLM-as-judge、AI code review、eval suite |
两条结论:
- 只有一半会失败:只有 sensors,Agent 会重复犯错;只有 guides,你永远不知道规则有没有生效。
- 能确定性就别用模型判断:「A test gives the same answer every time and costs nothing to rerun」。推断性 sensor 自身还需要被评估,且应固定
temperature=0。
判据 10:任何「必须 100% 生效」的约束,落在左侧(computational)。落在右侧的规则只是概率性建议。
8.3 Hooks:从建议到强制的机制
官方 Claude Agent SDK 的 hook 事件,按阶段分组(这是目前公开最完整的参考实现):
| 阶段 | 事件 | 能做什么 |
|---|---|---|
| 工具前 | PreToolUse |
阻断 / 修改工具调用(exit code 2 = 阻断) |
| 工具后 | PostToolUse、PostToolUseFailure、PostToolBatch |
注入校验结果、写审计 |
| 回合 | UserPromptSubmit、Stop、StopFailure |
注入上下文、结束前自检 |
| 会话 | SessionStart、SessionEnd、InstructionsLoaded |
初始化、审计加载了哪些规则 |
| 压缩 | PreCompact、PostCompact |
压缩前归档完整 transcript |
| 子 Agent | SubagentStart、SubagentStop、TeammateIdle |
并行任务追踪 |
| 任务 | TaskCreated、TaskCompleted |
任务关闭前要求测试通过 |
| 隔离 | WorktreeCreate、WorktreeRemove |
工作树生命周期 |
| 权限 | PermissionRequest、PermissionDenied |
自定义审批 |
| 其他 | ConfigChange、FileChanged、CwdChanged、Notification |
动态重载 / 告警 |
Hook 类型:command(shell,JSON 走 stdin/stdout)、http、mcp_tool、prompt、agent。
三个可直接改用的模式
① PreToolUse 拦截危险写操作 官方
{
"hooks": {
"PreToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "if echo \"$TOOL_INPUT\" | grep -qi '\\.env'; then echo 'BLOCKED: .env 不允许写入' >&2; exit 2; fi"
}]
}]
}
}
注意 matcher 的边界:只匹配
Write|Edit时,用 Bash 写.env不会触发。要覆盖子进程,必须同时配 sandbox 的文件系统 deny 规则。官方 权限与沙箱是互补的两层:权限管「哪些工具/文件/域名」,沙箱管「OS 级 Bash 与其子进程」。
② PostToolUse 注入失败信号 社区
{
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [{ "type": "command", "command": "npx tsc --noEmit 2>&1 | head -20" }]
}]
}
}
社区 Addy Osmani 概括得好:"success is silent, failures are verbose"。通过时模型什么都听不到(零延迟),失败时错误文本被注入 loop,模型自纠。
③ Stop 前定义「Done」 社区
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "prompt",
"prompt": "结束前确认:(1) 测试是否通过?不确定就跑一次。(2) typecheck 是否通过?(3) 是否改动了任务范围外的文件?任一为否,继续工作。"
}]
}]
}
}
判据 11:「Done 必须是一个可判定的状态,不是一种感觉」。没有硬退出条件,Agent 要么过度扩张,要么在测试还是红的时候宣布完成。这与第 15 章 Terminal Bench 2.0 的 self-verification middleware 是同一个机制。
8.4 在 DSH 里的等价物
官方 docs/architecture.md 的扩展点表,可以直接对上:
| 通用模式 | DSH 接缝 |
|---|---|
| PreToolUse | tools/pre-execute(waterfall,监听器需 next()) |
| PostToolUse | tools/post-execute、审批策略 |
| Stop / 完成判定 | agent/turn-stopping(串行事件,无 next()) |
| 请求前改写 / 拒绝 | agent/pre-step(可 accept / rewrite / reject) |
| 权限 allow/deny | subsystems/permission-presets、subsystems/approval |
| 沙箱 | ctx.sandbox backend,消费者在 spawn 前包装 argv |
| 注入上下文 | agent.inject()(正规入口,落在下一次被准入的请求) |
官方 提醒:waterfall 类监听器必须调用 next() 委托,否则截断流水线——这是写 DSH 插件时最常见的「加了插件后什么都不动」的原因。
8.5 反模式清单
| 反模式 | 为什么坏 | 替代 |
|---|---|---|
| 把禁令全写进系统提示词 | 概率性生效,长任务后半段会被压掉 | 迁到 hook + sandbox |
| 每一步都弹人工审批 | 「A gate on every step trains reviewers to click Accept」社区 | gate 只放 side effect 前 |
| 用 LLM 判断「格式对不对」 | 慢、贵、结果漂移 | linter / typecheck / coverage test |
| 为当前模型弱点写复杂 pipeline | 下个版本模型升级即报废 | 写永久性约束,月度复审删除临时规则 |
| hook 里跑长耗时的全量测试 | 每个 tool call 都触发,拖垮交互 | 分级:快检查每次跑,全量放 Stop / CI |
| 静默 fallback | 「A missing placement fails the build, and a missing sandbox fails the run」社区 | fail closed |
8.6 本章产出与验收
核心产出:一个 settings.json(或 DSH 对应 patch),含至少 1 条 PreToolUse 阻断、1 条 PostToolUse 反馈、1 条 Stop 自检;以及一次「故意违规被拦下」的截图。
验收点:
- 能用 exit code 2 真的阻断一次危险操作,并确认模型收到了阻断原因
- 能解释为什么 matcher 只写
Write|Edit挡不住 Bash 写文件 - 能把一条现有
AGENTS.md规则,升级成确定性 hook - 能说出 waterfall 监听器忘记
next()会发生什么 - 能指出自己 Harness 里哪条 gate 是多余的
延伸阅读
| 来源 | URL | 等级 |
|---|---|---|
| Claude Agent SDK · Hooks(完整事件表) | https://code.claude.com/docs/en/agent-sdk/hooks | 官方 |
| Claude Code · 自定义子 Agent 与 hooks | https://code.claude.com/docs/en/sub-agents | 官方 |
| Claude Code · 权限与沙箱互补 | https://code.claude.com/docs/en/permissions | 官方 |
| Claude Code · Sandboxing | https://code.claude.com/docs/en/sandboxing | 官方 |
| DSH 架构 · 扩展点表 / 事件域 | https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md | 官方 |
| 官方文档 · 审批 / 权限预设 | https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/approval | 官方 |
| Böckeler, Guides / Sensors | https://martinfowler.com/articles/harness-engineering.html | 社区 |
| Harness Engineering 完整指南(四层递进) | https://aiworkflowpro.com/harness-engineering-guide/ | 社区 |
| amux, The Complete Guide(hook 示例) | https://amux.io/guides/harness-engineering | 社区 |
| codeongrass, The Outer Harness(Stop hook) | https://codeongrass.com/blog/outer-harness-real-work-ai-coding-agents | 社区 |