---
slug: "what-an-approval-allows"
title: "用户点了“允许”，Agent 究竟获准做什么？"
description: "批准一封邮件之后，Agent 能换个收件人再发送吗？沿着 xopc 的外部连接器执行路径，看看一次批准如何绑定账户、动作和参数，以及为什么一次性批准仍然解决不了所有重试问题。"
date: "2026-09-21"
author: "xopc"
language: "zh-CN"
readingTime: "约 10 分钟"
number: "03"
category: "执行与授权"
cover: "/blog/what-an-approval-allows/images/cover.png"
anchors:
  "先看一封改过收件人的邮件": "changed-recipient"
  "账户连上了，动作也未必能执行": "policy-layers"
  "把批准绑定到具体动作": "exact-action"
  "给人看的预览，与给代码的校验": "preview-and-hash"
  "一次批准怎样走到执行": "approval-lifecycle"
  "已使用，不等于已发送": "execution-gap"
  "批准要回答的问题": "verification"
---

## 先看一封改过收件人的邮件

你让 Agent 给同事发一份进度说明。它准备好了收件人、标题和正文，停下来等你确认。你看完，点了“允许”。

随后，Agent 发现另一个人也在项目里，于是给邮件加了一个抄送地址。它手里仍然有刚才的批准。这次发送，还算你同意的那件事吗？

如果批准只是会话里的一个 `confirmed = true`，系统很难回答。这个标记表达了“用户同意过”，却没有说明同意的是哪封邮件、哪个账户，或者哪一组参数。

xopc 的外部连接器路径会把这些条件单独记下来。下面以 Composio 连接器的执行实现为例，拆开一次动作从策略检查、请求批准，到使用批准的过程。邮件只是为了把问题说具体；文中的字段示例也经过简化，不是某个邮件 API 的完整参数格式。

> 本文描述这条连接器执行路径。不同工具有各自的权限机制，不能把这里的规则推及所有命令、浏览器操作或第三方服务。

## 账户连上了，动作也未必能执行

授权某个外部账户，解决的是“系统能以哪个身份访问服务”。允许某个 Agent 用这个账户做哪些事，还需要本地策略。

`evaluateConnectorExecutionPolicy` 按顺序检查连接器是否启用、Agent 是否被允许、账户是否在允许范围内，以及动作需要的权限是否超过上限。权限分为 `read`、`write` 和 `admin`。未被标记为 curated 的动作，在这层策略中还需要 admin 上限；这个标记是动作元数据的一部分，不代表系统已经证明该动作没有风险。

通过这些检查之后，才轮到“本次是否需要确认”。配置可以要求每次确认、写入时确认、管理员动作确认，或不要求这一步确认。也就是说，xopc 并没有把每一次工具调用都变成弹窗。

![连接器启用、Agent 与账户范围、动作权限上限先决定是否允许进入确认阶段；用户批准不会覆盖前面的拒绝。](./images/policy-gates.svg)

*图 1 · 策略检查的先后关系。确认只满足需要用户批准的那一项条件，不能把被禁用的连接器或越权动作变成可执行动作。*

例如，某个连接器只允许读取。用户点过一次确认，不会自动把它升级成可以写入。如果策略允许写入、但要求写入时确认，执行路径会返回 `confirmation_required`，此时外部动作还没有被调用。

这个顺序很重要。若先问用户“要不要继续”，再把答案解释成突破所有限制，就把两个不同的问题混在了一起：一是这项能力是否开放，二是用户是否同意本次使用。

## 把批准绑定到具体动作

需要确认时，xopc 会生成一条批准记录。里面有用户主体、连接器、连接、Agent、会话、动作 ID、参数指纹和过期时间；在对应的等待流程中，还会关联等待记录。

重新执行时，仅仅拿着一个 `approvalId` 不够。执行器会先检查它是否属于当前用户、连接器、动作、会话和 Agent，并核对原批准关联的连接所代表的账户。然后才检查参数指纹并尝试使用批准。

在这条路径里，指纹由三部分组成：传给动作的参数、账户 ID，以及可取得时的当前任务范围。任务范围来自当前 transcript、目标或输入的标识及目标修订信息。它让“同一会话里曾经批准过”不至于自然变成“同一会话以后都能沿用”。

```text
这次批准针对：
  账户       工作邮箱 A
  动作       发送邮件
  收件人     同事甲
  正文       已审阅的进度说明
  任务范围   当前输入或等待中的目标

重新调用时：
  同事甲 → 同事乙       参数指纹不同
  工作邮箱 A → 邮箱 B   账户不匹配
  发送邮件 → 删除邮件   动作不匹配
```

对象字段会先递归排序，再序列化并计算 SHA-256。因此，调整 JSON 对象的键顺序，不会凭空产生一个不同的批准对象；数组顺序和实际取值的变化仍然会影响指纹。

![同一组动作参数与账户产生相同指纹；更换收件人或正文后指纹改变，原批准不能沿用。](./images/approval-binding.svg)

