引子:远程 Agent 给你「画」了个界面
设想你在和一个 Agent 聊天,让它帮你订一桌餐。它没有跟你来回文字拉扯(几人?哪天?几点?),而是直接甩给你一个带日期选择器、时间段、确认按钮的表单——你点几下就搞定。
关键来了:这个表单,不是你本地 App 预先做好的页面,而是那个跑在别的公司服务器上的远程 Agent,「画」出来递给你的。
这立刻引出两个尖锐的问题:
- 你怎么敢渲染一段来自不可信远程 Agent 的 UI?万一它塞段恶意 JS 呢?
- 你又怎么让这份 UI 在 Web、手机、桌面上都长得一样,还完美继承你 App 的样式?
传统答案是把 HTML/JS 塞进 iframe——又重、又割裂、又是安全噩梦。Google 给出了另一个答案:A2UI(Agent-to-User Interface),一套声明式 UI 协议。它的设计纲领一句话能说完:
传输一种 UI——像数据一样安全,又像代码一样有表现力。
这篇就讲透 A2UI:为什么需要它、它的三个反直觉设计、安全模型,以及 UI 协议「三巨头」里它和 MCP Apps、ChatKit 到底差在哪。最后落到一个实践——如何把它的思想借到一个自研 Agent 框架(以我的 Hermes 为例)。
一、第一性原理:跨信任边界的 UI
核心矛盾
理解 A2UI 要先抓住它要解决的那个矛盾。
- 过去:Agent 和 UI 在同一个 App 进程里,Agent 想画什么直接操作 DOM 就行,因为完全可信。
- 现在(多 Agent 时代):干活的 Agent 往往是远程的——跑在别人服务器、别的组织里。它碰不到你的 UI,只能发消息。
那远程 Agent 想给用户呈现富交互界面(表格、图表、表单),怎么办?传统答案是 iframe 嵌 HTML/JS。问题一堆:重、视觉割裂(永远不匹配宿主样式)、安全边界复杂。
破局:把「生成 UI」和「执行 UI」解耦
A2UI 的解法是解耦:
- Agent 只「描述」UI 意图——发一段声明式 JSON 数据。
- 客户端用「自己的」原生组件渲染——完全掌控安全与样式。
因为 Agent 发出的是「不可执行的数据」,所以安全;又因为描述足够丰富,所以有表现力。这就是「safe like data, expressive like code」的来历。
本质上,这是 Server-Driven UI(SDUI)的复兴。Instagram 原生 App 约一半屏幕已是 SDUI。A2UI 把它从「自家服务端→自家客户端」(信任的)升级为「别人家的 Agent→我家客户端」(不信任的)这个更难的场景。
二、三层架构:把 UI 拆成三个正交层
A2UI 把 UI 严格拆成三个正交层,这是理解它一切机制的基座:
| 层 | 关心的问题 | 控制权 |
|---|---|---|
| ① UI 结构层 | 页面由哪些组件、怎么组合 | Agent |
| ② 应用状态层 | 每个数据字段的当前值(Data Model) | 数据绑定(双向) |
| ③ 渲染执行层 | 抽象组件如何映射到原生 widget | 客户端 |
为什么要这么拆?因为这三件事的变化频率和归属方完全不同:结构是 Agent 临场生成(频繁变)、数据是用户交互产生(实时变)、渲染是客户端固有能力(基本不变)。
把「渲染」完全交给客户端,就实现了「一次生成、到处渲染」的跨平台——同一份 JSON 能在 Web(Lit/Angular/React)、移动(Flutter/SwiftUI/Compose)、桌面渲染。
flowchart TB
L1["① UI 结构层 Structure<br/>组件树 → Agent 生成"] -->|通过 path 绑定| L2["② 应用状态层 State<br/>Data Model → 双向绑定"]
L2 -->|驱动更新| L3["③ 渲染执行层 Rendering<br/>原生 widget → 客户端掌控"]
L1 -->|映射| L3
三、为 LLM 量身的设计:扁平列表 + ID 引用
这是 A2UI 最反直觉、也最精妙的设计。
组织组件有两种方式:传统的嵌套树(像 HTML),和 A2UI 的扁平列表 + ID 引用。看个真实例子(v0.9.1):
1 | {"version":"v0.9.1","updateComponents":{"surfaceId":"booking","components":[ |
注意 submit-btn 的 child: "submit-text"——它不物理嵌套一个文本节点,而是引用另一个组件的 id。整个 UI 是一个扁平的组件列表,靠 id 互相引用组装。
为什么不用更「自然」的嵌套树?因为要顺着 LLM 的脾性,而不是跟它较劲:
| 维度 | 嵌套树 | 扁平列表 + ID |
|---|---|---|
| 闭合性 | 必须精确匹配括号,漏一个全错 | 每条独立,无嵌套闭合压力 |
| 流式生成 | 难(要等完整树) | 易(逐条吐) |
| 纠错 | 牵一发动全身 | 补一条覆盖某 id |
LLM 是 token-by-token 自回归生成,深度嵌套 JSON 要它「一次写对全部括号」违反生成特性——越深越容易错。扁平列表把「结构关系」从语法嵌套变成语义引用,让 LLM 可以边想边吐、错了就补。
这是「LLM-friendly」从口号落到协议细节的典范。好的协议设计要顺着生成模型的脾性,而不是逼模型适应协议。
配合数据绑定(组件用 path 绑定 Data Model,而非直接持有值),组件成了无状态纯渲染器——状态全在 Data Model,事件全回 Agent。这一进一出(value.path 读数据、action.event 发事件)构成组件的 I/O 契约,是跨平台渲染能成立的前提。
四、安全模型:Catalog 白名单与「消灭执行面」
A2UI 的安全核心是一句话:Agent 只能从客户端的「菜单」里点菜,不能冲进厨房。
这个「菜单」叫 Catalog——客户端预定义的可信组件白名单。Agent 说「我要一个 Button」,但 Button 的实际实现是客户端自己的。Agent 无法注入代码、无法访问 DOM、无法做任何超出 Catalog 的事。
flowchart TB
AG["Agent 想渲染某组件"] --> CHECK{"在 Catalog 白名单?"}
CHECK -->|是 预批准| OK["允许, 用客户端原生实现"]
CHECK -->|否 未批准| BLOCK["拒绝, 防 UI 注入"]
那如果客户端想要更复杂的能力(嵌入一个图表、甚至受控的 iframe)怎么办?用 Smart Wrapper(开放注册表):开发者把任意已有组件注册进 A2UI,把安全策略(沙箱、「信任阶梯」)写在自己的组件逻辑里,而非依赖协议核心兜底。
进阶:双重 iframe——嵌入不可信 HTML 的极致隔离
A2UI 甚至能安全嵌入一段不可信的第三方 HTML(比如一个 MCP App)。它用了一个反直觉的「双重 iframe」:
- 外层 Sandbox Proxy(同源、不沙箱、验证来源)
- 内层(allow-scripts 但严禁 allow-same-origin)
为什么要双层?因为单 iframe 若同时 allow-scripts + allow-same-origin,可编程操作父 DOM 甚至移除自身 sandbox 逃逸。A2UI 内层严禁 allow-same-origin,使第三方 HTML 因独特 origin 失去 localStorage/cookie/IndexedDB,且无法触碰父 DOM。所有跨信任边界的数据流,都必须经 Agent 这道「中介」——零信任在 UI 层的极致体现。
为什么不直接用 HTML/iframe?
这是社区最大的质疑。A2UI 的回答很硬:浏览器 HTML 天然绑定 CSS+JS,本质是「远程代码执行引擎」。企业多 Agent 场景下(让第三方 SaaS 的 Agent 跟自家 Agent 并存),接受任意 HTML 就是接受代码注入。把 UI 抽象成声明式 JSON,从根上消灭了「执行」这个攻击面。
五、UI 协议三巨头:信任放哪,决定了你是谁
A2UI 不是唯一解。当下 Agent UI 协议有「三巨头」,它们的本质差异就一句话——信任放哪:
| 维度 | A2UI(Google) | MCP Apps(MCP 官方) | ChatKit(OpenAI) |
|---|---|---|---|
| 信任放哪 | 客户端 catalog(无执行面) | iframe 沙箱(容忍执行但隔离) | OpenAI 平台(全栈托管) |
| 渲染方 | 客户端原生组件 | iframe | OpenAI 托管 widget |
| 自由度 | 中(catalog 约束) | 高(任意 HTML/JS) | 低(widget 类型约束) |
| 样式一致性 | 完美继承宿主 | iframe 隔离(割裂) | OpenAI 主题 + 有限定制 |
| 跨平台 | 声明式天生跨平台 | 绑 Web/iframe | 绑 OpenAI SDK |
| 生态 | 开放(A2A/AG-UI) | MCP 生态 | OpenAI 闭环 |
选型 = 回答「你愿意把 UI 安全托付给谁」:
- 企业多 Agent Mesh、要跨平台原生感 → A2UI
- MCP 工具、要高自由度富交互 → MCP Apps
- OpenAI 闭环、要快速上线 → ChatKit
三者分层共存,非一统天下。而且 A2UI 和 MCP Apps 互补——A2UI 的 custom 节点能嵌入 MCP App(双重 iframe),Google 官方称「两者结合最佳」。
一个判断:现在是「汽车工业早期 80 家厂商」阶段。真正能赢的标准,会是「无聊到产品团队能忍受 5-10 年」的那个,而非 demo 最酷的。A2UI 的「声明式 + 无执行面」最符合长期标准化特征,前提是 v1.0 稳定 + 渲染器生态补齐。
六、与 A2A:传输底座
A2UI 反复强调「传输可插拔(A2A / AG-UI / SSE / WebSocket)」,它不绑定传输。其中 A2A(Agent2Agent) 是最重量级的传输底座——A2UI 的 JSONL 消息,正是作为 A2A 的 data 类型 Part,在 Agent 间流式传输的。
A2A 提供的「有状态、可流式、可中断恢复」传输,恰好是 agent-driven UI 在多轮对话里的刚需。A2A 的 Task 生命周期承载 A2UI 的 Surface 生命周期,SSE 承载渐进渲染。
一句话:A2UI 定义「UI 长什么样」,A2A 定义「UI 怎么传过来」——一个管表现层,一个管通信层。 A2A 的深度解读见本系列另一篇。
七、落地:从 A2UI 借鉴,给 Hermes 设计结构化输出
理论讲完,落到一个实际问题:如果不直接采用 A2UI 协议(标准尚早期),怎么把它的思想用一个自研 Agent 框架? 以我的 Hermes Agent 为例。
Hermes 当前的输出主要是自然语言散文(给人看的),下游消费方是 CLI 和 Telegram。直接上 A2UI/MCP Apps 的 iframe/widget 模型过重。但 A2UI 的三个核心思想可以轻量迁移:
| A2UI 思想 | Hermes 落地 |
|---|---|
| ① 声明式契约 | schema 描述意图,非执行命令 |
| ② 状态/渲染分离 | schema 只描述数据,CLI/Web/Telegram 各自渲染 |
| ③ 扁平 + 版本化 + 增量 | 扁平 JSON + schema_version + Task 状态流 |
一个意外收获:Hermes 自己的记忆里已经记录了「结构化生成成为标准实践」(Outlines/Guidance/Instructor,Pydantic→JSON Schema→FSM)。所以这套 schema 不是引入新依赖,而是把 Hermes 已学的技术用到它自己的输出上——学以致用的闭环。
三个 schema(对齐 Hermes 真实接口)
我把 Hermes 的 cron/jobs.json、messages_send、任务状态这些真实接口,抽象成三个 Pydantic schema。每个都带 schema_version(对应 A2UI 的 version 字段),字段扁平(对应 A2UI 的扁平列表)。
HermesCronJob(对齐 jobs.json)——让 LLM 直接产出可被 hermes cron create 消费的结构化意图,而非自然语言:
1 | class HermesCronJob(BaseModel): |
HermesNotify(对齐 messages_send + 4 个通知模板)——把散文模板升级为结构化消息,多端按 category 差异化渲染:
1 | class NotifyCategory(str, Enum): |
CUSTOM 预留位 + custom_kind 校验,是为 Hermes 自进化产生的新通知类型(技能进化报告、Dojo 结果)留口子——避免频繁改枚举,又用校验防滥用。
HermesTask(任务执行单元 + 状态流)——对应 A2UI 的增量更新(updateDataModel):
1 | class TaskState(str, Enum): |
最有意思的是 HermesTask 和 A2A 的 Task 模型天然同构——这意味着 Hermes 未来若要接入多 Agent Mesh,HermesTask 几乎可直接映射为 A2A Task,接入阻抗极低。这是一个「先按 A2UI/A2A 思想设计自己的 schema,未来自然对接标准」的低风险路径。
落地路径
- Phase 1:Pydantic 定义三个 schema(复用 Hermes 已学的 Outlines/Instructor)
- Phase 2:接入 hermes cron create / messages_send
- Phase 3:Task 状态流 + WebUI 卡片渲染
八、判断与启示
把 A2UI 放回更大的图景,四点判断:
1. A2UI 是「跨信任边界 agent-driven UI」当前最正经的开放答案。 它用声明式 + Catalog 白名单,从根上消灭执行面,做到「安全如数据、表达如代码」。在企业多 Agent Mesh 场景,这套设计哲学是对的。
2. 三个反直觉设计是它的精髓。 三层解耦、扁平列表+ID 引用(为 LLM)、Catalog 守门——每一个都是对「UI 该怎么生成」的重新思考,而非堆功能。
3. 三巨头分层共存,A2UI 与 MCP Apps 互补。 别纠结「选谁」,按场景:Mesh 跨平台选 A2UI,MCP 富交互选 MCP Apps,OpenAI 闭环选 ChatKit。
4. 对自研框架,思想比协议更值得借。 标准尚早期(v1.0 候选、渲染器未齐),现在 all-in 风险高。但它的「声明式输出 + 状态/渲染分离 + 扁平版本化」思想,可以立刻用 Pydantic 落到自己的 schema 上——既改善当下输出,又为未来对接标准留好低阻抗通道。
Agent 时代正在重走互联网协议分层的路:UI 表现层(A2UI)、Agent 通信层(A2A)、工具接入层(MCP),各有一套开放标准,各司其职。A2UI 补上的,是「Agent 怎么把界面安全地递给用户」这一层。理解了它,你就能判断:当远程 Agent 要给你「画」个界面时,什么样的设计才既敢用、又好看。
信息来源
| 来源 | URL |
|---|---|
| A2UI 官网 | https://a2ui.org/ |
| What is A2UI | https://a2ui.org/introduction/what-is-a2ui/ |
| Google 官方公告(2025-12-15) | https://developers.googleblog.com/introducing-a2ui-an-open-project-for-agent-driven-interfaces/ |
| GitHub a2ui-project/a2ui | https://github.com/a2ui-project/a2ui |
| Hacker News 讨论 | https://news.ycombinator.com/item?id=46286407 |
| MCP Apps in A2UI(双重 iframe) | https://a2ui.org/guides/mcp-apps-in-a2ui/ |
| 下一篇:A2A 协议深度解读 | (本博客 Agent UI 系列) |