# 为更安全的智能体操作设定批准延迟预算

智能体批准会以两种相反的方式失败。让每个操作都询问人员，智能体就会把一天的时间耗在审核队列中等待。因为等待令人厌烦而删除决策点，智能体又会获得没人能真正监督的权限。

批准延迟预算可以避免这两种失败。它规定某类智能体操作的人工决定最多可以等待多久，衡量这段时间消耗在哪里，并在日常工作无法满足预算时迫使团队重新设计流程。预算不是要求人员更快点击的目标，而是对工作流以及交给智能体的权限设置的约束。

我见过团队把批准提示当成控制存在的证明，后来却发现一个开发者在忙着完成自己的工作时，批准了二十个几乎一模一样的请求。那个人并没有在审核，只是在充当一个缓慢的中继。解决办法很少是更好的提醒通知，通常是减少含糊的权限、减少不必要的调用，并为真正需要阻力的操作提供清晰的升级路径。

## 批准延迟预算是人工决定的截止时间

批准延迟预算，是从请求变得可审核到最终做出允许或拒绝决定之间，所能接受的最长时间。它必须按操作类别变化，因为智能体读取拉取请求、创建临时测试资源，以及修改生产环境访问设置，这些操作的紧迫性和后果并不相同。

把预算当作操作契约的一部分。如果智能体需要在两分钟内得到回答，才能让交互式任务继续进行，那么系统必须让该操作能在两分钟内轻松判断，或者在普通工作期间不要为它发起询问。如果任务安全地等到早上也没问题，就不要假装它需要打断工作的提示。

预算由三部分组成：

- **队列等待**：从创建请求到审核人员打开请求的时间。
- **决定时间**：从打开请求到允许或拒绝的时间。
- **派发时间**：从做出决定到操作开始或失败的时间。

许多团队把这些时间合并成一个数字，称为批准时间。这样会掩盖真正的修复方向。二十分钟的队列等待，需要改变路由、负责人或调度。二十分钟的决定时间，说明请求缺少上下文、包含过多权限，或者要求一个本不该交给匆忙人员判断的问题。

围绕工作的截止时间设置预算，而不是围绕抽象的安全等级。一个合理的起点可以是下面这样：

| 操作类别 | 示例 | 决定预算 | 超时行为 |
| --- | --- | ---: | --- |
| 立即处理，影响较小 | 读取构建状态或列出仓库分支 | 5 分钟 | 拒绝，并让智能体报告受阻原因 |
| 交互式、范围受限的写入 | 创建一个指定的测试问题或更新草稿评论 | 10 分钟 | 拒绝，保留请求以便稍后检查 |
| 计划中的维护 | 轮换一个不紧急的集成设置 | 4 个工作小时 | 交给指定审核人员，或重新安排 |
| 高影响操作 | 删除数据、修改访问权限、向外部发布 | 明确指定的时间窗口 | 超时即拒绝，并升级给指定负责人 |

这些只是示例，不是适用于所有团队的政策。采用值班轮换的部署团队，时间窗口可能和单人开发者不同。关键是要在队列形成前明确预期。

除非你能像服务等级协议那样配置人员，否则不要把它称为服务等级协议。预算是设计限制。它告诉你，如果能够做决定的人正在睡觉、开会或处理事故，任务就不能依赖交互式决定。智能体也应该知道这一点。它可以准备请求、选择安全的替代路径，或给出易懂的解释后停止，但不应每分钟重复发出同一个请求。

## 衡量完整的请求生命周期，而不是一次点击

如果时间戳从通知到达手机时才开始，就无法改善批准延迟。应从系统创建可审核操作时开始记录，并使用一个能跨越重试和界面刷新的请求标识符记录每次状态变化。

可以使用下面这样的小型事件记录。字段看起来刻意普通，但普通记录更容易排序、关联，也能在事故复盘中保留下来。

```json
{
  "request_id": "req_7f31",
  "run_id": "run_241",
  "action_class": "bounded_write",
  "target": "issue tracker/project-amber",
  "created_at": "2025-03-08T14:02:01Z",
  "presented_at": "2025-03-08T14:02:03Z",
  "opened_at": "2025-03-08T14:09:18Z",
  "decided_at": "2025-03-08T14:10:06Z",
  "decision": "allow",
  "executed_at": "2025-03-08T14:10:07Z",
  "outcome": "success"
}
```

按照这个结构，队列等待时间可以计算为 `opened_at - presented_at`，决定时间为 `decided_at - opened_at`，派发时间为 `executed_at - decided_at`。同时保留 `created_at`。它能发现一个更隐蔽的缺陷：请求在任何人看见之前，被中介程序暂存了。

不需要复杂的分析系统，一条查询就能表达这些指标：