*图 2 · 动作、身份与会话在独立字段中核对；参数、账户和任务范围参与指纹计算。图示省略了具体的连接标识和参数结构。*

这里的哈希承担的是一致性检查，不是给参数加密，也不是判断两段文字“意思差不多”。哪怕 Agent 只是觉得原文不够顺，改了正文，也应当把它当作变更后的动作，而不是替用户决定这点修改无关紧要。

这种做法会增加重新确认的机会。我们接受这个代价，因为“批准以后仍能修改什么”必须有清楚的边界。对于需要减少打扰的场景，应当通过明确的策略配置来处理，而不是把一次批准悄悄扩大成长期授权。

## 给人看的预览，与给代码的校验

用户不可能阅读一串哈希来决定是否发送邮件。因此，批准记录同时保存参数预览。

xopc 的预览函数会对名称命中 password、token、authorization 等模式的字段进行遮盖，并限制字符串长度、数组项数和嵌套深度。这样可以减少把凭据直接展示出来的机会，也避免确认区域被很长的工具参数撑满。

但预览和指纹承担不同工作。预览为了让人理解，允许省略；指纹对完整的动作参数计算，不能只用截断后的文字。否则，两段开头相同、后面不同的正文，可能在预览里看起来一样，却被错误地当作同一个批准对象。

这个区分也暴露了一项产品限制：**参数没有被替换，不代表用户已经看清全部参数。** 字段名匹配无法识别所有藏在正文中的敏感信息，截断也可能让重要内容落到预览之外。对于长邮件、批量修改等操作，一个真正好用的确认界面仍需要让人检查目标和完整内容。底层指纹解决不了这部分阅读体验。

## 一次批准怎样走到执行

批准记录最初是 `pending`。用户决定后，它可以进入 `approved` 或 `denied`。超过有效期时，也会在相应检查路径中变成 `expired`。这条 Composio 路径创建的批准目前有效期为十分钟。

执行端只有拿到尚未过期、状态为 `approved`、且参数指纹一致的记录，才能把它改为 `consumed`。读取和更新放在 SQLite 写事务里。已经使用过的记录不能再次充当批准。

需要注意，点“允许”并不等于当场调用了外部服务。在关联了等待记录的流程中，恢复函数还会检查当前等待是否仍然是原来的等待、属于同一个用户和 Agent，并且处于开放状态。恢复请求带着等待 ID、transcript ID 和版本信息；旧的确认结果不能随便唤起一个已经换了目标的等待。

![批准从待确认到已批准，再经匹配与有效期检查成为已使用；之后才进入外部调用，调用成功或失败分别记录。](./images/approval-lifecycle.svg)

*图 3 · 批准状态和执行结果是两套信息。这里的“已使用”表示本次批准已被消耗，不表示第三方服务已经完成动作。拒绝与过期路径为简化说明。*

一次性使用有实际价值：重复提交同一个批准 ID，不会无限得到新的执行机会。它也要求调用方认真处理失败，不能遇到报错就原样循环重试。

## 已使用，不等于已发送

现在来看这套设计最不好处理的一段时间差。

执行器先消耗批准，再进入带策略检查的外部执行路径。接下来可能发生三种情况：服务明确返回成功；本地或远端在动作发生前报错；动作已经发生，但响应在网络上丢失了。

对于最后一种情况，客户端看到的超时，不能证明邮件没有发出去。原批准已经使用，也不能反过来证明邮件一定发出去了。`consumed` 表示的是授权凭据的状态。

有人可能会问：那能否先执行成功，再把批准标记为已使用？这样又会留下另一个窗口：外部服务已经做完，本地还没来得及更新就崩溃。重启后仍看到 `approved`，于是可能再做一遍。

本地 SQLite 事务管得住本地状态，无法把远端邮件服务也包进同一个事务。**一次性批准不等于外部操作“恰好执行一次”。** 这条实现路径并没有靠批准表单独兑现这样的保证。

要继续减少重复动作，需要看具体服务是否支持幂等键、能否通过返回的资源 ID 查询状态，或者能否先核实外部结果再决定重试。这些是进一步处理不确定结果的办法，并非本文这段批准机制已经统一实现的能力。

连接器执行审计也因此区分策略决定与结果状态：等待确认或被策略拒绝，可以记录为未执行；进入外部调用后，再记录成功或错误及相关耗时。查问题时，需要把批准记录、执行审计和外部事实一起看。只看到一个绿色“已批准”，还不足以向用户报告“邮件已发送”。

## 批准要回答的问题

这个设计最终要回答的是一个很普通的问题：**用户刚才同意的，和 Agent 现在准备做的，还是不是同一件事？**

账户、动作、参数、任务范围和有效期，把这个问题拆成代码可以核对的条件。外部执行是否成功，则继续交给执行结果和可查询的事实回答。两边都说清楚，批准才不会变成一个看起来令人放心、实际含义却很模糊的按钮。

[上一篇：聊天记录还在，为什么不能原样交给模型？](/zh/blog/history-is-not-context)

[了解 xopc 的连接器与协作能力](/zh/product-map)
