Skip to content

什么是 Agent Surface ​

Agent Surface 是 Web 页面向外部 Agent 提供能力的一组协议边界。页面把已有业务动作投影为可发现的声明,并在受限会话中接受调用。Surface 不运行 Agent,不解析 DOM,也不规定传输;它只规定页面公开什么、外部客户端能观察到什么,以及一次调用必须经过哪些检查。

这层边界处理的是页面内外的差异。页面内代码可以直接访问运行时、状态和闭包;浏览器扩展、桌面客户端或远程 Agent 不能。外部客户端若只能依赖 DOM、截图或另一套后端 API,通常可以完成一次点击,却难以判断这个按钮代表读取还是不可逆写入、作用于哪条记录、是否需要确认,以及业务状态是否真的改变。

问题的形态 ​

以一个订单管理页面为例。列表每行显示订单号、客户和备注,并提供行内编辑。订单号 SO-1002 是业务系统里的稳定标识;当前 DOM 中,这一行的备注输入框可能渲染为 #note-42,旁边是保存按钮。页面内部对应的动作可以声明为 update_note@order,输入是记录 id 和备注文本。

假设运营人员要把 SO-1002 的备注改为「客户要求周五前回电」。在页面内部,这段逻辑是一个已有函数;在页面外部,它只能表现为找到输入框、填入文本并触发保存这一组事件。

两者并不等价。#note-42 属于当前渲染,重新排序、翻页或组件重渲染都可能改变它;update_note@order 描述业务动作,只要记录仍在会话可见范围内,客户端就可以用 record: "SO-1002" 指定目标。Agent Surface 的交换单位是后一种,也就是页面声明过、能够被再次执行的动作。

因此,Surface 不是一套通用浏览器 API。它不提供 click(selector)、eval(script) 或任意 DOM 步骤,也不替页面推断未声明的操作。它只开放页面所有者愿意公开的能力,并把调用送回原有执行路径。

当页面不能或不愿声明能力时,外部客户端仍可使用 CDP、Playwright 或视觉自动化。那些路径解决的是如何操作一个界面;Surface 解决的是页面愿意以什么业务语义接受操作,以及这些操作受什么边界约束。两者可以并存,但不应混为一谈。

契约的三个部分 ​

Surface 的输出和检查围绕三组信息展开。它们不是三个服务或三个进程,而是同一份契约中相互约束的部分。

部分回答的问题主要内容
能力页面允许执行什么稳定 id、说明、输入结构、影响类型、确认策略、作用域
上下文当前页面是什么状态路由、区域、记录、可引用 id、状态视图、反馈、不可用项
控制谁在执行,结果是什么已验证会话、作用域、有效期、撤销、确认、结构化结果、审计

能力是契约的核心,但单独一份动作清单不足以安全执行。客户端还需要知道动作作用于哪条记录、当前是否可用、是否需要人工确认,以及执行后业务是否改变。上下文提供选择动作所需的事实;控制限定本次会话能做什么,并为结果提供可核验的结构。

能力使用业务名称,不使用选择器。下面是一个折叠后的行内动作示例:

text
update_note@order
  effect: write
  approval: ask
  inputSchema:
    note: string
  record:
    param: record

update_note@order 是稳定模板。inputSchema 声明业务参数 note;record 是折叠模板的元数据,表示调用时要用名为 record 的输入指定具体行。具体记录 id 来自上下文的 recordIds,实际调用时与 note 一起放进 input。页面解析该行后执行同一套可用性、确认和权限检查。模板不会因为列表被筛选、排序或滚动而改变。

有效能力不是页面声明的全部能力。设 C_page 为页面声明的集合,C_session 为已验证会话允许的集合,则本次可用的能力为:

text
C_eff = C_page ∩ C_session

describe 和 invoke 都只作用于 C_eff。不在交集中的能力既不会被外部客户端发现,也不能通过该会话调用。C_session 通常由宿主或传输层在验证用户、Agent、作用域和有效期后构造;Surface 本身不读取 Cookie、JWT、OAuth token 或委托密钥。

