审批通过后 Agent 改了参数怎么办?Human-in-the-loop 为什么必须绑定参数快照?

简介: 人工审批须绑定精确参数快照,而非模糊授权。它批准的是“可信主体在特定任务中发起的那一次具体行动”——包括订单号、金额、原因等不可篡改的结构化参数,确保审批人所见即所执,杜绝参数漂移与越权执行。

人工审批真正批准的,不应该是一个模糊的“可以执行”,而应该是某个可信主体在某个任务中提出的那一次精确行动。

假设一个 Agent 帮用户处理订单退款。

第一次调用工具时,它提交了:

{
   
  "order_id": "ORD-20260723-018",
  "amount": 300,
  "reason": "duplicate_payment"
}

系统判断这是一项高后果操作,于是暂停执行,并向审批人展示:

订单:ORD-20260723-018
退款金额:300 元
退款原因:重复支付

审批人确认信息无误,点击“批准”。

随后任务恢复,Agent 再次生成工具调用。但这一次,它提交的是:

{
   
  "order_id": "ORD-20260723-018",
  "amount": 3000,
  "reason": "duplicate_payment"
}

如果运行时只记录:

这个任务已经审批通过

或者只记录:

refund_create 这个工具已经审批通过

那么第二次调用很可能会借用第一次审批,直接执行 3000 元退款。

这不一定来自恶意攻击。参数变化可能源于:

  • 模型重新推理后生成了不同数字;
  • 上下文压缩丢失了原始参数;
  • 工作流恢复时重新执行了参数生成节点;
  • 工具 Schema 的默认值或字段映射发生变化;
  • Agent 根据后来读取到的信息重新规划了行动;
  • 网络重试被错误实现成一次全新的工具调用;
  • 人工审批等待期间,任务上下文或业务数据已经变化。

问题的本质并不是“模型为什么不听话”,而是:

系统把对一次具体行动的批准,错误地升级成了对未来调用的通用授权。

这类问题可以称为审批参数漂移。它揭示了 Human-in-the-loop 最容易被忽略的一层:有人工参与,不等于人工批准的内容与最终执行的内容一致。

一、审批不是一个布尔值

很多系统最初实现人工审批时,会在任务表上增加一个字段:

approved = true

这个字段能表达“有人点过同意”,却回答不了:

  • 谁提出了这项行动?
  • Agent 当时代表谁行动?
  • 审批人看到的是哪个工具?
  • 审批页面展示了哪些参数?
  • 恢复执行时还是不是同一项能力?
  • 参数是否发生了变化?
  • 这份批准可以使用几次?
  • 它是否已经过期?
  • 审批发生后,业务状态是否仍然允许执行?

因此,生产系统中的审批对象不应该只是一个任务,也不应该只是一个工具名。

更准确的批准对象应当接近:

可信主体
+ 能力或工具
+ 规范化参数
+ 任务与请求身份
+ 审批决定及其签发者

可以把它表示成一个绑定元组:

(subject, capability, tool, args, job_id, request_id)

审批人批准的是这个元组所描述的行动,而不是向 Agent 发放一张可以自由填写内容的空白支票。

二、为什么只绑定工具名仍然不够

一种常见改进是把批准记录从任务级缩小到工具级:

job_id = job-123
tool = refund_create
status = approved

这比一个全局 approved=true 更好,但仍然不够。

同一个 refund_create 可以表达完全不同的业务后果:

{
   "order_id":"A","amount":100}

和:

{
   "order_id":"B","amount":100000}

工具名相同,不代表行动相同。

同样,下面两个调用也不应该共享批准:

{
   "employee_id":"E-21","status":"disabled"}
{
   "employee_id":"E-21","status":"deleted"}

一个是暂时停用,一个可能是不可逆删除。只看工具名,系统无法知道审批人究竟同意了哪种后果。

因此,审批至少必须绑定到精确参数。只要影响行动语义的字段发生变化,就应该视为另一项行动:

换对象 = 新行动
换金额 = 新行动
换状态 = 新行动
换收件人 = 新行动
换批量范围 = 新行动

新的行动应该重新评估风险、重新执行授权,并在需要时重新审批。

三、参数快照解决“审批人到底看见了什么”

当运行时决定暂停一项操作时,首先应该冻结一份参数快照。

例如:

