可以把模型想成提出操作建议的工程师,把 Harness 想成车间。工程师可以说“启动这台机器”,但车间仍要确认机器是否存在、操作是否安全、谁有权限、运行范围多大,以及结果记录在哪里。
读完这篇,你应该能回答:模型输出一个工具调用后,究竟是谁执行?Harness 最少有哪些部件?Prompt 里写“不要联网”和真正关闭网络有什么区别?
术语说明:厂商对 Harness 的覆盖范围并不完全相同。本系列采用便于学习的较窄定义:Harness 是模型之外、一次运行之内的执行环境;Agent Loop 是驱动模型与 Harness 反复交替的流程。篇末社区教程只作延伸阅读。
一、先看懂建议怎样变成真实动作
1.1 Harness 到底是什么
Harness 是包在模型外面、把模型建议变成受控真实动作的软件环境。Harness engineering 就是设计这套环境里的工具、安全规则、执行空间、状态记录和恢复方式。
它之所以必要,是因为模型本身只产生文字或结构化的动作提议。模型可以建议读取文件、修改代码、运行命令,但真正接触文件系统、进程、网络和凭据的是外层程序。把这层程序设计好,Agent 才不只是“会说”,而是能够可靠地“做”。
1.2 跟着一次运行测试,看清 Harness 的组成
模型准备验证支付模块,提出“运行 payment tests”。Harness 接手后,会按顺序完成下面这些工作:
- 找到工具:确认系统确实提供“运行测试”这项能力,而不是让模型随意发明命令接口。
- 检查输入:确认测试目标、工作目录和参数有效,缺失时返回明确错误。
- 判断权限:这次任务是否允许启动进程?若动作风险更高,是否必须先让人批准?
- 限制范围:即使允许执行,也只开放需要的仓库目录、进程和网络范围。
- 真正执行:管理超时、取消和资源限制,启动测试进程。
- 整理结果:完整日志存到外部文件,只把退出码、关键错误和日志引用送回模型。
- 留下记录:记录动作何时开始、是否完成和改变了什么,保证中断后能够判断是否需要继续。
少了其中任何一步都会出现具体问题:没有输入检查,错误参数进入真实环境;没有权限和隔离,Prompt 里的“请小心”挡不住危险动作;没有结果和状态记录,进程崩溃后只能靠模型猜之前发生了什么。
把这次 Tool Call 放回完整运行,就是一条反复交替的链:Harness 准备请求 → 模型提出动作 → Harness 检查并执行 → 结果成为 observation → observation 进入下一次请求 → 模型再决定下一步。Harness 承载每次动作;Agent Loop 决定这条链是否还要再走一轮。
二、拆开这套行动环境的关键部件
2.1 把七步收拢成一套运行环境
模型擅长在不完整信息下提出计划;运行时必须把不确定性关在确定边界里。什么工具对当前角色可见、参数是否符合 schema、哪些目录可写、网络连到哪里、结果如何序列化,都应由代码和策略拥有,而不是由模型临场决定。
最小 Harness 只要能准备请求、提供工具、检查输入与权限、受限执行并返回结果。成熟系统为了可重现和可恢复,会再补上版本化工具目录、事件记录、进度观察、artifact 和 checkpoint。下面的表把这两层放在一起,便于工程评审。

| 控制面 | 拥有的事实 | 不能外包给模型的原因 |
|---|---|---|
| Request assembler | 模型、instructions、tools、context view、预算 | 请求必须可重现、可观测 |
| Tool registry | 名称、schema、能力、版本、结果类型 | 不能让模型发明真实接口 |
| Policy / permission | allow、deny、ask、作用域 | 安全边界需要确定性 |
| Sandbox / executor | 文件、进程、网络、资源限制 | 提示词不能隔离副作用 |
| State / transcript | run 状态、事件、结果引用、checkpoint | 恢复与审计需要持久事实 |
| Observer / verifier | 进度、指标、执行证据 | 自我报告不能成为唯一真值 |
Harness 负责记录并暴露 evidence;一次 run 是否继续由 agent loop 决定,跨任务和跨版本的结果质量是否成立则由 evals 判断。Verifier 可以在 harness 内执行测试,却不能因此独占产品真值。
2.2 一次 Tool Call 要经过七个关口
Tool call 是模型输出的一段结构化提案。一个稳健 harness 不会把它直接映射成函数调用,而会把“解释、授权、执行、观察”拆开。