上下文同样不是页面快照。它只包含选择和检查动作所需的信息,并对省略给出说明。例如,一个有一千条订单的页面不应发送一千条完整记录,而应返回当前窗口、可引用记录 id、总数,以及按 id 读取单条记录的方式。外部客户端据此知道哪些记录可引用,但不会获得整个 DOM 或整个应用 store。

与页面内 Agent、外部 Agent 的关系 ​

Surface 位于页面能力与外部客户端之间,不替代页面运行时:

text
页面业务代码
  ├─ 能力注册表
  │    ├─ 页面内 Agent(可直接调用)
  │    └─ Agent Surface ── 传输 ── 外部 Agent
  └─ 页面执行器(由页面内 Agent 与 Surface 共用)

页面内 Agent 是运行在页面中的客户端,可以直接读取注册表和状态,并调用执行器。外部 Agent 不共享页面闭包,因此只能通过 Surface 获得序列化的能力投影,再经传输层发起调用。两者最终使用同一份能力声明和同一个执行器。

这一关系不要求页面同时具备两类客户端。一个页面可以只有页面内 Agent,也可以只开放 Surface,或者同时提供两者。Surface 不是页面内 Agent 的远程副本;它不需要一个页面内 Agent 存在,也不规定外部客户端如何规划任务。

同样,Surface 不新增一套业务实现。它引用现有的注册表和执行器,因此动作的可用性、确认、人工接力、校验和后端请求仍由页面原有逻辑决定。若页面内 Agent 能执行某个动作而外部客户端不能,差异应来自会话作用域、确认策略或后端授权,而不是两套实现之间的行为漂移。

一次调用的路径 ​

外部客户端通常先调用 describe,再调用 invoke。

describe 是无副作用的读取。它校验协议版本、请求 id 和会话,然后返回经过作用域裁剪的能力清单与有界上下文。下面的结构是经 handle() 或传输返回的响应包络,只保留说明选择过程所需的字段;直接调用 surface.describe() 时,返回值从 result 的内容开始,不包含外层 ok 包装。

json
{
  "protocol": "pling.surface/0.1",
  "ok": true,
  "result": {
    "capabilities": [
      {
        "id": "update_note@order",
        "description": "修改订单备注",
        "inputSchema": {
          "type": "object",
          "properties": {
            "note": { "type": "string" }
          },
          "required": ["note"]
        },
        "effect": "write",
        "approval": "ask",
        "record": { "param": "record", "total": 48 }
      }
    ],
    "context": {
      "recordIds": ["SO-1001", "SO-1002"],
      "recordIdsTotal": 48,
      "recordsTotal": 48
    }
  }
}

这里的 record 是能力级元数据,不是业务输入。param: "record" 指定调用时用于选择目标记录的输入键;total: 48 表示这个模板当前覆盖 48 条可引用记录。若部分记录不在视口内,还会出现 offscreen 计数。

对应的 invoke 因而包含两个部分:inputSchema 定义的业务参数 note,以及折叠动作要求的记录 id record。Surface 用 record 还原具体动作,并在调用页面执行器前移除这个协议参数;执行器只收到业务参数。

客户端从返回结果中选择能力、记录和输入,然后发起 invoke:

json
{
  "protocol": "pling.surface/0.1",
  "id": "req_01",
  "method": "invoke",
  "capability": "update_note@order",
  "input": { "record": "SO-1002", "note": "客户要求周五前回电" }
}

页面侧处理一次 invoke 时,顺序大致如下:

  1. 校验请求包络、协议版本、请求 id 和方法。
  2. 解析已验证会话,拒绝缺失、过期或已撤销的会话。
  3. 计算有效能力集合,拒绝越界能力。
  4. 解析记录与输入,检查动作当前是否可用。
  5. 通过页面执行器执行;该执行器保留人工界面使用的校验、确认与业务路径。
  6. 返回结构化结果,并在能够观察时附带有界的 after 变化。
  7. 按宿主配置写入元数据审计;限流和防重放等保护在调用执行器之前生效。