{
   
  "tool": "refund_create",
  "scope": "refund.create",
  "subject": "tenant-18:user-204",
  "args": {
   
    "order_id": "ORD-20260723-018",
    "amount": 300,
    "reason": "duplicate_payment"
  },
  "job_id": "job-7f93",
  "request_id": "req-b285"
}

这份快照至少承担三种作用。

1. 生成审批展示

审批页面应该从被冻结的参数生成摘要,而不是在审批时再次让模型解释“它准备做什么”。

模型生成的自然语言可以帮助阅读,但不能成为批准范围的唯一依据。审批人最终看到的金额、订单、目标账号或批量范围,必须能回到原始结构化参数。

2. 恢复任务

审批结束后,系统应该恢复同一项任务,而不是重新创建一项“看起来差不多”的任务。

如果 Runtime 需要再次调用模型,也必须把已批准的精确调用作为不可修改的执行输入,而不是只告诉模型:

退款已经批准,请继续。

3. 建立审计证据

事后需要能够回答:

审批人看见的参数
是否等于
最终派发的参数

如果系统没有保留快照,只保留一句“某人批准了退款”,这条记录无法证明最终执行的就是当时展示的内容。

四、为什么还需要参数哈希

只保存参数快照还不够。恢复执行时,运行时还需要一种稳定方式判断:

本次参数和被批准参数是否完全一致?

直接比较原始 JSON 字符串会遇到一个问题。下面两段 JSON 的键顺序不同,但语义相同:

{
   "order_id":"A","amount":300}
{
   "amount":300,"order_id":"A"}

如果直接比较字符串,系统会错误地把它们判断成两个请求。

因此,常见做法是先规范化参数,再计算摘要:

args_hash = SHA-256(canonical_json(args))

一个基本的规范化过程通常需要明确:

  • 对象键是否递归排序;
  • 数组顺序是否保留;
  • 缺失字段与 null 是否区分;
  • 数字、布尔值和字符串是否禁止隐式转换;
  • 非法 JSON 值如何处理;
  • 不同语言实现如何得到相同字节序列。

例如:

{
   "amount":300}

不能和:

{
   "amount":"300"}

得到相同语义,因为一个是数字,一个是字符串。

而:

{
   "ids":[1,2]}

和:

{
   "ids":[2,1]}

是否相同,取决于数组顺序在该工具中的业务含义。通用实现通常应保留数组顺序,避免擅自改变调用语义。

哈希不是什么

参数哈希只是对快照的稳定引用,不是万能安全凭证。

它不能单独证明:

  • 快照来自可信主体;
  • 审批决定由有资格的人签发;
  • 审批记录没有被篡改;
  • 业务状态仍然有效;
  • 原始参数中不存在敏感数据。

因此:

  • 哈希需要和任务、主体、工具及审批记录一起保存;
  • 跨信任域传递时,需要认证通道或签名信封;
  • 原始快照仍需按敏感数据策略保存和脱敏;
  • 不应把 args_hash 当成权限令牌。

五、一条可审查的审批执行链

一条最小但完整的链路可以分成十步。

第一步:模型提出工具调用

tool = refund_create
args = { order_id, amount, reason }

此时的参数仍然是不可信输入。

第二步:运行时校验并规范化参数

系统先依据工具 Schema 校验类型、必填字段和结构,再把参数转换成稳定的 JSON 语义。

校验发生在计算快照之前,避免审批一份无法真实派发的参数。

第三步:判断是否需要外部决策

判断依据可以来自:

  • 操作风险;
  • 声明中的审批意图;
  • 部署环境策略;
  • 企业审批系统;
  • 当前可信主体与场景。

这里的目标是判断“是否需要暂停”,不是让模型自己决定“我觉得这次不危险”。

第四步:冻结审批意图

系统保存:

approval_id
job_id
request_id
subject
tool
scope
args snapshot
args_hash
risk / reason
created_at

审批页面和外部审批通知都应引用同一份冻结数据。

第五步:审批权威作出决定

谁有资格批准,取决于企业自己的身份、组织和策略体系。

运行时可以发起审批并等待结果,但不应该凭空假设:

任何登录控制台的人都可以批准任何操作。

第六步:决定信封返回

一份可机器验证的决定至少应该重新带回:

approval_id
job_id
request_id
args_hash
decision
decision_id
approver
decided_at

