什么是 Harness
模型只会生成文本。Harness 是把其中一部分文本变成工具调用、真正执行,再把结果送回模型的那一层运行时。
这个词已经用得很随便。打开一个编码产品,界面、模型、工具、循环经常被叫成同一个名字。
先把它们拆开。后面几件是实际接 agent 时绕不开的:协议长什么样、循环在哪停、权限和上下文归谁、MCP 装上之后为什么有时像没装。
先给一句能用的定义
Harness 是包在模型外面的运行时。
它做五件事:
1. 准备一组工具,每个工具有名字、说明、参数
2. 用模型认得出的格式,把这组工具放进本次请求
3. 认出模型回复里的工具调用,并真正执行
4. 把执行结果写回对话
5. 决定要不要再叫一次模型,直到这次任务停下来模型负责下一个 token。Harness 负责让某些 token 变成副作用,再把副作用的结果变成下一批 token。
你在终端里看到的 TUI、在桌面端看到的聊天窗,是界面。界面把这场循环展示给人。
人用的是界面
人很少靠「为每件事现写一段程序」来使用计算机。人用的是已经包好的入口:按钮、快捷键、命令、网页。
Chrome 是浏览器的界面。Claude Code 的终端界面、ChatGPT 的桌面应用,也是界面。人通常看不到底下那层怎么读文件、怎么改 diff、怎么发请求。人看到的是输入框、批准按钮、终端输出。
理论上,人可以为每个任务写一段新的 Python 或 JavaScript。产品存在的理由,就是不必这么做。
模型默认只会写字
语言模型的本职是生成文本。读文件、改文件、跑命令、搜网页,都是文本之外的事。
这些能力要变成模型能用的东西,就得先变成工具。现在的编码模型,训练时就见过这类工具:读文件、改文件、执行命令、搜索。名字各家不同,形状差不多。
模型仍然发不出一次真正的系统调用。它能做的,是在回复里交出一份「请帮我跑这段」的结构。教学上可以把它想成这样一段文本:
read_file
path: README.md生产里往往更进一步。提供商的 API 通常直接给出结构化的工具调用,例如 Anthropic 的 tool_use 内容块,或 OpenAI 风格的 function call。模型权重吐出的仍是 token,API 已经帮你把其中一段收成了名字加参数。
于是会出现两种 harness:
文本协议
模型把调用写在正文里
Harness 自己解析
原生工具协议
API 返回结构化的 tool call
Harness 按这块结构执行写成正文的那种调用属于第一种。它说明一件事:调用首先是模型能写出的东西。自己写 harness 时,优先走提供商的原生工具协议。解析正文里的伪 XML,是没有原生工具、或要走自定义通道时的退路。
无论哪一种,解析完都还只是数据。计算机没开的时候,敲键盘也不会有反应。必须有一个程序接住这份数据,跑对应的代码,再把 stdout、报错、文件 diff 送回去。
少任何一步,模型都只是在空转。
一次调用里,Harness 在做什么
把上面五件事展开,就是大家说的 agent loop:
组装本次请求
系统提示、历史、工具清单、相关文件
│
▼
调用模型
│
├── 只有正文 ──────────────► 结束,把正文交给界面
│
└── 带有工具调用
│
▼
权限 / 沙箱检查
│
▼
执行工具
│
▼
把结果追加进对话
│
▼
再次调用模型循环在哪里停,也是 harness 的事,工具自己决定不了:
- 模型明确表示这一轮结束
- 达到最大轮数
- 人中断
- 工具失败,且当前策略选择停下来,而不是把错误塞回去让模型重试
编码产品里的 harness 通常还拥有这些。定义里没写,一动手就会碰到:
| 职责 | 它决定什么 |
|---|---|
| 工作目录和沙箱 | 命令能碰到哪些文件 |
| 权限 | 哪次写入、哪次联网要人点头 |
| 会话记录 | 关掉窗口之后还能否续上 |
| 上下文预算 | 对话变长后,压缩什么、丢掉什么 |
| 钩子 | 工具执行前后再插一层检查 |
| 子代理 | 再开一个循环,用另一份工具和提示 |
所以「有 read_file」只说明工具存在。两个产品可以都叫这个名字,一个直接覆写整文件,一个只应用补丁;一个默认执行,一个每次弹批准。用起来完全是两个东西。差异在 harness。
界面和 Harness 是两层
Claude Code 经常被直接叫成 harness。这个说法只对了一半。
你在终端运行 claude,打开的是 TUI。你在 Claude 桌面端打开 Code,打开的是 GUI。这两层都是界面。
界面底下,Claude Code 通过 Anthropic 的 Agent SDK 跟模型交互。ChatGPT 那边是同一结构:聊天应用对着 Codex App Server,这层做在 CLI 里。
人容易混,是因为这两款产品把 harness 烤进了应用里,打开就能用,看不见接缝。
同一套 harness 可以被别的应用装进去。已经看得到的几种接法:
- Cursor 的 harness 被 Grok Bot 使用
- Anthropic Agents SDK 和 Codex App Server 被 T3 Code 使用
- Pi 被 OpenClaw 使用
换一款「编码 agent」,有时换的是模型,更多时候换的是这层运行时:工具表、权限默认值、上下文怎么塞、编辑怎么落地。模型可以很接近,产品手感可以差很远。
工具归谁
把 harness 想成一间厨房。锅、刀、锅铲是工具。没有一间厨房能备齐每个厨师要的全部厨具,所以好的 harness 从一开始就假定:常用的自己带,其余的允许人加进来。
Codex 和 Claude Code 自带的工具很多。Pi 这类 harness 故意很瘦,把选择权留在外面。
这里有一条容易忽略的边界:
人 模型
│ │
│ 只看见界面 │ 只看见名字、说明、参数、返回文本
│ │
└──────────┬──────────┘
│
Harness
│
真正持有工具实现的一层
│
read_file / bash / 你自己加的插件人和模型都摸不到工具的实现。
对模型,工具是一份 schema,加上后来塞回来的一段结果。对你,工具是 diff、终端、一次批准提示。所以两个人都可以觉得自己「在用 Claude」,一个用的是官方工具表,一个还挂了邮箱、数据库、浏览器。权重可能一样,模型能碰到的世界不一样。这个世界是 harness 暴露出来的。
MCP 是工具的另一种住法
MCP(Model Context Protocol)解决的是:工具的代码不必住在 harness 里面。
可以分成两种托管:
内置工具
实现跟着 harness 走
权限、沙箱、生命周期由 harness 管
MCP
实现住在另一个进程,或另一台机器
Harness 负责发现、转发、把结果写回上下文可以压成一句:工具是「在这里跑一段代码」,MCP 是「到别处发一次请求,让那边做事」。常见用途是把外部数据接进来,比如邮箱、站点分析、工单。
对模型来说,两边长得一样:名字、描述、参数 schema、一段返回。模型分不出这段结果来自进程内函数,还是来自一次 MCP 调用。这个区别是给人看的,用来决定代码放哪、谁来升级、挂了算谁的故障。
一个贴切的类比是浏览器:扩展像内置工具,代码跑在你这台机器上的浏览器里,能改页面怎么工作。网站像 MCP,你不把实现装进浏览器,你只需要知道地址,按它公布的方式去用。
装上服务器,不等于模型就会用。Harness 还得做完这四步:
连上 MCP server
│
▼
把 tool schema 放进这一次请求
│
▼
模型吐出调用时,转发到那个 server
│
▼
用模型认得出的格式,把结果写回去少一步,它就是一本没人翻开的菜谱。
为什么有些能力做成 MCP,而不是做成插件
有些能力不属于任何一间厨房。比如让一条 Claude Code 线程再去拉起 Codex 里的子代理,编排逻辑得活在 harness 外面。T3 Code、Herdr、Orca、Superset 都在做这类事。
这些代码可以重写成 Claude Code、Codex、Antigravity、Pi、Cursor 各自的插件。那样每接一个新 harness,就要移植一次。
另一条路是把代码留在编排这层,对外只暴露一个 MCP server。各个 harness 只要会说 MCP,就能调用,不必各自内置这套编排。
MCP 在这里像一本菜谱。它不改造厨房的灶台。厨师会不会翻开,取决于你有没有把这本书放上案板,也取决于这间厨房认不认这本书的格式。
Agent 是模型和 Harness 叠在一起
单独的模型只会生成文本。单独的 harness 只是一间空厨房,没有人下刀。
两者叠在同一次循环里,才成为 agent。
日常口语里的「一个 agent」,经常只是这份配置:
哪个模型
哪份系统提示
哪组工具
哪些 MCP server
默认权限松还是紧配置和运行时要分开记。换一份提示词,还是同一个 harness。换一套压缩上下文、派生子代理、失败后重试的策略,那是换了运行时。产品页面上它们都可能被叫成「换一个 agent」。
有些产品是餐厅,不是厨房
坐在 harness 上面的产品,自己不是 harness。不另外带一套 harness,它跑不起来。T3 Code 就是这种:厨房可以换,它负责把客人和菜单接进去。
对应关系是:
厨师 = 模型
厨房 = harness
餐厅 = 坐在上面的那层产品
菜谱 = 它通过 MCP 交给每间厨房的能力餐厅允许你换厨房。它有自己的做法,会把菜谱递进去。厨师不会自动去翻。MCP 得接上,模型也得在某一步真的发出调用。
一张可以带走的分层
从下往上:
模型
权重。根据上下文生成下一批 token
提供商 API
消息格式、工具 schema、流式输出
经常已经把工具调用收成结构化块
Harness
循环、工具执行、权限、沙箱
上下文组装、会话、压缩、子代理
界面
终端、桌面、网页
人在这里打字、看 diff、点批准
产品
上面几层的一种打包方式
有的把 harness 包在里面,有的要求你自己带一套进来判断一个新名词该放哪一层,可以问三个问题:
- 拿走模型,它还能不能自己把任务做完?不能,说明它在靠模型产生下一步。
- 关掉界面,循环还能不能跑?还能,说明界面只是观察口,干活的是 harness。
- 这个能力的代码住在哪?住在进程里,就是内置工具。住在别处、靠协议进来,就是 MCP,或别的远程工具。
三个问题都指向同一件事:模型提出下一步,harness 让下一步发生,再把发生过的事告诉模型。人通过界面观看,并在该停的时候插手。
Related Posts
拆解 OpenAI Pando:用因果一致性和可复用 packfile 把 Git 扩到 agent 规模
OpenAI 的 Git 后端 Pando 把写入降为按 ref 串行化、只对 ref 指针做 CAS,读取则复用推送时的 pack、通过 Git 协议 v2 的 packfile-uri 从区域缓存分发。后台用类似 LSM 的分层压实,让拉取规划做到 O(log Δ)。
RSC
React Server Components (RSC) in Next.js are components that render exclusively on the server, making them the default component type in the App Router.
Cloudflare-D1
The easiest way to add SQL to your application