先说结论
我把 HarnessRouter 那个 UHP 规范仓库整个扒了下来——protocol/versions/ 下四个版本的完整章节、docs/ 里的实测数据、conformance/ 的检查项,一路读到 CHANGELOG 里同一天打了七个补丁的段落。读完的结论只有一句:
Agent Harness 这一层,终于有人把它当成一个需要协议的层来对待了。
如果你今年一直在跟 Agent 基础设施,应该已经感觉到那股暗流。2026 年上半年,六家主要的 AI 和云厂商几乎前后脚把"自己的 agent harness"做成了产品——一个原本属于工程实现的零件,几个月内变成了采购清单上的一行。可每一家的 harness 接口都不一样,结果就是:任何一个想把 agent 接进自己产品的团队,都要为每个 harness 重写一遍集成。
UHP 想解决的,就是这一层的接口问题。它不是一个新模型,也不是又一个 Agent 框架。它是一份 HTTP 契约,规定"客户端怎么驱动一个 harness"。
我把规范里的关键设计挑出来拆了一遍,顺手把它的安全条款整理成了一张对照清单。下面就是这些。
它到底是什么:任务,不是一次 completion
先把它和其他东西划清界限。
UHP 的定义很短:An open standard for running complete agent harnesses as shared infrastructure.(把完整的 agent harness 当作共享基础设施来运行的开放标准。)
这里的 harness 指一个完整的 agent 运行时——自己会规划、调工具、改文件、把结果报回来。Codex、Claude Code、Hermes 都是 harness。它们各自都会干活,问题在于:没有两家在"产品该怎么驱动它"这件事上达成过一致。 怎么起一个任务、怎么跟进进度、怎么接着聊、怎么取消、怎么拿回它产出的文件、失败了怎么知道为什么——今天的现实是,每个产品都为每个 harness 把这些问题重新回答一遍。
UHP 的回答是:这些问题只回答一次。
它跟模型 API 的关系,规范里有一句话我觉得是全文最精准的:
Model APIs give you a turn: messages in, tokens out, tools you have to run yourself. UHP gives you a task: work in, and a running agent that uses its own tools, keeps its own session, and hands back results and files. The unit of exchange is a job, not a completion.
模型 API 给你的是一个 turn(一轮对话),UHP 给你的是一个 task(一个任务)。交换的单位是 job,不是 completion。这句话把定位钉死了——它不在模型层,也不在工具层,它在"怎么跑一个 agent"这一层。
三个角色
规范里只有三个角色,画成一条链:
┌──────────┐ UHP over HTTP ┌──────────┐ implementation-defined ┌──────────┐
│ Client │ ────────────────▶ │ Server │ ─────────────────────────▶ │ Harness │
└──────────┘ └──────────┘ └──────────┘
- Client:想要把活干完的应用(产品后端、CLI、CI、甚至另一个 agent)。它只说 UHP,而且规范明确要求它不应该知道背后跑的是哪个 harness。
- Server:实现这份规范的一方。它接收任务、驱动一个或多个 harness、用规范定义的词汇回报进度。怎么跑 harness(容器、子进程、队列、远端 worker)全部属于实现细节,不许漏进 wire format。
- Harness:一个完整的 agent 运行时,用一个稳定的 base 字符串标识,比如
codex、claude-code、hermes。
这里有个我觉得设计得很对的概念:configured harness(已配置 harness)。
它不是"某个 base",而是"某个 base + 一套配置"——默认模型、system prompt、工具限制、skills、MCP servers、步数与时间预算。它是一等、可寻址的对象,客户端要干活时选的就是它。
Why a configured harness, and not just a base? The same base behaves very differently with a different system prompt, a different model, or a different tool set.
同一个 base,换了 system prompt、换了模型、换了工具集,行为可以差到不是同一个东西。把配置提升成一等对象,产品就能"不重新部署后端就改掉 agent 的行为"。
拆解一:三档一致性 + 能力发现
UHP 没有"要么全支持要么别来"这种粗暴划分。它定义了三个累进的一致性等级(Core ⊂ Extended ⊂ Full):
| 等级 | 必须实现 |
|---|---|
| Core | 能力发现、harness 发现、任务的流式与非流式执行、会话续接、取消、错误模型 |
| Extended | Core + 文件输入、artifact 取回、会话列表/检视 |
| Full | Extended + harness 生命周期管理(增改删)+ 会话分享 |
配套的是能力发现端点 GET /v1/uhp。注意它有两个不寻常的要求:
- 这个端点必须免鉴权——客户端得先知道自己在跟一个 UHP server 说话,才谈得上要不要出示凭证。
- 服务器必须显式报
false,而不是省略能力字段——这样客户端才能区分"不支持"和"这服务器比这个字段还老"。客户端则必须把缺席的 key 当作false。
发现文档里 conformance_class 和 capabilities 还必须自洽:声称 extended 的服务器,必须把 files_input、files_output、session_listing 报成 true。规范的原话很硬:
advertising is a promise, and a client that trusts it and receives a
404has been lied to in a way it cannot recover from.
能力声明是承诺,不是愿望清单。这一条我喜欢。
拆解二:七个对象,每个 id 都带前缀
对象模型是七个类型,每一个都带 object 字段标明类型,id 带类型前缀——好处是一个标识符永远不会让你猜它指向什么:
| 对象 | object 值 |
id 前缀 | 生命周期 |
|---|---|---|---|
| Harness | harness |
chrn_ |
直到删除 |
| Response | response |
resp_ |
按服务器策略保留 |
| Session | session |
hsess |
直到删除 |
| File | file |
file_ |
随其 container |
| Container | container |
cntr_ |
随其 session |
| Environment | environment |
henv_ |
直到删除;可选 |
| Memory | memory |
hmem_ |
直到删除;可选 |
它们的关系是这样:
Harness ──┐
├──▶ Session ──┬──▶ Response ──▶ Response ──▶ … (每个任务一个,链式)
Model ───┘ └──▶ Container ──▶ File, File, … (artifacts)
两个设计取舍值得单独说。
Session 是隐式创建的。 第一个任务就自动建好 session,不需要先 POST /sessions。理由写在规范里:要求先建会话会凭空多一次往返、一个失败点、一个要清理的对象——而大多数第一次发来的任务根本用不上"会话"这个概念。只发一次性任务的客户端,可以一辈子不知道 “session” 这个词;需要连续性的客户端,引用它已经拿到的 id 就行。
Environment 和 Container 是分开的。 Environment 是"一个项目的文件和装好的依赖",构建一次、只读地挂进每个引用它的会话;Container 是会话的文件命名空间,agent 写出的文件变成里面的 artifact。规范里一句话概括:artifact 来自工作目录,environment 是工作目录读取的东西。把"只读底座"和"可写产出"拆开,这个边界划得干净。
拆解三:任务状态机 + 一条不能缓冲的流
任务从提交到结果,走这几个状态:
┌──────────────┐
POST /responses │ │
────────────────▶│ in_progress │
└──────┬───────┘
│
┌──────────────────┼──────────────────┬───────────────────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌────────────┐ ┌───────────┐
│completed │ │ failed │ │ incomplete │ │ cancelled │
└──────────┘ └──────────┘ └────────────┘ └───────────┘
这里最容易被忽略、但我觉得最实用的是 incomplete 和 failed 的区别。因为步数或时间预算停下来的工作,标 incomplete;出错才标 failed。规范特意解释了这个区分的意义:对客户端来说,incomplete 通常值得接着跑,failed 通常不值得。而且一旦进入终态就不许再变,连部分产出也必须保留:
Discarding partial work because a task later failed destroys the only evidence of what went wrong.
任务跑挂了就把之前的产出丢掉,等于把"到底哪里出问题"的唯一证据也一起丢了。这条规则很老练。
流式部分,协议直接定了事件词汇(response.created / response.output_text.delta / response.function_call_arguments.done …),并要求 sequence_number 从 0 开始、每个事件精确 +1——客户端因此能发现掉包,而不是悄悄渲染出一个缺口。有一条部署陷阱被专门点名:
Servers behind a proxy MUST disable response buffering. This is the single most common deployment error for UHP servers; it looks exactly like the harness being slow.
代理没关响应缓冲,会看起来和"harness 很慢"一模一样。这行提醒说明写规范的人自己踩过。
还有一个哲学性的选择:流是优化,存储的 response 才是真相源(source of truth)。 断线不会中止任务,服务器继续跑;客户端回来用 GET /v1/responses/{id} 重新读就行。规范直接点名了反模式——“把 stream 当成结果唯一存在的地方"的产品,每次负载均衡回收连接都会丢工作。
拆解四:故意长得像 OpenAI Responses API
这一点是被明说的,不是巧合:
UHP’s task surface is deliberately shaped like the OpenAI Responses API, and a conformant server MUST accept the subset of that request body … This is a compatibility decision, not an accident.
产品里已经有大量代码在说 Responses 协议了;现成的 SDK、流式解析器、UI 组件,不用改就能对着 UHP server 跑。UHP 在此之上加的是 harness 才需要、而模型端点根本没有概念的东西:跑的是哪个 harness、它的工具和 skills、跨任务存活的会话、回来的文件、以及"对已经在跑的活取消”。
扩展只发生在文档化的增量位置(metadata、少量额外请求字段、额外对象类型),绝不改变已有字段的含义。一个把所有 UHP 扩展都忽略的客户端,仍然能拿到一个可用的任务。这个"向后兼容到能摆烂"的取向,对一个想被大范围实现的标准来说是对的。
拆解五:packaging 不自己造,直接复用三层
这是我读下来觉得最聪明的一处:UHP 不定义 package 格式。
一个 UHP plugin,本身就是一个 Agent Plugins 1.0.0 的包——一个根目录有 plugin.json 的文件夹,MCP servers 声明在 mcp.json,skills 放在 skills/<name>/SKILL.md,每个 skill 又是一个带 SKILL.md 的 Agent Skills 文件夹。三层套得干干净净:
Agent Skills SKILL.md and its folder what one skill is
Agent Plugins plugin.json + mcp.json + skills/ how skills and tools are packaged
UHP Plugins this chapter how a package is installed into a hosted harness
为什么要"采用一个格式"而不是"定义一个格式"?规范给了个特别实在的理由:
A package format is worth exactly the number of places it installs.
一个包格式的价值,等于它能装进去的地方数量。因为 Agent Plugins 是由来自主要 agent 客户端的指导委员会维护的,为一个客户端写的 plugin 不用改就能装进 UHP harness,反之亦然。如果 UHP 自己造一个格式,每个边界都要转换,而"转换"正是内容会丢的地方。
同样的思路也体现在它和 MCP 的关系上。命名文档里说得很清楚:MCP 标准化"能力怎么暴露",Agent Plugins 标准化"skills 和 MCP server 定义怎么打包",UHP 把前两者收进一个配置。生态里的开放标准,在一层接口上汇合,而不是碎成一堆。
我看到的三个"坑",以及它们为什么是对的
规范里有两处,初读像"没做完",细看才发现是刻意的决定。这也是我认为这份规范值得认真对待的原因。
坑一:tools 和 include 被保留,但永远不起作用
这两个字段跟着 Responses 的 wire shape 一起进来的。UHP 的处理是:服务器接受它们、从不据此行动、并在 metadata.ignored_fields 里报出来。它直接说这是"a decision rather than a gap left open"——是个决定,不是留着没补的洞。
tools 为什么不能按 Responses 的意思用?因为在 Responses 里客户端执行工具:模型吐 function_call,客户端跑它,再把结果作为下一轮的输入发回去。而 UHP 把这两段都放进了 output——harness 自己调用、自己执行、把过程当作可观测性报出来。没有一条"工具结果"的输入路径,所以 tools 暗示的那个循环,在 UHP 的对象模型里根本没地方完成。
那 tools 能不能当"这个任务用哪些 MCP server"?也不行,而且理由更重:这会变成一个越权原语。 如果一次请求能挂一个 MCP server,那么任何拿到 API key 的人都能把 agent 指向自己选的端点,agent 就用 harness 的凭证和工作区权限在服务端执行工具。而 harness 的所有者和 API 调用者通常是不同的两拨人。请求级的声明,等于把一个能力决策从"负责任的业主"手里悄悄挪给"任意调用者"。
规范把底下的规则总结成一句我很赞同的:
narrowing is safe, widening is escalation.(收窄是安全的,放宽是越权。)
未来版本可以合理地加一个"按请求禁用某工具"的列表,但永远不该加"按请求授予"。
坑二:模型替换必须上报
如果请求的模型无法在该 harness 上服务,服务器只有两条路:要么 422 model_unavailable 失败,要么替换成该 harness 的授权默认模型、并在 response 里记下来(requested_model / model_fallback / model_fallback_reason)。客户端永远能通过比对 model 和 metadata.requested_model 来回答"我点的模型到底跑了没有"。
这条规则的理由,戳到了所有做过评测和成本归因的人:
A server that substitutes silently makes every measurement downstream wrong — benchmarks, cost attribution, quality comparisons — and the client has no way to detect it.
静默替换,会让下游所有的度量全错——benchmark、成本归因、质量对比——而客户端还无从察觉。上报一个替换,成本就两个字段。
我自己在做 Agent 观测和网关时最怕的就是这个:你以为在 A 模型上跑出来的数据,其实是 B 模型跑出来的,而且没有任何地方会报错。UHP 把"模型身份"提到协议层强制可审计,这一步走得很正。
坑三:安全章节是一张"给实现者打勾的清单"
UHP 的 security 章节有个少见的写法:凡是别处提过的安全要求,这里重复一遍,而不是只给交叉引用。理由也写明了——一条要读者从六个章节里拼起来的安全规则,就是一条会被漏掉的规则。
我把它整理成了一张对照清单:
- 凭证:除
GET /v1/uhp外每个端点都要鉴权;不得区分"没这个 token"和"这个 token 不配用",否则可被枚举;任何响应体、错误、事件、日志、artifact 里都不许回显凭证;provider 凭证是系统里最值钱的秘密,而 agent 沙箱是最不可信的地方——agent 能被诱导读自己的环境再打印出来。 - 对象作用域:越权访问必须返回 404 而不是 403。403 等于确认这个 id 存在,正是枚举攻击者想要的;而且作用域要覆盖每个操作,不只读——取消、删除、续接、下载都算对象访问。
- artifact 是敌意内容:agent 可以被说服写出带指定内容、指定文件名的文件,所以服务 artifact 的一方必须把它当敌意内容处理。下载必须带
X-Content-Type-Options: nosniff(否则一个叫x.html的 artifact 就变成打客户端自己源站的存储型 XSS);应该从与主站不同的 origin 提供;通过 artifact id 做路径穿越,被点名是"UHP 实现里最可能的严重漏洞",一致性套件专门有 X-08 探针,但规范自己说"通过探针只是地板,不是证明"。 - prompt injection 是客户端的责任:规范承认 UHP 防不了,并且说"假装能防的规范比明说的更糟"。它能提供的工具是:
disabledTools(最小的工具集就是最小的注入面)、max_step和timeout_seconds(有界任务才不会被诱导成无界)、按信任级别拆不同的 harness(读不可信输入的 harness,不该同时持有高权限工具)、以及把工具调用做成可实时看见、可门控的事件流。 - 资源耗尽:必须限制任务时长,并且预算停下时报
incomplete而不是completed;同一 session 的第二个并发任务必须拒绝(session_busy)——“两个 agent 在同一个工作目录里不是一个有定义的状态”;上传超限必须报file_too_large,不许截断——被静默截断的输入会产生一个自信而错误的答案。 - plugin 是第三方代码:plugin 里声明的 stdio MCP server 必须跑在跑 agent 的那个沙箱里、用 agent 的权限、且不得经 shell 启动;服务器只许展开
${PLUGIN_ROOT}和${PLUGIN_DATA}两个占位符,且只在args、env、cwd里——“一个能把沙箱环境读进自己进程参数的 plugin,就会读到沙箱持有的任何凭证”。
这份清单我几乎是逐条点头读完的。它跟这两年 Agent 安全领域摔过的跤,一条条对得上。
一份实测数据:harness 不只是接口差异,是可度量的差异
规范仓库里还带了一份实测(docs/benchmark.md)。同一模型 deepseek-v4.1-flash,同一个包 SpreadsheetBench Verified 的前 50 个任务,web 工具全部关闭,WORKERS=2、TASK_CAP_S=900,四个 harness 跑出来的结果:
| Harness | Tasks | Resolved | Reward | Wall(sum) | Median wall | Tool calls | Calls failed | Fresh in | Cached in | Output |
|---|---|---|---|---|---|---|---|---|---|---|
| cline | 47 | 36 (77%) | 0.77 | 6882s | 108s | 736 | 121 (16%) | 1.20M | 19.74M | 1.21M |
| dsh | 46 | 36 (78%) | 0.78 | 6506s | 100s | 721 | 113 (16%) | 761k | 12.32M | 752k |
| opencode | 48 | 39 (81%) | 0.81 | 5515s | 75s | 563 | 67 (12%) | 666k | 11.64M | 709k |
| pi | 47 | 40 (85%) | 0.85 | 3605s | 48s | 526 | 89 (17%) | 484k | 7.53M | 546k |
同一个模型,决定解出率、耗时、token 消耗的不是模型本身,是 harness。pi 用 3605 秒墙钟跑出 85%,cline 用 6882 秒跑出 77%——几乎两倍的时间、更低的分数、更多的 token。这份表本身就是 UHP 存在的理由:如果这一层差异这么大,而每个产品都要为每个 harness 重造集成,那浪费是结构性的。
数据也保持了诚实:有两列被"靠边放"(cline 在 0.18.0 的 36/50、opencode 在某个补丁之前的 37/48),另有若干次运行因为"访问了网络"或"跑到工作区外面找任务"被单列不计分,比如有一条记录显示 harness 执行了 find / -name '*.xlsx' 2>/dev/null。把"不合规的运行"单列而不是偷偷抹掉,这个细节让我对这份数据的可信度评价高了一档。
版本节奏读出来的东西
UHP 的版本号是日期。我把 CHANGELOG 的版本线拉出来:
2026-08-11— 首次发布2026-09-12— 增加 Plugins 章节(含 plugin 内的 stdio MCP server)、plugins能力、五个错误码2026-09-28— 一次修订2026-10-04— 当前版本,同一天打了七个补丁
八个星期,四个版本,最后一天连打七个补丁。这个节奏说明两件事:一,这层确实有人真的在写实现、写的时候把规范里的洞一个个顶出来;二,它还没定型。规范自己把状态标成 “Draft standard — stable enough to build on, versioned so it can change safely”,是诚实的措辞。
补丁的内容也很有意思——大多集中在 Memories 章节的一条条细节上(记录内容必须是 parts 数组、memory id 不透明、recall 搜子树、统一"谁在操作 memory"的词表……)。这些不是功能膨胀,是把语义反复拧紧。一个标准愿不愿意在细处拧,往往比它喊的愿景更能说明它会活多久。
我的判断:这层会不会被大厂吸走?
得说实话,这里有个真实的张力。
UHP 由 HarnessRouter 发起并主导,参考实现是它的 Community Edition(Apache-2.0,对规范套件的 Full 级报告,当前 52 项检查)。而 2026 年上半年把 harness 做成产品的,是六家大厂。一个由创业公司发起的标准,能不能管住大厂自己的 harness,是这份规范最大的问号。
但 UHP 有几处设计,客观上把"被采纳"的门槛压得很低,这是我愿意给它正面评价的原因:
- 不要求托管服务。规范原话:conformant server 可以完全跑在你自己机器上、用你自己的 provider key、把一切存在你拥有的卷上。没有账号、没有 license key、不打电话回总部。
- 一致性本地可验证。
pip install -e protocol/conformance然后跑uhp-conformance就行,一致性是一个"可独立验证的属性",不是承诺。 - 复用而非另造。package 复用 Agent Plugins,能力暴露复用 MCP,peer 协作留给 A2A(命名文档明确说 UHP 与 A2A 互补而非竞争)。它把自己定位成"让这些标准在一层接口上汇合",而不是再开一个战场。
这套打法的风险也清楚:它的价值完全取决于有多少东西真的实现了它。 一份再优雅的规范,如果大厂 harness 各自为政、只在自家控制面里互通,UHP 就会沦为"参考实现很好、生态没跟上"的又一份 RFC。规范套件和实测数据的存在说明发起方明白这个道理,但明白和赢是两回事。
我个人的判断偏谨慎乐观:它更可能先赢在"第三方 harness 的可移植性"上,而不是赢在大厂互认上。 只要有一批中小 harness 和产品愿意用 UHP 当统一入口——尤其是那些本来就要同时支持好几个 harness 的产品——它就能先成为一个真实可用的翻译层。至于能不能再往上长成 harness 生态的公共边界,取决于 HarnessRouter 之外的第二个、第三个独立实现来得有多快。命名文档里那句自我要求,其实也是它自己的考题:
A sentence here that no test enforces is a wish, not a standard.
一句没有被测试约束的话,是愿望,不是标准。
写在最后
如果你在做 Agent 产品、Agent 网关、或者任何一个要"同时接好几个 harness"的东西,UHP 值得花一个下午读一遍——不是因为它现在一定用得上,而是因为它把这一层该长什么样,用很具体的条款讲了一遍。哪些该是协议层的、哪些该留在实现里、哪些字段是越权原语、模型身份为什么必须可审计,这些问题它都给了明确的答案。
我这篇拆得还算细,但规范本身比我讲的更值得读。仓库在 github.com/HarnessRouter/harnessrouter,站点在 unifiedharnessprotocol.org。我最想看到的下一步,是 docs/IMPLEMENTATIONS.md 里那张实现列表,在接下来一两周里多出几个和大厂无关的名字。
如果你也在琢磨"控制平面该画在哪一层",之前写过两篇可以一起看:一篇是 《Dots vs Muse:永远在线 Agent 的架构之战》,讲的是 agent 底层运行架构的分歧;还有 《OpenCode vs Claude Code vs Cursor》,正好是 UHP 想抹平的那种"每种 harness 一套驱动方式"。工具与能力的供应链那一侧,可以接上 《Agent 供应链安全》 里对 MCP 生态威胁的梳理——UHP 的 plugin 章节,本质上就是在给这条链补上"包怎么安全落地"的那一环。