受治理的桌面自动化
adk-computer-use 是位于 computer-use-mcp 桌面自动化服务器之上的治理层。它本身不执行任何操作:它将观察、批准、变更和验证组织为一个确定性的图,并检查外部运行时返回的内容。
响应绑定
外部运行时具有权威性,但其响应在进入图状态之前会先在本地进行检查。对于每个 ComputerUseRuntime 实现,这些检查都在参考图中运行,而 MCP 适配器会在其直接调用边界再次执行这些检查。类型化反序列化只能证明形状,而不能证明来源——ControlLease、TargetReservation 和 ExecutionReceipt 都没有强制不变量的构造函数,因此,属于其他会话的一个格式正确的对象也会被顺利解析。
每个响应都会重新绑定到生成它的请求:
| 响应 | 与信封进行核对 |
|---|---|
ControlLease | session_id, principal_id, agent_id, execution_mode, 活动状态、未过期 expires_at、剩余 配额(actions_used < action_budget),以及位于 boundaries 内的目标 |
TargetReservation | session_id, principal_id, agent_id, execution_group_id, intent_id == action_id, 活动状态、未过期的 expires_at,以及精确的应用/窗口目标范围 |
ExecutionReceipt | session_id、action_id和action_digest,针对信封的args_digest |
不匹配会产生 ComputerUseError::IdentityMismatch,指出该字段,响应会被拒绝而不是存储。
use adk_computer_use::runtime::binding::validate_lease;
// Refuses a lease that is well-formed but belongs to another session.
validate_lease(&lease, &envelope)?;
注意: 摘要是可用的最强绑定。
args_digest是批准所依据的内容,因此携带不同摘要的收据描述的是不同的工作,即使每个标识符都匹配。
一个可选字段如果返回时缺失,会被视为规格不足,而不是矛盾;一个存在且不同的字段则是不匹配。
执行时重新验证
预览不是变更权限。引用图会在调用 execute_action 之前,立即在 execute 节点重新验证所有变更输入:
| 输入 | 即时检查 |
|---|---|
ActionEnvelope | RFC 3339 时间戳、正的有效期窗口,以及 now < expires_at |
ControlLease | 标识、模式、激活状态、剩余预算、过期时间和目标边界 |
TargetReservation | 身份、动作意图、活动状态、过期时间,以及精确的目标范围 |
| 批准 | 路由、动作摘要、策略摘要,以及恰好一个权限来源(grantId 或运行时持有的批准) |
当预览返回时,包络检查也会运行,因此一个已经过期的预览永远不会到达 预留或批准。它会在执行时再次运行,因为批准中断、目标 预留或租约获取都可能消耗剩余的有效期窗口。
ComputerUseMcpRuntime 保留了 preview_action 返回的精确包络。若在预览之后任何包络字段发生变化,
直接的 execute_action 调用会被拒绝,即使动作 ID 仍然匹配。
对于检查点恢复,图会将运行时预览存储在仅追加状态通道中。 批准和执行要求该通道恰好包含一个与活动 预览匹配的预览。恢复输入可以添加状态,但不能替换检查点中的值:试图 替换会创建第二条条目,并在预留或变更之前失败。
预留清理
一旦预留被接受,每条终止路径都会尝试释放它:
- 租约获取、授权、验证、执行、收据和验证失败;
- 验证成功;以及
- 当运行时返回了一个预留时,预留验证或序列化失败。
当清理成功时,主失败仍保持为主失败。如果清理也失败了,图会 返回一个确定性的错误,其中包含这两个失败。如果在其他方面成功的验证之后清理失败, 则会作为错误返回,而不是被隐藏。
验证不是承诺
ComputerUseRuntime::verify 返回一个 VerificationOutcome,而不是布尔值:
| 结果 | 含义 | is_verified() | is_committed() | status() |
|---|---|---|---|---|
Verified | 声明的后置条件被观察到成立 | true | true | completed |
CommittedUnverified | 运行时执行了该动作;没有后置条件证据 | false | true | committed_unverified |
Failed | 未提交,或者证据与后置条件相矛盾 | false | false | verification_failed |
图中的 verify 节点会写入 verified、committed,以及一个 result.verificationDetail,用于说明短于 Verified 的任何内容。
重要: 已提交的收据只是对操作已被接受并执行的确认。它并不能证明预期效果已经发生。
verify之前返回receipt.status == Committed,它将一个已提交但无效的操作报告为 已完成 —— 来自一个标记为 "verify" 的节点。
什么算作证据
对于声明摘要值的后置条件(valueDigest、contentDigest),验证需要
在收据结果上有一个与之匹配的 verification.observedDigest。对于仅声明存在性的后置条件,则需要一个显式的 verification.satisfied: true。
| 收据证据 | 结果 |
|---|---|
verification.satisfied: false | Failed — 一个明确的负向观察 |
observedDigest 与预期摘要匹配 | Verified |
observedDigest 不同 | Failed |
无 verification 对象 | CommittedUnverified |
verification 存在,无 observedDigest,应有摘要 | CommittedUnverified |
缺乏证据绝不应被视为成功的证据。如果 computer-use-mcp 在发出收据之前进行验证,那是它的契约;此适配器不会假定这一点。
租约限制
除非满足以下所有条件,否则会拒绝租约:
| 检查 | 在以下情况下拒绝 |
|---|---|
| 到期时间 | expires_at 已过期,或无法解析为 RFC 3339 |
| 剩余预算 | actions_used >= action_budget |
| 目标边界 | 信封的 target.app_id 不存在于一个非空的 boundaries.app_ids 中 |
| 窗口边界 | 信封的 target.window_id 不存在于一个非空的 boundaries.window_ids 中 |
空边界表示“未限定”,并授权任何目标——没有限制并不等于 不对任何内容进行限制。
重要: 这个验证器的第一个版本检查的是
action_budget == 0,也就是 总预算,因此一个带有action_budget: 1, actions_used: 1的租约在授权 不进行任何操作时也会通过。它还从未读取expires_at或boundaries,所以一个已过期的租约和一个作用域 属于不同应用程序的租约都被接受了。无法解析的过期时间会被拒绝,而不是 被忽略:其有效性无法确定的租约是无效的。