揭示 Agent 重启后的关键设计缺陷:人类批准不是永久权限,而是对特定事实时刻的决策。延迟执行期间订单可能已退款、策略可能已变更、角色权限可能已失效。
人类审批,是针对特定事实条件下某个操作所作出的决定,而不是一个永久有效的权限位。
假设一个 AI Agent 正在准备退款请求:
Order: SO-1001
Amount: CNY 199.00
Reason: Duplicate payment
运行时将该操作判定为高风险,于是暂停任务,并请求人工审批这笔具体请求。
10:00,审批人检查参数后点击 Approve。
任务并没有立即执行。它仍处于暂停状态,在队列中等待,经历了一次协调器重启,最终于 15:00 进入分发阶段。
在这五个小时里,以下任何情况都可能已经发生变化:
系统是否应该仅仅因为数据库中仍有一行 approved = true,就继续执行?
这次审批并不是一项永久授权。它是针对某个具体操作作出的决定:由特定主体代表、使用特定 capability、携带特定参数,并且基于特定政策和一组具体业务事实。
当 Agent 不再只是回答问题,而是开始产生真实的业务后果时,这一区别就变得至关重要。
在传统管理软件中,审批与执行往往紧密相连。用户提交表单,经理批准,系统随后很快就会执行相应操作。
这种交互方式很容易让人形成一种过度简化的心智模型:
approval = true
一旦保存了这个值,下游代码就会将该操作视为获得了永久授权。
Agent 任务则有所不同。一个任务可能跨越多个异步边界:
understand intent
-> select a capability
-> construct arguments
-> request approval
-> wait for a human
-> resume the task
-> enter a dispatch queue
-> call the business system
-> record the outcome
整个生命周期可能持续几分钟、几小时,甚至几天。进程可能重启,政策可能重新部署,业务对象也可能通过其他渠道发生变化。
在这种环境中,“审批曾经发生过”只是一项历史事实。它无法证明这个操作现在仍然有效。
生产级设计至少必须区分五个概念:
将这五个概念全部压缩成一个 boolean,会掩盖最重要的故障模式。
像下面这样的审批 prompt 并不足够:
Approve the refund?
它没有告诉审批人:
一条有效的审批记录应该绑定一个具体的执行信封。根据保障级别,它可以包括:
trusted_subject
capability
canonical_arguments
task_identity
policy_version
approval_time
expiry_time
approver
保障要求更高的部署还可以绑定:
tenant
business_object_version
tool_or_server_artifact
request_purpose
delegation_context
并非所有实现都需要采用相同的表示形式。有些系统可能使用签名对象,有些使用持久化数据库记录,还有些使用外部审批系统。相比具体格式,不变量更重要:
系统必须能够证明,即将执行的操作仍然是当初经过审核的那个操作。
一种常见的保护措施,是对参数进行规范化处理并保存其哈希值:
args_hash = SHA256(canonical_json(arguments))
分发之前,运行时会再次计算哈希。如果值不一致,就不能复用之前的审批。
这可以防止一类危险的参数漂移:
At approval:
order_id = SO-1001
amount = 199.00
At execution:
order_id = SO-1001
amount = 19900.00
但是,即使参数哈希完全一致,也不能证明当前执行仍然安全。
参数可能没有变化,但与此同时:
因此,参数相等是复用审批的必要条件,而不是充分条件。
这正是其中的核心区别:
Structural integrity:
Is this the same request?
Temporal validity:
Is the decision still applicable now?
一个健壮的系统必须同时满足两者。
审批的新鲜度不是一项单独的检查,而是一组由不同组件负责的检查。
capability、规范化参数、可信主体、tenant 或任务身份,已经与获得审批的执行信封不再匹配。
预期行为:不要分发。将同一个持久化任务重新置为需要审批的状态,或者在分发之前拒绝该任务。
执行操作的主体或审批人,已经不再具备当前政策所要求的角色、成员资格、委托关系或权限。
预期行为:从权威系统重新解析可信身份和授权。绝不能信任由模型生成的身份字段。
任务暂停期间,风险政策、审批阈值、路由 allowlist、capability 声明或工具实现发生了变化。
预期行为:将审批时的政策与 capability 上下文,同当前上下文进行比较。如果变化具有实质性影响,则必须重新作出决定。
订单、发票、账户、库存项、部署目标或其他业务对象,在审批后发生了变化。
预期行为:必须由业务系统,或者由基于同一权威数据的最新 preflight API,重新评估当前状态和最终权限。
其职责边界可以概括如下:
任何单一的审批服务,都无法安全地凭空构造所有这些事实。
最重要的验证时刻,不是人类点击 Approve 的时候,而是在业务操作即将分发之前。
一条保守的恢复路径如下:
1. Load the same durable task identity.
2. Reconstruct the canonical execution envelope.
3. Verify that the capability and arguments still match the approval evidence.
4. Check whether the approval is expired or revoked.
5. Compare the approved policy context with the current policy context.
6. Re-resolve the trusted subject where required.
7. Dispatch with a stable business idempotency identity.
8. Let the business system recheck object state and final authority.
9. Record the outcome against the same task.
如果任何分发前的绑定检查失败,任务都不能悄无声息地继续执行。
正确的结果通常是以下状态之一:
awaiting_reapproval
rejected_pre_dispatch
具体的状态名称取决于实现。真正需要保障的安全属性并不是:
“我们有一条审批记录。”
而是:
“任何业务分发,都不会使用已经失效的审批证据。”
审批过期不能像临时网络错误一样处理。
考虑以下过程:
task T is approved
-> approval expires
-> dispatcher attempts to resume T
-> validation detects expiry
不安全的实现可能会创建一个新任务、自动重试,或者因为参数没有变化而复用旧审批。
这三种行为都会削弱控制边界。
same durable task
-> expired approval detected
-> no dispatch
-> explicit reapproval or terminal rejection
仅仅为了绕过过期决定而创建新任务,会破坏任务的连续性。将过期视为传输重试,是把政策失败与交付失败混为一谈。复用旧决定,则会把有时间限制的审批变成永久权限。
任务身份应该延续,但执行授权未必能够延续。
审批决定通常是在某个政策版本下产生的:
policy_version = refund-policy-2026-08-01
当任务恢复时,运行时应该能够比较:
approved_policy_version
current_policy_version
但是,版本不一致并不总是需要采取相同的处理方式。
可移植契约可以要求实现保留相关政策上下文,但不应试图统一规定每一家企业如何定义“实质性政策变更”。
这项决定属于部署政策,以及拥有该规则的权威主体。
即便审批证据完全有效,也无法证明某个订单现在仍然可以退款。
审批层可能知道:
the approver agreed to refund SO-1001 for CNY 199.00
但只有业务领域才能可靠地知道:
whether SO-1001 still exists
whether it belongs to the current tenant
whether CNY 199.00 remains refundable
whether another refund already succeeded
whether the subject may perform this action now
这就是为什么 Agent 治理架构必须保留最终的业务边界。
审批可以证明所要求的人工决定确实发生过,但它不能替代当前的对象级授权、事务约束、tenant 隔离或领域不变量。
Approval controls whether execution may proceed.
The business system controls whether the consequence may exist.
只有架构图还不够。审批有效性应该被表达成不同实现都可以运行的故障场景。
下面是一条很有价值的测试用例:
Scenario: approval expires while a task is paused
Given:
- one durable task identity
- approval evidence bound to a trusted subject,
canonical arguments, capability, and policy version
- implementation-defined validity metadata
When:
- the runtime attempts to resume or dispatch the task
- after the approval is no longer valid
Then:
- expiry is detected before dispatch
- dispatch_count remains 0
- business_effect_count remains 0
- the old approval is not reused
- the same task moves to reapproval or pre-dispatch rejection
测试还应该禁止以下捷径:
不同实现可以选择不同的时钟、lease、TTL 格式、状态名称和工作流引擎,但它们仍然应该能够证明同一个外部可观察属性。
这正是“我们支持人工审批”和“我们能够证明审批在故障与延迟下仍然有意义”之间的区别。
人们很容易想通过将所有运行时关注点都加入 capability 声明来解决这个问题:
approval:
required: true
ttl: 30m
workflow: finance-review-v7
approver_query: ...
policy_engine: ...
这样很快就会把一份可移植声明变成某个组织专属的工作流语言。
更清晰的边界划分是:
它可以表达某项操作带有审批意图,以及其他稳定的治理语义。
它们负责实现证据绑定、有效性元数据、过期、撤销、暂停与恢复行为,以及政策版本处理。
它会在产生业务后果之前,重新检查当前主体权限、tenant 边界、对象状态、领域不变量和最终许可。
标准应该描述最小的可移植语义。实现应该让操作层面的保障真正成立。业务系统则应该保留对自身状态的权威。
这种职责划分是有意为之。它既能保持契约的互操作性,又不会假装一套 schema 可以取代企业身份系统、审批工作流或业务授权。
审查一条 Agent 审批路径时,请检查:
如果系统无法回答这些问题,那么 approved = true 就不是治理保障,而只是一个历史标记。
Human-in-the-loop 经常被呈现为一个只有两个按钮的界面:Approve 和 Reject。
真正的工程问题,从点击按钮之后才开始。
审批决定必须始终绑定赋予它意义的主体、capability、参数、任务、政策和有效性条件。当暂停的 Agent 任务恢复时,系统必须判断这些绑定是否仍然成立。如果不成立,执行就必须在分发之前停止。如果仍然成立,业务系统依然必须执行最新、最终的授权检查。
其治理原则非常简单:
审批不是 boolean。它是针对某个具体操作、受时间限制的证据,而且必须在执行时再次证明其有效性。
我负责维护 ACC 和 BailingHub。这是两个开源项目,旨在探索可移植的 capability 治理语义,以及面向操作现有业务系统的 Agent、可自行托管的执行控制机制。它们是具体的设计实验,并非唯一有效的架构。业务系统仍然是最终权威。
如需采取进一步措施,可以考虑屏蔽此人和/或举报滥用行为。