decision_id 用于处理审批系统的重复回调;args_hash 用于确认它决定的是同一份参数快照。

如果审批系统和执行系统跨越不同信任域,还需要签名或等价的来源认证机制。

第七步:原子消费批准

批准不能被无限复用。

系统需要把:

approved + unused

原子地转换成:

approved + claimed

如果两个并发执行者同时尝试使用同一张审批单,只能有一个成功。

简单地先查询“尚未使用”,再在调用完成后更新“已使用”,中间存在并发窗口。更稳妥的做法是使用条件更新、事务或等价的 compare-and-set。

第八步:只按原参数恢复

恢复执行时再次计算当前参数哈希:

current_args_hash == approved_args_hash

相等,才有资格继续。

不相等时,系统应该:

  1. 拒绝当前调用;
  2. 记录参数漂移;
  3. 返回已批准的精确参数,要求按原样恢复;
  4. 如果确实要修改参数,创建新的行动并重新评估。

它不应该为了“流程顺畅”自动修改审批记录,也不应该悄悄为新参数创建第二张审批单,让 Agent 在审批循环里空转。

第九步:使用稳定幂等身份派发

审批单的一次性消费,解决的是“批准不能被重复使用”;业务幂等解决的是“副作用不能被重复形成”。

两者不能互相替代。

如果进程在“消费批准”后、“收到业务结果”前崩溃,恢复逻辑必须继续同一个任务和同一个业务请求身份,而不是重新创造一次退款。

高保证场景还需要持久任务状态、可靠派发或 Outbox 等机制,缩小批准状态和外部调用之间的崩溃窗口。

第十步:业务系统执行最终授权

即使工具、主体、参数和审批完全一致,业务系统仍然可能拒绝:

  • 订单已经退款;
  • 剩余可退金额发生变化;
  • 操作人权限已被撤销;
  • 当前租户或门店不匹配;
  • 业务状态已不允许继续;
  • 同一业务幂等键已经成功处理。

批准证明的是:

有人在某个时刻同意了这项行动

它不能证明:

这项行动在任何未来时刻都必须成功

最终 Authority 仍然属于业务系统。

六、审批有效期为什么只是问题的一部分

很多团队发现审批可能被长期复用后,会增加:

expires_at

这是有价值的,但它只解决时间窗口,不解决参数漂移。

一份 30 分钟内有效的批准,如果只绑定工具名,仍然可能在第 2 分钟授权错误金额;一份永不过期但严格绑定精确参数的批准,也可能因为业务状态变化而不再适合执行。

因此,至少需要同时区分:

内容一致性:执行的是不是批准的那项行动?
时间新鲜度:批准是否仍在有效窗口?
业务新鲜度:当前业务状态是否仍允许执行?

三者分别由参数绑定、运行时策略和最终业务校验承担。

七、五种常见但危险的实现

1. 把批准挂在整个会话上

session.approved = true

这会让同一会话后续所有高风险操作都有机会借用一次批准。

2. 只批准自然语言摘要

“用户同意退款”

摘要适合帮助人阅读,却不能替代结构化参数。自然语言遗漏的字段,往往正是决定真实后果的字段。

3. 批准后让模型重新生成参数

如果模型只收到“已经批准,请继续”,它会重新推理,而不是恢复一份不可修改的执行请求。

4. 发现参数不同后自动更新审批单

这等于让行动提出方在批准之后修改被批准内容。正确做法是拒绝漂移参数;需要修改时重新建立决策。

5. 把审批成功当成业务授权成功

审批人可以同意行动,但业务系统仍必须校验对象、租户、余额、状态和权限。上游批准不能覆盖业务不变量。

八、审计日志至少要证明什么

一次涉及人工审批的 Agent 行动,审计记录不应只有:

refund approved

至少应该能够关联:

  • 发起请求的人或系统;
  • Agent 实际代表的可信主体;
  • 任务与请求身份;
  • 工具、能力范围和风险;
  • 审批前的参数快照或受保护引用;
  • 参数摘要;
  • 审批人、决定、时间与决定幂等键;
  • 批准被何时、由哪个执行者消费;
  • 最终派发参数是否匹配;
  • 业务系统最终接受或拒绝的结果;
  • 重试、去重、超时和恢复事件。

需要注意的是,日志字段齐全不等于证据天然可信。

