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