```sql
SELECT
  action_class,
  percentile_cont(0.50) WITHIN GROUP (ORDER BY opened_at - presented_at) AS p50_queue_wait,
  percentile_cont(0.95) WITHIN GROUP (ORDER BY opened_at - presented_at) AS p95_queue_wait,
  percentile_cont(0.95) WITHIN GROUP (ORDER BY decided_at - opened_at) AS p95_decision_time,
  count(*) FILTER (WHERE decision = 'deny') AS denied,
  count(*) AS total
FROM approval_requests
WHERE created_at >= current_timestamp - interval '14 days'
GROUP BY action_class;
```

不同数据库的百分位数语法可能不同，但衡量方式不变。针对每个操作类别报告 p50 和 p95，同时报告数量和拒绝率。平均值会让一次五秒的体验和少数等待九十分钟的请求看起来差不多。p95 能告诉你慢尾是否会破坏真实任务。

还要记录在决定到达前，智能体是否取消、重试或放弃了任务。如果智能体已经选择了另一条路径，迟到的批准才执行，会比普通拒绝更糟。它会产生一个与开发者当前工作不再匹配的操作。

不要只因为缩短决定时间就奖励审核人员。那会把经过思考的拒绝变成表面上的绩效问题。应一起检查允许、拒绝、过期和撤回的分布。如果拒绝数突然下降，同时阅读时间变得极短，通常说明人员已经发现点击允许可以清除麻烦。

## 等待时间和审核时间指向不同缺陷

队列等待和决定时间共用一块时钟，但原因和负责人不同。把它们合并会带来错误的修复。

当请求发送给了错误的人、太多人以为别人会决定、通知在工作时间之外到达，或者审核人员没有理由中断当前任务时，队列等待会变长。增加更多通知通常会让情况更糟，因为它只是把同一份含糊的责任扩散给更多人。

当卡片迫使审核人员重新推断智能体意图时，决定时间会变长。像 `POST /v1/resources` 这样的请求无法审核。审核人员需要知道目标、操作、对象数量、使用的权限，以及可见后果。他们不应打开终端、检查源代码，再自行判断请求是创建草稿，还是向客户发送消息。

一张有用的批准卡片应使用简单语言回答五个问题：

1. 哪次智能体运行发起了请求，哪个经过签名的进程启动了这次运行？
2. 哪个外部目标会接收该操作？
3. 什么内容会改变，或者哪些数据会离开机器？
4. 哪项范围受限的权限允许执行该操作？
5. 审核人员拒绝请求或什么都不做时，会发生什么？

不要把更多细节误认为更好的上下文。完整的原始载荷可能会掩盖唯一重要的字段。先展示简洁的后果，再让审核人员在需要时查看命令、端点、去除密钥后的请求头和载荷。批准删除操作的审核人员需要看到受影响的对象名称。批准 HTTP 读取的审核人员需要看到主机、路径和查询范围。每项操作都需要与风险相匹配的证据。

一个反复出现的错误，是因为审核人员反应慢，就通过授予宽泛的会话批准来优化队列等待，而真正的问题是操作不清楚。这会把上下文问题变成权限问题。应先修复请求说明和操作边界。

相反的错误也很常见：团队因为觉得队列不安全，就要求一百个普通读取操作分别确认。队列之所以不安全，是因为它造成了习惯性批准。反复出现的低影响提示，会训练审核人员依据形式和时间点击批准，而不是依据内容。等真正有影响的请求出现时，这种习惯仍然存在。

## 审核队列说明工作流需要换一种形态

队列不只是拖慢工作，还会改变智能体和人员的行为。智能体会重试、把任务拆成更小的调用，或暂存未完成的计划。人员看到不断增长的列表后，会开始批量清理。每次处理都会让队列显得没那么危险，直到一个异常请求隐藏在熟悉的请求中。

考虑一个常见的失败场景。有人要求智能体根据 issue tracker 数据准备发布说明。它先读取项目列表，再获取每个问题，然后读取所选问题的评论，最后创建一份草稿。如果每次 HTTP 调用都需要批准，一个规模不大的任务也会变成几十条提示。

9:30，开发者认真批准了前几个读取请求。9:45，他们要去开会。10:30，智能体已经排队等待重试和相关调用。开发者回来后，看到一整面发往同一服务的请求，于是快速批准。其中一个请求创建了公开评论，而不是草稿说明，因为端点和预期结果被埋在原始请求文本中。开发者没有现实的机会在那个队列里把它区分出来。

错误的决定早在公开评论出现前就已经发生了。工作流让日常读取与一个可见写入操作竞争，还让人员在长时间中断后重新恢复上下文。这是设计缺陷，不是审核人员的失误。