如果同一个可能被攻陷的 Runtime 既能伪造审批结果,又能改写唯一的审计记录,这份日志更多是运维记录,而不是独立可验证证据。更高保证等级下,可以让审批权威签发决定、使用只追加存储,并由业务边界重新验证关键绑定。

九、一个最小执行伪代码

下面的伪代码强调的是责任顺序,而不是某个框架的固定 API:

const args = validateAndNormalize(toolSchema, modelArgs)
const argsJson = canonicalJson(args)
const hash = sha256(argsJson)

const exactApproval = await approvals.findApprovedUnused({
   
  jobId,
  subject,
  tool,
  argsHash: hash,
})

if (!exactApproval) {
   
  const approvedOtherArgs = await approvals.findApprovedUnused({
   
    jobId,
    subject,
    tool,
  })

  if (approvedOtherArgs) {
   
    await audit("tool_args_drift", {
   
      jobId,
      tool,
      expectedArgsHash: approvedOtherArgs.argsHash,
      actualArgsHash: hash,
    })
    return rejectWithApprovedSnapshot(approvedOtherArgs)
  }

  return createApprovalIntent({
   
    jobId,
    requestId,
    subject,
    tool,
    args,
    argsHash: hash,
  })
}

if (!await approvals.claimOnce(exactApproval.id)) {
   
  return reject("approval_already_consumed")
}

return businessSystem.execute({
   
  subject,
  tool,
  args,
  stableRequestId,
})

在真实生产系统中,还要继续处理:

  • 审批决定来源认证;
  • 决定回调幂等;
  • 任务持久化;
  • 派发崩溃窗口;
  • 业务幂等;
  • 超时状态查询;
  • 敏感字段脱敏;
  • 证据保留策略。

伪代码没有消除这些问题,但它至少避免了最危险的一步:把一次批准误当成一段可自由改写的执行权限。

十、ACC 与运行时应该怎样分工

能力契约可以声明:

  • 哪项操作需要可信主体;
  • 操作风险是什么;
  • 是否存在审批意图;
  • 哪些参数条件会触发人工介入。

这些是可以随能力一起发布的可移植语义。

但能力声明不应该假装知道:

  • 企业里谁有资格审批;
  • 审批系统使用什么组织模型;
  • 批准有效多久;
  • 决定如何签名;
  • 任务如何暂停和恢复;
  • 业务对象此刻是否仍可操作。

这些属于部署时运行策略、审批权威、执行协调和业务系统。

因此,一条清晰的分工是:

声明层:
  表达“这里需要外部决策”

运行时:
  冻结精确行动、暂停任务、绑定决定、恢复原请求

审批系统:
  判断谁有资格批准,并签发决定

业务系统:
  执行最终对象级授权、状态校验和事务提交

声明层保持可移植,运行时负责兑现,业务系统保留最终 Authority。

十一、一个公开实现样本

BailingHub 采用了一种具体做法:

  1. 工具参数先按照实际 JSON 传输语义规范化;
  2. 对象键递归排序,数组顺序保留,再计算 args_hash
  3. 审批记录绑定 job_id + tool + args_hash
  4. 决定回调重新核对 approval_id / job_id / request_id / args_hash
  5. 已批准记录只能被原子消费一次;
  6. 同一工具出现已批准但参数不一致的调用时,记录 tool_args_drift 并拒绝执行;
  7. 运行时把已批准的精确参数返回给任务恢复链路;
  8. 副作用调用继续使用幂等账本;
  9. 业务系统仍然执行最终权限和业务状态判断。

这只是一个可运行的工程样本,不是唯一实现,也不是 ACC 核心规范强制的内部结构。

如果审批需要跨组织、跨 Runtime 或跨多个 Agent 传递,还需要进一步处理标准化规范化、签名委托链、独立时间基准和可验证证据。这些问题不应该被一个 args_hash 掩盖。

十二、上线前检查表

批准对象

  • [ ] 批准是否绑定可信主体,而不是模型生成的 user_id?
  • [ ] 是否绑定精确工具或能力标识?
  • [ ] 是否绑定任务和请求身份?
  • [ ] 审批人是否能看到真正影响后果的结构化参数?

参数一致性

  • [ ] 参数是否先经过 Schema 校验和 JSON 规范化?
  • [ ] 规范化规则是否对对象、数组、null 和类型有明确语义?
  • [ ] 执行前是否重新计算并比较参数摘要?
  • [ ] 参数不一致时是否拒绝,而不是自动更新批准?

