Agent Harness Engineering
一个生产级 Agent 里,98% 不是 AI
「换个更聪明的模型」常常不如「把包裹模型的那层壳做好」。本版在概念之外,补齐了可参考的伪代码、逐层机制、主流框架对照与一次完整的「修 bug」走查。
0 · 30 秒速览
一句话:Agent Harness(外壳/运行环境),是包在大模型外面、负责「给它喂上下文、管它能用哪些工具、拦住危险动作、记录它每一步、出错帮它兜底」的一整套工程基础设施。模型只负责「想」,harness 负责把「想」变成可靠、可审计的「做」。
有个数字最能说明问题:MBZUAI 在 2026 年 5 月分析 Claude Code 源码后估算——一个生产级 Agent 里约 98.4% 是 harness 基础设施(权限、上下文管理、沙箱、工具路由、恢复),真正的「AI 决策逻辑」只占约 1.6%(来源)。也就是说,当大家都能调到差不多强的模型时,产品好不好用,几乎全押在这层壳上。
📖 本版阅读建议:第 1–3 节是概念地基;第 4–7 节是逐层机制 + 伪代码(想动手实现看这里);第 8 节是Claude Code / Cursor / Copilot / Codex 真实对照;第 9 节用一次「修 bug」把所有层串起来走一遍。
1 · 为什么需要「外壳」——先说痛点
光有一个聪明的模型,为什么造不出可靠的 Agent?因为把模型直接扔进真实环境里干活,会立刻撞上四堵墙:
业界一个扎心的数字:95% 的 AI 项目从未真正上线,瓶颈往往不是模型能力,而是「上下文交付」这类工程问题(来源)。harness 工程,就是来推倒这四堵墙的。
2 · Agent Harness 到底是什么
说人话:harness 不是 Agent 本身,而是包裹 Agent 的那一整套基础设施——它能用哪些工具、有哪些护栏、靠什么反馈自我纠正、人怎么监控它。(来源)
所以「Harness Engineering(外壳工程)」这门手艺的本质,是工程师的角色从「自己写代码」变成「设计一套系统,去管住 Agent 怎么写代码 / 怎么干活」。它也被一些人称作 Agent 的「控制面(control plane)」。
3 · 分层解剖:一个 harness 由哪些层组成
把定义里那串职责拆开,一个完整 harness 大致是这么几层「围着模型」叠起来的。记住这张分层图,后面每一节都在讲其中一层的内部机制:
| 层 | 管什么 | 典型内容 | 本文 |
|---|---|---|---|
| ① 上下文 / 记忆 | 模型每次「看到什么」、任务进行到哪 | 上下文组装与裁剪、压缩(compaction)、项目记忆、任务状态 | §6 |
| ② 工具与权限 | 它能调哪些工具、动作危不危险 | schema 校验、权限分级、沙箱隔离、MCP 接入 | §5 |
| ③ 执行环 | 怎么把「想」变成有节奏的「做」 | 计划-执行-验证循环、停止条件、人类审批门、子 Agent 委派 | §4 |
| ④ 可观测性 | 看得见、出错查得到、花钱控得住 | tracing、日志、指标、评测、审计、成本控制 | §7 |
这也解释了为什么之前几期的主题(MCP、沙箱、上下文工程、多智能体、记忆、评测)其实都是 harness 的零件——而外壳工程,是把这些零件装成一台能上线的机器。
4 · Agent Loop 引擎:机制 + 伪代码
harness 的执行层,机械上「往往就是一个 while 循环——复杂度不在循环本身,而在这个循环所管理的一切」(来源)。先看这个循环长什么样:
4.1 循环骨架(带预算与停止条件)
下面是一段提炼自公开实现的伪代码(概念示意,非可直接运行),把「预算、停止条件、结果回灌」这些关键点标出来:
# 概念伪代码:一个带边界的 agent 循环
def run_agent(task, tools, budget):
ctx = build_context(task, tools) # §6 组装初始上下文
turns = 0
while True:
# ---- 停止条件:任何一条命中就退出(防无限循环)----
if turns >= budget.max_turns: return stop("超出最大步数")
if budget.tokens_left <= 0: return stop("超出 token 预算")
# ---- 接近上下文上限则先压缩(§6)----
if ctx.size() > CONTEXT_LIMIT * 0.9:
ctx = compact(ctx)
reply = model.call(ctx) # 唯一一次「AI 决策」发生的地方
turns += 1
if not reply.tool_calls: # 模型没再要工具 → 收工
return done(reply.text)
for call in reply.tool_calls:
result = execute_tool(call) # §5:校验+权限+沙箱执行
ctx.append(observation=result) # 结果回灌,拒绝/超时/报错也算
if stuck_detected(ctx): # 连续 3 次相同调用 → 判定卡死
return escalate_to_human(ctx)
4.2 关键细节:每个结果都是「观察」,卡死要能识别
两个容易被新手漏掉、却决定 Agent 稳不稳的机制:
- 结果回灌要「诚实」。拒绝、超时、参数错误、中止——这些「坏结果」也是观察,必须原样回灌给模型当反馈,而不是悄悄吞掉。Agent 的自我纠错能力,全靠这些诚实反馈(来源)。
- 加一个「卡死检测」启发式。一个简单有效的做法:如果最近三次工具调用完全相同,就跳出循环、把当前状态交给用户(来源)。这直接对应近期「Agent 无限循环」的失败模式。
「计划-执行-验证(Plan–Execute–Verify)」是这个循环的一种有结构的版本:计划阶段好的 harness 会先做「仓库影响图」——动手前跑符号分析,搞清任务会碰到哪些文件(Red Hat 实践,来源);执行阶段模型只产出结构化调用交给 harness(§5);验证阶段用测试/规范检查结果,没过就再转一圈。
5 · 工具与权限层:整个 harness 的命门(含代码)
如果只能记住一条 harness 铁律,就是这条:
5.1 一次工具调用要过的「预执行关卡」
harness 在真正执行工具前,会连问几个问题(pre-execution gates):这是已知工具吗?参数合法吗?这个动作需要用户批准吗?请求的路径在工作区内吗?(来源)。落成代码大致长这样:
# 概念伪代码:工具执行的校验层(模型永远碰不到真实副作用)
def execute_tool(call):
# ① schema:是不是已知工具、参数结构对不对
if call.name not in TOOL_REGISTRY: return deny("未知工具")
if not validate_schema(call.args, TOOL_REGISTRY[call.name].schema):
return error("参数不合法") # 回灌给模型,让它改
# ② 边界:路径/资源是否越界
if not path_inside_workspace(call.args):
return deny("越出工作区")
# ③ 权限:按风险等级决定放行 / 审批 / 拒绝
risk = classify_risk(call) # 见 5.2
decision = permission_gate(risk, call) # allow | ask | deny
if decision == "deny": return deny("策略禁止")
if decision == "ask": wait_for_human_approval(call) # 高危动作停在这
# ④ 沙箱执行 + 记录(§7)
result = sandbox.run(call)
audit_log.write(call, result)
return result
5.2 按「风险」给动作分级
不是所有工具调用都一样危险。harness 要按风险走不同的权限路径(来源):
| 风险级别 | 动作举例 | 建议策略 |
|---|---|---|
| 低 | 读文件、查数据、起草(draft) | 可自动放行 |
| 中 | 写文件、改配置 | 沙箱内执行 + 记录 |
| 高 | 对外通信、发邮件、调外部 API | 需策略校验 / 人类审批门 |
| 极高 | 金融操作、删除、提权等破坏性/特权动作 | 强制人工确认,绝不自动执行 |
可以用一份声明式配置来表达这套分级,让策略和代码分离:
# 概念示例:声明式权限策略(风险→路径)
policies:
read_file: { risk: low, action: allow }
write_file: { risk: medium, action: allow, sandbox: true }
http_request: { risk: high, action: ask } # 弹审批
send_email: { risk: high, action: ask }
delete_path: { risk: critical,action: ask, require: human }
run_migration: { risk: critical,action: deny } # 直接禁
5.3 人类审批门:动作被「冻结」而不是被执行
高危动作命中 ask 时,一个优雅的实现是:当 Agent 走到写操作,harness 不执行,而是把这个待执行的调用序列化、发出一个审批请求、阻塞执行,并把 Agent 的状态「停泊」在这个写节点上,完整保留现场,直到收到批准信号才继续(来源)。好处是:人不在时任务不会失败,而是安全地暂停等待。
5.4 两类控制协同
预防性控制(preventive)在坏结果发生前就拦住;纠正性控制(corrective)在检测到违规后触发响应(来源)。工具箱本身则常通过 MCP 扩展——把 CI 状态、部署日志、运行指标接成 Agent 可用的实时数据源;MCP 月 SDK 下载已达约 9700 万次,几乎所有主流编码 Agent 都支持它(来源)。
6 · 上下文与记忆:压缩(compaction)机制
模型的上下文窗口是有限的,而长任务的历史会越堆越长。harness 必须在「快撑满时」把上下文压小,又不能压丢关键信息——这就是 compaction。
# 概念伪代码:接近上限时触发压缩
def compact(ctx):
# 1) 裁剪:超长工具输出/文档只留头尾
for msg in ctx.tool_outputs:
if len(msg) > CLIP_LIMIT:
msg = msg[:800] + " …[截断]… " + msg[-400:]
# 2) 归纳:把早期回合压成摘要,保留近期原文
old, recent = ctx.split_at(keep_last=RECENT_N)
summary = model.summarize(old, keep=["决定","改动","待办"])
return Context(summary + recent)
工程上还有个实用挂钩:Claude Code 提供 PreCompact 钩子,在压缩前触发,让你先把重要状态快照下来,免得早期回合被摘要抹掉(来源)。这就是「记忆层」和「可观测层」联动的一个例子。
7 · 可观测性与失败恢复
「看不见就没法改」——可观测性是 harness 里最容易被省略、又最不该省的一层。
成熟一点的做法,是一整套横跨所有环节的可观测层:tracing(追踪)、logging(日志)、metrics(指标)、evals(评测),配合 Opik、Langfuse、Braintrust 这类工具(来源)。这也和「Agent 评测需要 trace 埋点做前提」接上了——评测其实是可观测层之上的一个应用。
更进一步,可观测性能反过来驱动 harness 自我进化:arXiv 上已有「观测驱动的、自动演化 harness」的工作(Agentic Harness Engineering,arXiv 2604.25850)——用线上观测到的失败,自动改进外壳配置。
8 · 真实框架对照:各家的 harness 长什么样
「模型趋同、harness 决定体验」在真实产品上体现得很明显。下面对照四家主流编码 Agent 的外壳能力(据 2026 年公开对比,来源1、来源2):
| 维度 | Claude Code | Cursor | GitHub Copilot | Codex |
|---|---|---|---|---|
| 形态 | CLI / 终端 | AI 原生 IDE | IDE 插件 + 云 | CLI / 云异步 |
| 配置载体 | 规则文件 + Hooks + Skills + Subagents | Rules、MCP、Hooks、Skills、Plugins、Subagents | Issues/PR/CI 原生集成 | .codex/agents 配置 |
| 工具协议 | MCP | MCP(还读 .claude/.codex 配置) | MCP | MCP |
| 权限机制 | PreToolUse 钩子 + 权限模式(见下) | 规则 + 钩子 | 企业级策略控制 | 沙箱 + 审批 |
| 擅长 | 复杂推理、多文件协同改 | 最深的 IDE 内 AI 体验 | 最低迁移成本、企业管控成熟 | 无人值守长任务、开 PR |
值得注意:Cursor 会去读 .cursor、.claude/agents、.codex/agents 多套配置,而同一个 MCP 服务器(如 Firecrawl)用一套配置就能在 Claude Code、Codex、Cursor、Copilot、Gemini CLI、OpenCode、Devin 之间通用(来源)——这正是 harness 层标准化带来的红利。
8.1 以 Claude Code 为例:权限模式 + PreToolUse 钩子
Claude Code 把第 5 节讲的「权限层」做成了可配置的具体机制,很适合拿来理解「权限如何落地」:
- 权限模式(permission modes)
- 决定工具请求怎么处理:
plan模式屏蔽一切写操作(只让它读和规划);acceptEdits自动放行文件编辑但 Bash 等仍需授权;bypassPermissions放行到达它的请求;dontAsk拒绝一切未预先批准的动作(来源)。这正是「按风险分级」的产品化。 - PreToolUse 钩子
- 在每次工具调用之前运行,能看到完整命令串(含管道、子 shell);脚本可返回
allow / ask / deny,deny 时工具永不执行——而且 deny 即使在 bypassPermissions 模式下也照样生效(来源)。评估顺序是「钩子先跑,再查 deny 规则」。 - SDK 层对应
- 在 Agent SDK 里,这对应
authorize_tool_call(执行前可放行/拦截)与process_tool_result(执行后可改写结果)两个挂钩点(来源)。
# 示意:一个 PreToolUse 钩子,拦住危险 Bash
# 输入:工具名 + 完整命令串;输出:allow/ask/deny
if tool == "Bash" and matches(cmd, ["rm -rf", "curl | sh", ".env"]):
return {"decision": "deny", "reason": "命中危险模式"}
if tool == "Bash" and writes_outside_repo(cmd):
return {"decision": "ask"} # 交人确认
return {"decision": "allow"}
8.2 另一种范式:OpenAI SDK 的三级护栏
OpenAI 的 Agents SDK 把护栏分三层跑:输入护栏(在第一个 Agent 上运行)、输出护栏(在最终输出上运行)、工具护栏(在每一次工具调用上运行)。护栏在动作执行前校验,于是「会删数据、花钱、碰受保护资源」的调用可以被拦截、改写或升级(来源)。不同框架名字不同,但「在执行前设卡」的思想完全一致。
9 · 完整案例走查:Agent 修一个 bug,harness 每步做了什么
把前面所有层串起来。任务:「修复用户反馈的登录超时 bug」。看 harness 在背后逐步做了什么(方括号标注对应章节):
harness 不把「修个登录 bug」这句模糊话直接丢给模型,而是拼装结构化上下文:相关文件、报错日志、代码约定、验收标准(测试要通过)。这就是「结构化上下文优于自由文本工单」。
模型先规划;harness 跑符号分析生成「仓库影响图」,预判这次会碰到
auth/session.py 等文件,把盲改变成有地图地改。模型返回结构化调用
read_file(auth/session.py)。harness 过关卡:已知工具 ✓、参数合法 ✓、路径在工作区内 ✓、风险=低 → 自动放行,沙箱读取,结果回灌。调用
write_file,风险=中 → 在沙箱内执行、记录 audit log。若配了 plan 模式,这一步会被屏蔽,只让它先给出方案。harness 执行测试工具。假设测试没过——报错原样回灌当「观察」,模型据此再改。循环进入第二轮。
假设模型连着三次提交几乎相同的错误改动 → 卡死检测触发,或步数逼近预算上限 → harness 跳出循环,把现场交给人,而不是无限烧钱。
模型想
run_migration 改数据库结构 → 风险=极高 → 动作被「冻结」、状态停泊、弹出人类审批。人点「批准」后才继续。上面每一步的工具调用、参数、结果、token、耗时都进了 trace。任务若翻车,你能回放整条轨迹定位是哪一步、哪类错误——也是评测与改进 harness 的原料。
10 · 常见坑
- 把模型直接接到工具上。省掉中间校验层 = 给提示注入开了一条直达「任意执行」的高速路。
- 所有动作一个权限档。读文件和删库用同一套放行逻辑,迟早出事;必须按风险分级。
- 只堆模型,不做外壳。指望换个更强模型解决一切,却忽略那 98.4% 的基础设施——上不了生产的根因通常在这。
- 零可观测性。不打日志、不做 trace,线上一出问题就是黑箱,连复盘都做不了。
- 循环无边界。不设步数/成本上限、不做卡死检测,Agent 可能陷入无限循环,烧钱又卡死。
- 压缩太狠。归纳提示过激,Agent 会忘掉工具 schema、项目约定和在办工作——压缩要保住骨架。
11 · 落地六步
- 先隔离执行环境。给 Agent 一个沙箱,别让它直接碰生产系统。
- 把工具调用收进「校验层」。模型只产出结构化调用,harness 负责校验 schema、查权限、再执行(§5 的代码即模板)。
- 给动作分风险等级。用声明式策略把低风险自动放行、高/极高风险加人类审批门。
- 搭有边界的循环。设步数与 token 上限、加卡死检测,每步结果都回灌当反馈(§4)。
- 打日志、接 trace。至少记录动作、工具调用、token;有余力上 Langfuse/Opik/Braintrust。
- 用观测数据迭代外壳。拿线上失败去改进上下文组装、权限策略和工具集——这才是 harness 工程的日常。
换个说法:一句话记住这六步
12 · 继续深入的资源
- Around the Loop: Building a Coding Agent Harness in Python(Edd Mann)
- 从零搭循环、压缩、停止条件的实操长文,本文很多代码思路出处。链接
- Configure permissions — Claude Code / Agent SDK Docs
- 权限模式、PreToolUse、authorize_tool_call 的一手官方文档。链接
- Claude Code Hooks 完全指南
- 30+ 钩子事件、PreCompact、决定返回值(allow/ask/deny)。链接
- Best AI Coding Agents 2026(Firecrawl)
- Claude Code / Cursor / Copilot / Codex 的 harness、成本、准确率横评。链接
- AI Agent Best Practices: Production-Ready Harness(2026)
- 本文很多铁律的出处(98.4% 数字、工具校验、风险分级、三级护栏)。链接
- awesome-harness-engineering(社区精选清单)
- 工具、模式、评测、记忆、MCP、权限、可观测性、编排一站式索引。链接
- arXiv 2605.13357 · 2602.14690 · 2604.25850
- harness 的学术定义、编码 Agent 外壳研究、观测驱动的自动演化。