之前我一直没搞懂 Harness 到底是什么。
文章翻了不少,都在讲 Harness 很重要。
说它关系到权限、可靠性、验证、可控性、安全性、约束。
看完我反而更糊涂了。
每个词都听着对,但没有一篇讲清楚它具体是个什么东西。
后来认真研究了一下。
发现 Harness 的实现思路,就是让 LLM 做选择题,而不是问答题。
一、Harness 是怎么来的
在说 Harness 之前,得先说说它出现之前经历了什么。
这几个概念不是谁发明的理论,而是工程问题一步步暴露出来的结果。
Prompt
最早的问题是 Prompt。
模型刚出来的时候,大家发现同一个问题换个问法,结果就差很多。
提示词怎么写,很大程度上决定了模型输出的质量。
于是有了 Prompt Engineer 这个角色,研究怎么提问能让模型答得更好。
但 Prompt 能解决的问题是有限的。
它只能影响模型在给定输入分布上「更可能说对话、写对代码」。
它解决不了模型缺乏边界感的问题,也解决不了模型接入真实系统后的风险。
Context
接着发现光会提问不够。
模型是无状态的,每次调用都是接收一段输入 token,输出一段 token。
本身不维护持久系统状态。
它不知道仓库里有什么分支、生产环境有哪些约束、什么算上线、什么算事故。
这些东西只能通过文本告诉它。
于是有了 Context Engineer,研究如何为这一轮调用,准备正确、充分的输入信息。
Context 解决的是信息完整性问题。
让模型在这一轮看到更多关键信息,从而减少胡编乱造。
但它仍然解决不了执行层面的问题。
Harness
再往后,模型开始接入真实系统——读写文件、执行命令、操作数据库。
这时候问题变了。
不再是答对答错的问题了。
之前讲 Prompt 和 Context,不管怎么讲,都是想让模型「答得好一点」。
但模型一旦真的动起手来,问题就不是答得好不好的问题了。
关键是,模型有幻觉。
你写在提示词里的规则,它不一定照着执行。
它可能自己脑补,胡乱执行一通。
比如:
你让它改某个文件,它顺手把不相干的几个文件也改了
你让它修个 bug,它跑偏去重写了整个模块
你让它跑个命令,它生成了错误的代码去执行
它可能误删文件,可能胡乱往外发请求,可能改了不该改的配置
提示词里写得再清楚都没用。
因为它不是在「遵守规则」,它只是在「生成像是在遵守规则的输出」。
一旦输出落到真实系统里,就由不得你了。
所以 Harness 要解决的问题是:
模型接入真实系统后,怎么让它尽量不要乱执行。
怎么让它只做你预期内的事,不要顺手搞出别的来。
这就是 Harness 要解决的问题。
Loop
光有 Harness 还不够。
因为前面这些都还是单次执行的场景——模型动一步,本质上是单轮的事。
但 Agent 真正的用法,是要长时间、一轮一轮自己跑下去的。
这里说的 Loop,不是简单的「人需要审批每一步」。
而是人们在实践里发现,「迭代开发」这种模式可以抽象出来。
一个目标,拆成一轮一轮去做,每一轮做完看结果、再决定下一轮怎么走。
这种模式提炼出来,就是 Loop。
平台需要把这种模式抽象成一种能力,提供给开发出来的 agent。
agent 可选地启用这种特性。
但它也有适用性。
如果你本身的工作流就不是这种迭代的、一轮一轮推进的,那强行套一个 Loop 上去,意义也不大。
这四个概念是递进的:
| 概念 | 解决的问题 |
|---|---|
| Prompt | 单次回答的质量 |
| Context | 信息的完整性 |
| Harness | 单次执行的安全性 |
| Loop | 长时间迭代的可靠性 |
每一层都建立在上一层的基础上。
二、为什么我之前总是理解不了
因为这些术语是给平台开发者说的。
什么是平台开发者?
开发「能开发 Agent 的工具」的人。
Anthropic 公司的工程师,写的是 Claude Code
Cursor 公司的工程师,写的是 Cursor
OpenAI 公司的工程师,写的是 Codex
对他们来说,这些概念对应着具体需要实现的工程问题:
| 术语 | 对平台开发者意味着什么 |
|---|---|
| Prompt | 如何设计系统提示词,让模型理解自己的工作方式和边界 |
| Context | 如何管理上下文窗口,如何拼装和裁剪信息,如何处理记忆 |
| Harness | 如何搭那套壳——模型怎么接、工具怎么调、权限怎么控、验证怎么跑 |
| Loop | 如何设计任务调度、重试策略、异常处理、人工接管机制 |
这些都是平台层需要解决的实际问题。
因为它们是用平台开发出来的每一个 agent,都会遇到的共性问题。
但大多数人并不是平台开发者。
如果在整个 Agent 工程里分个层,会更清楚:
| 层 | 是谁 | 在干什么 |
|---|---|---|
| 平台层 | 平台开发者 | 搭好那套壳,让所有 Agent 在同一个壳里跑;模型怎么接、工具怎么调、权限怎么控、验证怎么跑,都是他们定的 |
| Agent 开发者层 | 做扩展的人 | 在别人搭好的壳里,写 Skills、配规则、写 MCP 工具 |
| Agent 使用者层 | 用现成产品的人 | 通过自然语言或简单界面让 Agent 干活,自己审查结果、做决策 |
大多数人都在中间这层,或者最上面那层。
不是造平台的人,自然理解不了 Harness 在解决自己什么痛点。
三、那 Harness 到底解决了什么问题
回到 Harness 本身。
它要解决的问题是:模型接入真实系统后,怎么让它不要乱执行。
前面说了,模型有幻觉,不照提示词执行,可能胡乱执行。
那 Harness 是怎么解决这个问题的呢?
办法其实很简单——不让模型自由生成命令,只给它几个写好的工具去调。
模型只负责「调哪个工具、传什么参数」。
工具被调用之后干什么,是提前写好的代码决定的,不由模型说了算。
这就是「做选择题,而不是问答题」。
| 答案空间 | 谁说了算 | |
|---|---|---|
| 问答题 | 无限的,模型想写什么写什么 | 模型 |
| 选择题 | 有限的,每个选项干什么是确定的 | 代码 |
举个例子。
模型要改文件,不是让它直接 rm、直接 echo、直接重写整个仓库。
是给它一个 write_file(path, content) 的工具。
调用之前,先检查这个 path 在不在允许目录里。
不在,直接拒绝。
在,才执行。
模型要跑测试,不是让它自己拼 shell 命令。
是给它一个 run_tests(pattern) 的工具。
背后是写好的测试流程,跑完之后结果会被结构化地返回给模型。
模型要操作 git,不是让它自己敲 git push。
是给它 commit_changes(message) 这样的工具,背后自动在
feature 分支上干活,禁止推主分支。
这样一来,模型能做的事,就被限定在这几个工具里了。
它再有幻觉,最多也就是选错了工具、传错了参数。
但工具背后干什么,是代码定的,它改不了。
它绕不过去。
模型可能选错,但选错的结果不是直接操作系统。
而是调用了某个工具——工具的执行结果可以被验证、被回滚、被记录。
这就是「可控」的真正含义。
四、内置工具
前面一直在说「给模型几个工具去调」,那这些工具是哪来的,先交代清楚。
像
Read、Write、Edit、Bash、Glob、Grep、WebFetch
这些,都是平台自带的内置工具。
不是每个项目自己造的,而是平台层统一提供的。
所有在这个平台上跑的 agent,默认都能用这一套。
平台为什么要内置这些?
因为读写文件、跑命令、搜代码,是几乎所有 agent 都要做的事。
每个项目都自己写一遍,没必要,也写不齐。
平台把这些公共能力收进来,顺手在每一个工具外面,包上权限检查、参数校验、日志记录。
这样 agent 一上来,能干的事就是受限的、可观测的。
至于这些工具的 schema 怎么传给模型、模型怎么决定调哪个、平台怎么执行、结果怎么回到模型,是一条完整的调用链路,放在文末附录展开。
五、出错了怎么办
光拦住还不够,模型还是会选错,工具调下去还是会出问题。
所以 Harness 还得兜住出错的情况。
关键操作之后,强制验证——改完代码跑测试,测试没过,就把错误信息结构化地丢回给模型,让它继续改。
「完成」也不是模型说做完了就算数,是平台检查该跑的验证跑没跑、过没过。
失败的时候,区分是可重试的错,还是必须人工介入的硬失败,分别处理。
整个过程记进日志,错了能查到、能回滚。
这其实就是把「交付要测试、要 review、错了要回滚」这件很朴素的事,从人盯,变成了代码自动盯。
六、一条完整的链路
举个具体的例子。
一个 TypeScript monorepo 项目,Agent 接到一个任务:修复 src/foo.ts 里的 bug,并补上测试。
链路大致是这样:
1 | 1. 读取文件 Agent 通过工具读相关文件和已有测试 |
整个过程里,模型始终在壳内提建议。
什么时候读文件、怎么改代码、怎么解释失败原因。
而所有「能不能真的动仓库」「什么时候算完成」「失败怎么处理」,都是平台说了算。
七、那为什么文章都爱讲这些
讲到这里,应该能看出来 Harness 做的事其实不复杂。
它做的事情其实很朴素:
把模型能干的事,收窄成几个受控的工具
在工具周围包上检查、验证、回滚
出错的时候自动收场,而不是任由它越跑越偏
那为什么网上那些文章,都把它说得那么复杂、那么玄呢?
因为写文章的是平台方,站的是自己的视角。
把自己的内部架构说得越复杂,平台就显得越有价值。
读者焦虑了,觉得搞不定,最后买他们的服务。
这不是阴谋,只是立场不同。
每个做平台的人,都希望自己的平台看起来更强大、更专业。
但作为开发者,得有自己的判断。
知道什么是自己该关心的,什么不是。
有个简单的自查方法:
| 你主要在写 | 你处在哪一层 |
|---|---|
| SDK、runtime、权限、验证、状态、日志 | 平台层工程(Prompt、Context、Harness、Loop 真正的落地点) |
| 系统提示、规则文件、Skills、MCP、CLI | Agent 层产品与扩展(重要,但不同于平台层的壳) |
| 发任务、审查结果、做决策 | 使用者层(关注怎么用、什么时候该介入,而不是怎么造) |
八、结语
本文主要想说的是,Harness 这个概念没有那么复杂。
它就是让 LLM 做选择题,而不是问答题。
把模型能干的事收窄成几个受控的工具,在工具周围包上检查、验证、回滚。
这些术语是平台开发者在解决自己的工程问题时用的。
对于 Agent 开发者来说,用好平台提供的功能就够了。
对于 Agent 用户来说,那就更不用管了。
附录:tool call 的完整调用过程
前面说 Harness 让 LLM 做选择题、平台负责执行,但没展开具体是怎么调的。
这一节把整条链路走一遍,工具 schema 怎么传、模型怎么决定调哪个、平台怎么执行、结果怎么回到模型。
以 Anthropic 的 Messages 协议为例。
1. 工具是谁写的,怎么注入的
工具分两类。
一类是平台自带的内置工具,像
Read、Write、Edit、Bash、Glob、Grep、WebFetch,平台工程师写死的,所有
agent 一上来都能用。
一类是项目自己加的,通过 MCP、Skills、CLI 注入进来,agent 开发者写的,只在特定项目里生效。
每个工具都是一个独立模块,自己定义四样东西:
name:叫什么名字description:是干什么的input_schema:要填哪些参数、什么类型、哪些必填call:被调用时真正干活的逻辑
平台在每轮请求前,把当前生效的工具收拢成一张列表,塞进请求里的
tools。
2. 传:把工具 schema 塞进请求
得先说一个前提:LLM 是无状态的。
它不记得上一轮自己说了什么,每次调用都是一次全新的请求。
所以平台每一轮都要把之前的对话历史,原样塞进请求里,模型才能接上之前的上下文。
平台发给模型的请求,大致是这样:
1 | { |
模型做选择就读三块:
system:身份和约束,大边界messages:对话历史,用户要它干嘛、之前调过哪些工具、每次结果是什么,它据此判断现在该干什么tools:这轮可用的菜单,每个工具的description说这道菜是干什么的,input_schema说要填哪些参数
tools 这张菜单是收窄动作空间的第一道关。
里面没有 rm 这种裸命令。
模型只能点菜单上有的菜,点不到 rm。
3. 选:模型决定调哪个
模型把这三块读进去,预测出「下一步该调哪个工具、参数填什么」,回一个 assistant 消息:
1 | { |
content 是个数组,可以同时装 text 和
tool_use 好几种 block。
注意,到这里为止,什么都没被执行。
模型只是说了一句「我想这么干」。
它不是在执行,是在生成一段结构化的选择。
4. 调:平台执行
平台拿到这个
tool_use,先不急着执行,过几道检查:参数校验、权限检查,过了才真正调用工具的
call。
对照源码里的 Tool 定义,每个工具自己管这几件事:
validateInput:参数对不对,缺没缺checkPermissions:这个操作允不允许call:真正干活的逻辑- 还有
isReadOnly、isDestructive、isConcurrencySafe这些标记,决定能不能并发、是不是危险
检查不过,直接拒绝。
过了,才真正执行。
5. 回:结果回到模型
注意,平台不是只回传一条 tool_result。
它回传的,还是上面那个完整结构——model、system、messages、tools
原样都在,只是把新结果追加到 messages
末尾,然后作为下一轮请求整体发给模型:
1 | { |
新结果就是最后那条 tool_result,tool_use_id
对上前面的 tool_use 的
id,模型就知道这次结果是给哪次调用的。
is_error 区分成败,content 装结果正文。
模型在下一轮看到这个结果,再决定接下来点哪个菜。
6. 一轮接一轮
所以整个流程就是这样一个循环:
1 | 传(tools + messages) → 选(tool_use) → 调(校验并执行) → 回(tool_result) → 追加到 messages → 下一轮 ... |
模型从头到尾干的事情只有一件:不停地输出「下一步调哪个工具、带什么参数」。
至于这个调用要不要执行、执行成什么样,都是平台控制的。
模型的角色,始终是个「出主意的」,真正动手的是平台。