成功响应表示请求已被理解并按契约处理,不等同于业务一定接受了操作。执行结果需要说明动作、目标记录、成功或失败,以及可观察到的变化。需要审批时,客户端可以知道动作处于等待状态,但不能自行批准;批准发生在页面内的可信流程或业务后端。

与相邻概念的关系 ​

概念负责什么与 Surface 的关系
页面内 Agent在页面中理解意图、选择动作并调用运行时可以使用同一注册表和执行器;不依赖 Surface
外部 Agent在页面外规划并调用能力通过某种传输使用 Surface;Codex 只是其中一个客户端例子
MCP把工具暴露给 Agent runtime 的协议与传输方式可以承载 Surface 能力,但不定义 update_note@order 的业务含义
CDP / Playwright / 视觉自动化操作浏览器、DOM 或界面不要求页面声明能力;可作为无契约页面的执行路径
业务后端判断当前用户能否对具体资源执行操作并提交事务Surface 为它携带请求与结果证据,但不替代它授权

MCP 常被误认为 Surface 的同义词。二者解决的问题不同:MCP 规定工具如何出现在 Agent 的调用列表中,Surface 规定页面提供哪些业务能力、上下文如何裁剪、会话如何约束、结果意味着什么。一个 MCP 服务器可以转发 Surface 请求;MCP 本身不会说明 approve_order 是否需要第二位审批人,也不会证明审批已经发生。

CDP 和视觉自动化也不与 Surface 争夺同一位置。它们可以在页面没有契约时工作,代价是依赖 DOM、布局或视觉推断。Surface 提高的是有页面配合时的准确性与可审计性;它不能阻止已经拥有浏览器底层权限的客户端绕过契约。

保证与限制 ​

在契约范围内,Surface 提供以下性质:

  1. 客户端只能看到和调用已声明的业务能力,不能通过协议获得通用 DOM 控制。
  2. 上下文有选择、有上限,并说明省略,不返回整个 DOM 或整个 store。
  3. 调用经过页面执行器,与人工界面共享可用性、确认和业务校验。
  4. Cookie、JWT、OAuth token 与委托密钥不进入协议上下文或模型上下文。
  5. 错误和结果具有结构;未知版本、未知方法、越界能力和不可用保护均失败关闭。

这些性质不构成以下保证:

  • Surface 不保证业务后端一定接受调用。会话有效并不代表用户有权限、订单状态允许或审批已经完成。
  • Surface 不提供认证、授权、传输、持久化或审计存储本身;这些由宿主实现。
  • Surface 不能阻止拥有 CDP 或等价权限的 Agent 在契约之外操作页面。
  • pling.surface/0.1 目前是带参考实现的工作草案,不是既定标准。

适用场景 ​

Surface 适合这样的页面:业务动作已经存在且语义稳定;页面所有者愿意明确开放其中一部分;外部客户端需要在不同会话中重复执行这些动作;开放范围、确认和结果需要被约束和记录。

它不适合替代所有浏览器自动化。页面无法修改、任务需要任意探索、目标只是抓取页面或执行未声明操作时,CDP、Playwright 或视觉自动化仍然是更直接的工具。高风险操作也不应仅因为“可以声明”就开放;它们需要后端的最终授权和可信的人工确认。

当前状态 ​

@plingjs/surface 已实现传输无关的 describe / invoke 内核,以及会话作用域、过期与撤销检查、可注入的限流与防重放保护、有界上下文、增量 after、折叠行内动作、审计元数据和审批证据剥离。参考仓库中包含页面集成与外部客户端示例。

pling.surface/0.1 是这一实现所对应的工作草案。具体页面能否安全、稳定地开放某组能力,仍需结合该页面的执行路径和后端授权进行验证。

小结 ​

Agent Surface 让外部客户端使用页面已经声明的业务动作,而不必从 DOM 或像素重建页面。它把可调用能力、受限上下文和执行控制放在同一份契约中;动作如何实现、是否授权以及是否产生业务效果,仍由页面执行器和业务后端决定。