- Parse:确认 tool call 结构完整,关联到当前请求。
- Resolve:在当前可见 registry 中找到精确工具版本。
- Validate:按 schema 检查类型、范围、互斥和必填字段。
- Authorize:结合角色、资源、动作和环境决定 allow / deny / ask。
- Isolate:建立文件、进程、网络和凭据边界。
- Execute:管理超时、取消、并发、重试与幂等键。
- Normalize:把 stdout、结构结果、错误与 artifact 变成稳定 observation。
async function dispatch(call, run) {
const tool = registry.resolve(call.name, run.toolsetVersion)
if (!tool) return observe("unknown_tool", call.id)
const args = tool.schema.parse(call.arguments)
const decision = policy.decide(run.actor, tool.capability, args)
if (decision === "deny") return observe("denied", call.id)
if (decision === "ask") return pauseForApproval(run, call)
const box = await sandbox.open(tool.limits)
events.append("tool.started", { runId: run.id, callId: call.id })
try {
const raw = await executor.run(tool, args, { sandbox: box })
return normalize(raw, artifactStore)
} finally {
await box.close()
}
}while 循环。是否再次调用模型,是下一篇 agent loop 的责任。{
"call_id": "call_42",
"tool": "run_tests@3",
"arguments": { "target": "./payment", "mode": "read-write" },
"policy": { "decision": "allow", "rule": "repo-test" },
"sandbox": { "fs": "workspace", "network": "off", "timeout_s": 120 },
"result": { "status": "failed", "exit_code": 1, "artifact_ref": "log://9f2" }
}同一个 call_id 会留下三种不同用途的事实:artifact store 保存完整日志,事件或调用账本保存 proposed、allowed、started、completed 等状态,model view 只接收足够决定下一步的 observation。恢复时,Runtime 先读取账本并与真实环境对账,必要时再按引用打开 artifact;它不会把一段聊天记录当成副作用是否发生的权威答案。
2.3 工具设计决定 Agent 会做哪些动作
Prompt 篇关心模型看到的工具说明,以及“应该怎么用”;本篇关心 registry、版本、schema validator、权限、dispatcher 和 artifact,以及“实际上能不能执行”。工具不是越多越好:两个语义重叠的写工具、一个万能 shell 和模糊的自由文本参数,会让动作空间膨胀。模型既要猜工具,又要猜参数,还要从非结构输出里判断成功。
2.3.1 一个好工具要同时服务模型与运行时
| 维度 | 给模型的界面 | 给运行时的界面 |
|---|---|---|
| 语义 | 何时调用、何时不调用 | 能力 id 与版本 |
| 输入 | 少量清晰字段 | 严格 schema 与规范化 |
| 输出 | 足够决定下一步 | typed result、artifact 与错误分类 |
| 副作用 | 明确会改变什么 | 权限、幂等、补偿和审计 |
| 成本 | 何时值得调用 | 超时、速率、token 与资源预算 |
大量输出不要整段塞回 context。Harness 可以把完整日志存成 artifact,只回填摘要、关键错误和可继续读取的引用。这同时改善 context budget 与恢复能力。
2.4 允许执行,不等于可以触达一切
Permission 回答“这个主体是否被允许做这个动作”;sandbox 回答“即便允许,动作实际能触达什么”。批准运行测试不等于允许访问整个主目录;允许读取仓库不等于允许把内容发到任意网络端点。二者必须组合。

- Capability scope:先不把无关工具暴露给模型。
- Policy decision:按动作、资源、主体和环境匹配规则。
- Human approval:只在高风险或含糊边界暂停,并展示可理解的 diff。
- Sandbox:用文件系统、网络、进程和凭据隔离限制 blast radius。
- Audit:记录谁提出、哪条规则允许、实际发生了什么。
三、进阶:让真实动作可恢复、可追查
3.1 进程中断后,不能只靠聊天记录恢复
Anthropic 的 Managed agents 把 session 看作围绕模型、harness 和 sandbox 的有状态环境。工程上至少要区分:UI 对话、模型可见 messages、append-only 事件、外部 artifact、checkpoint 与环境快照。它们生命周期不同,也服务不同恢复路径。
中断恢复时,最危险的做法是仅把旧聊天重新发给模型。运行时还需要知道哪些工具调用真正执行过、哪些在途、工作区是否改变、审批是否仍有效、外部对象是否已经创建。副作用状态必须从确定性记录恢复,不能让模型根据文本猜。
tool.proposed → tool.validated → policy.allowed → tool.started
→ runtime.crashed → run.recovered → effect.reconciled
→ observation.readycall_42 只有 started 没有 completed,runtime 先查询环境。只读动作可受限重放;带幂等键的写动作先对账;非幂等且状态未知的动作必须进入 blocked 或人工升级,不能直接再执行一次。3.2 用户和系统都要看见动作进行到哪里
只有最终文本和总 token 数,无法诊断 harness。需要把一次 run 串成 trace:request assembly、model events、tool proposal、policy decision、sandbox execution、observation、checkpoint 和 exit reason。敏感正文可以脱敏,但控制事件不能消失。
- 稳定的 run / turn / call id 关联所有事件;
- 记录 tool latency、排队、重试、取消和 artifact 大小;
- 区分模型错误、工具错误、策略拒绝、环境错误和用户中止;
- 保留退出原因与未完成工作,供 outer loop 判断下一步。
四、复盘:一个 Harness 至少回答六个问题
| 评审问题 | 如果答不出来 | 风险 |
|---|---|---|
| 当前模型究竟能看到哪些能力? | 没有版本化 registry | 能力漂移和错误选择 |
| Tool call 在哪里被确定性校验? | 依赖模型自检 | 非法参数进入副作用层 |
| 授权与隔离各由谁负责? | 只有 prompt 警告 | blast radius 无边界 |
| 大结果和秘密怎样处理? | 原样回填 context | 泄漏与窗口污染 |
| 进程中断后以什么事实恢复? | 只重放聊天 | 重复副作用与错误状态 |
| 每个退出是否有可机读原因? | 只有 final 文本 | Outer loop 无法安全接管 |
Harness 把“模型能想到”变成“系统能安全做到”。下一篇进入它内部不断前进的控制流:Agent loop 怎样让一次运行正确收敛。
官方资料
- OpenAI:Harness engineering
- OpenAI:Unrolling the Codex agent loop
- Anthropic:Building and evaluating trustworthy agents
- Anthropic:Managed agents
- Anthropic:Harness design for long-running applications