应按照有意义的意图重新组织工作。如果读取会话完全没有修改能力，可以在指定服务和任务时长范围内覆盖读取。创建草稿可以发起一次明确决定，并注明草稿位置和受众。公开发布应保持独立，因为它的后果改变了审核问题。

不要用“智能体可以使用 issue tracker”这样的笼统指令解决问题。这句话隐藏了太多内容。它没有说明智能体能否读取私有问题、编辑标签、公开评论或删除数据。权限范围的名称必须对应人员之后仍能识别的操作。

同样的逻辑也适用于 SSH。检查服务日志和执行迁移可以通过同一个连接进行，但它们不属于同一批准类别。连接身份不等于操作身份。

## 批准范围应跟随后果，而不是传输方式

好的批准边界描述的是人员究竟同意了什么。HTTP 还是 SSH、命令还是 API 调用、本地还是远程进程，这些都是传输细节。它们对实现很重要，却无法告诉审核人员操作是否可撤销、是否会影响外部对象，或成本是否很高。

先从后果类别开始。读取操作可能泄露数据，所以“读取”并不自动代表无害。写入也各不相同：创建私有草稿、修改生产设置和发送消息都会改变状态，但需要的审查程度不同。在决定哪些交互可以共用一次批准前，先把这些操作分开。

然后沿着审核人员可以验证的维度限制范围：

- 目标：指定的主机、仓库、项目或环境。
- 操作：读取、创建草稿、更新指定字段，或执行指定命令族。
- 对象集合：涉及的具体记录、文件或服务。
- 时长：一次操作、一次智能体运行，或一个短期计划窗口。
- 后果：私有、可撤销、对外可见，或具有破坏性。

避免围绕实现细节建立范围。“允许 POST 请求”是传输规则，不是批准边界。POST 既可以创建草稿，也可以删除账户。“允许命令行访问”也有同样的问题。它授予的是一种操作媒介，而不是一个易于理解的结果。

人们常常因为单独请求会打断流程，就主张使用宽泛的会话批准。他们对中断的判断是对的，但当一个会话可能混合无关操作时，解决办法并不是放宽权限。只有当目标和允许的后果在整个生命周期内都清晰可见时，会话才适用。如果智能体从收集发布说明切换到修改仓库权限，就需要新的决定。

当操作后果即使在一个已经受信任的运行中仍然很高时，应使用每次操作单独确认。发布、删除、轮换访问材料、修改网络可达性，以及向团队之外发送信息，通常都属于这一类。不要把每次操作确认当成对陌生工作的惩罚，而应在每次发生都需要人工判断时使用它。

## 在放宽审核控制前，先重新设计日常工作

当批准延迟超过预算时，首先删除那些本不该变成交互式请求的操作。这不意味着允许任意行为，而是把日常工作限制在足够清晰的范围内，让人员可以批准一次运行或任务，而不是批准每个机械子调用。

按以下顺序处理一个缓慢的类别：

1. 抽样检查 p95 请求，阅读每个请求前后的完整序列。将重试和重复调用与必要调用分开统计。
2. 标记后果首次发生变化的操作。那里通常才是需要明确决定的正确位置。
3. 将确定性的读取操作合并到范围狭窄的任务权限下，指定目标，并在运行结束时过期。
4. 将对外发布、删除、权限变更和大范围数据导出拆成独立请求。
5. 让一个没有参与工作流设计的真实审核人员重新测试任务。如果他们在批准前无法说出预期结果，就继续缩小请求范围。

只有当批次本身可审核时，批处理才有帮助。“在 Amber 项目中创建这四个指定的草稿问题”是合理批次，前提是卡片列出四个问题及其目标位置。“完成剩余的所有发布工作”不是批次，而是一项开放式委派。

不要让智能体只根据便利程度自行决定批次边界。给它一个任务对象：目标、预期结果、允许的数据源和过期时间。智能体可以将调用归入这个对象，但目标或后果发生变化时，批次就应结束。这也能改善事故复盘，因为日志反映的是实际工作单元，而不是一长串无名请求。

计划中的工作需要另一种重新设计。如果人员必须在夜间批准每晚的维护操作，团队就制造了一个可预见的失败。可以在执行前安排审核窗口，指定一名拥有合适预算的值班负责人，或者推迟任务。不要把无人值守批准伪装成自动化。

重试能力需要特别关注。如果底层操作没有变化，智能体应重复使用同一个待处理请求标识符。每次重试都创建新的批准卡片，会制造队列数量，并破坏审核人员对请求顺序的理解。如果目标、载荷、权限或预期后果发生变化，就创建新请求，并说明变化之处。

## 批准界面必须让正确的决定变得容易

审核人员需要的是对意图的简洁说明，而不是一份要求他们逆向分析智能体运行的邀请。围绕他们现在必须做出的决定构建界面，再提供更深层的证据，不要迫使他们自己寻找。