审批决定

  • [ ] 谁有资格审批由可信企业系统决定吗?
  • [ ] 决定回调是否有来源认证?
  • [ ] 重复回调是否使用稳定 decision_id 去重?
  • [ ] 批准是否只能被一个执行者原子领取一次?

执行与恢复

  • [ ] 批准后是否恢复同一个任务,而不是创建新任务?
  • [ ] 副作用调用是否使用稳定业务幂等键?
  • [ ] 崩溃恢复是否会重复执行或丢失已批准行动?
  • [ ] 超时后是否查询原任务状态,而不是盲目重试?

最终授权与证据

  • [ ] 业务系统是否仍校验租户、对象、权限和实时状态?
  • [ ] 审计记录能否对齐批准参数与真实派发参数?
  • [ ] 敏感参数快照是否被适当保护和脱敏?
  • [ ] 系统是否诚实区分运维日志与独立可验证证据?

结语

Human-in-the-loop 的价值,不是在人和 Agent 之间增加一次点击,而是把机器提出的高后果行动重新交给可信的外部决策。

要让这项决定真正有效,系统必须保证:

人批准的行动
=
运行时恢复的行动
=
业务系统最终收到的行动

只要工具、主体、参数、任务或业务状态发生变化,原批准就不能被静默外推。

这也是 Agent 进入真实业务系统后,一个看似细小却决定安全上限的工程问题:

审批不是给 Agent 一次通行权,而是只批准那一次被看见、被理解、被绑定的具体行动。

你们现在的 Agent 工具审批,是绑定到会话、任务、工具名,还是已经绑定到精确参数快照?如果任务在审批后重新推理,系统如何证明最终执行的仍然是审批人当时看到的那一次调用?

相关文章
|
5天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1904 5
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
13天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2509 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
13天前
|
人工智能 自然语言处理 数据挖掘
Qwen3.8-Max-Preview深度全解析:2.4万亿参数旗舰MoE模型+Token Plan限时优惠完整落地指南
2026年7月,全新旗舰级混合专家大模型Qwen3.8-Max-Preview正式开放抢先体验,作为通义千问Qwen3系列规格最高、综合推理能力顶尖的新一代模型,该模型总参数量达到2.4万亿(2.4T),是当前线上可调用的原生多模态旗舰模型,综合推理水准对标海外顶级Fable 5模型,在复杂工程开发、长文档深度分析、多步骤智能体自治、跨境多语言创作、海量数据挖掘五大高难度业务场景实现跨越式性能提升。
1367 2
|
11天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
1216 2
|
15天前
|
人工智能
Qwen3.8抢先体验!正式版即将发布并开源!
千问Qwen3.8即将开源,参数达2.4T,进化速度以“天”计,实力媲美Fable 5。预览版Qwen3.8-Max已上线阿里Token Plan等平台,限时优惠:日间Credits低至1折,夜间更优,个人/团队版月付仅35元起!
1396 53
|
12天前
|
自然语言处理 测试技术 API
通义千问Qwen3.8-Max-Preview全功能解析:2.4万亿参数旗舰模型深度使用指南
在大模型技术持续迭代的当下,通义千问推出的Qwen3.8-Max-Preview作为新一代旗舰预览版模型,凭借2.4万亿参数的超大规模、多模态融合能力与全场景适配特性,成为开发者与企业用户探索AI应用的核心工具。该模型采用稀疏混合专家(MoE)架构,是通义千问首个突破万亿参数的多模态模型,可同时处理文本、图像、视频与文档等多种数据形态,在全栈代码开发、复杂逻辑推理、长文档分析与多智能体协作等场景实现跨越式升级。本文将全面拆解Qwen3.8-Max-Preview的核心功能,详解API调用流程与配置方法,覆盖多场景实战技巧,帮助用户快速掌握这款旗舰模型的使用方法,充分释放其性能潜力。
645 2
|
12天前
|
SQL 关系型数据库 MySQL
【2026最新】DBeaver下载、安装、数据库管理一篇搞定(附官网社区版安装包)
DBeaver是一款免费开源的跨平台通用数据库管理工具,支持MySQL、PostgreSQL、SQLite、Oracle等几乎所有主流数据库,无需为每种数据库安装独立客户端,极大提升开发与数据分析效率。