先放置操作结果：“在 Amber 项目中创建私有发布说明草稿”比一个方法和路径表达得更多。将目标放在旁边。说明操作是在读取、修改、删除，还是发送数据。如果使用 SSH，应标明主机，并以能清楚显示重定向、文件写入和权限变更的形式展示命令。

同时展示权限上下文。审核人员应该知道请求来自新的智能体进程，还是来自已经批准的运行，也应知道该操作是否有特殊确认要求。进程身份很重要，因为针对一个智能体进程的批准，不应悄悄授权给另一个碰巧使用相同协议的无关进程。

Sallyport 为此使用固定的决定层级：锁定的保险库会拒绝所有操作，新智能体进程默认获得会话授权，而某个凭据可以设置为每次使用都需要批准。第一张会话卡片会优先显示进程的代码签名权限，这正是应放在人员面前的关键信息，用来判断这次运行是否就是他们原本打算启动的那一次。

不要把界面变成规则编辑器。面对截止时间的审核人员应该批准、拒绝或检查一个具体请求。如果团队反复需要某种例外，就在中断时刻之外重新设计任务范围或凭据边界。在批准流程中放入一套迷你策略语言，只会要求疲惫的人在压力下编写安全决策。

拒绝必须提供有用信息。返回智能体可以采取行动的原因类别，例如已过期、目标错误、需要更窄的范围，或需要人工审核。默认不要把审核人员的私密评论返回给不受信任的智能体。智能体需要足够的信息来停止重试或选择安全的替代任务，而不是内部讨论的完整记录。

## 在紧急请求到达前确定负责人和升级路径

没有负责人的批准预算只是愿望。每个操作类别都需要一个人员或轮值团队，在操作可能运行的时间段内负责做决定。团队可以委派审核，但不能委派“必须有人做决定”这一事实。

定义预算边界处会发生什么。在预算用去一半时，如果请求仍未被看到，系统可以只通知一次指定审核人员。达到预算上限时，低影响请求可以过期。高影响请求则应过期并通知任务负责人或值班轮换，而不是永远保持待处理。具体时间点没有状态清晰且有限重要。

诚实地使用工作时间。如果开发者在夜间本地运行智能体，而操作需要队友批准，任务可能必须等待。这是可以接受的。真正的问题是界面暗示任务会立即推进，却让智能体不断对无人值守的队列重试。

升级绝不能扩大权限。升级请求应交给更合适的审核人员，而不是进入自动允许路径。事故期间尤其要注意这一点，因为紧迫感很容易让人想一次绕过所有控制。可以预先批准紧急流程，但其中应明确规定狭窄的操作、指定的负责人和事后审核。“生产环境坏了”不是批准范围。

除了智能体延迟，也要检查人员成本。如果几乎所有提示都发给同一个开发者，即使中位延迟看起来不错，团队仍然存在路由问题。如果每个审核人员都能看到每个请求，团队建立的其实是一个贴着安全标签的共享收件箱。这两种模式都会造成疲劳和薄弱的责任归属。

## 将队列作为一连串决定进行审计

有用的审计轨迹不应只能重建最终操作。它还应显示智能体运行、呈现给审核人员的请求、做出的决定、实际的外部调用，以及任何取消或重试。没有这条顺序，团队就无法判断延迟批准是否导致了过时操作，也无法判断智能体在被拒绝后是否改变了计划。

通过标识符连接批准证据和操作证据，但不要把两者混为一谈。批准回答的是谁授权了某个明确意图。操作记录回答的是实际尝试了什么，以及目标返回了什么。当智能体请求执行到一半失败，或远程服务以意外方式解释载荷时，调查人员需要两类信息。

Sallyport 从同一份加密哈希链审计日志中生成 Sessions 日志和 Activity 日志。它的 `sp audit verify` 命令可以在离线状态下对密文检查哈希链，而无需保险库密钥。当有人需要验证日志完整性，却不想打开智能体凭据时，这很有用。

每周使用审计记录检查例外，而不是把它当成没人阅读的数据仓库。提取已过期请求、p95 最慢的示例、被拒绝的操作，以及长时间等待后仍执行的操作。对每一项提出一个具体问题：延迟来自负责人分配、意图不清、范围过大，还是任务本应采用不同的调度方式？

不要按批准速度评价人员。应按工作流是否在任务截止前，把一个易于理解的决定交给负责的人员来评价。如果重复审核只产生习惯性允许，就删除这种重复。如果罕见操作需要仔细思考，就提供仔细思考所需的时间、上下文和负责人。

先收集一周的生命周期时间戳。选择 p95 队列等待最差的一个操作类别，检查十条完整请求序列，然后修改产生最多重复提示的边界。这些工作会告诉你的东西，比另一张仪表板更多。
