公司动态

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

📅 2026/7/23 18:17:52
审批通过后 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这比一个全局approvedtrue更好但仍然不够。同一个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_atdecision_id用于处理审批系统的重复回调args_hash用于确认它决定的是同一份参数快照。如果审批系统和执行系统跨越不同信任域还需要签名或等价的来源认证机制。第七步原子消费批准批准不能被无限复用。系统需要把approved unused原子地转换成approved claimed如果两个并发执行者同时尝试使用同一张审批单只能有一个成功。简单地先查询“尚未使用”再在调用完成后更新“已使用”中间存在并发窗口。更稳妥的做法是使用条件更新、事务或等价的 compare-and-set。第八步只按原参数恢复恢复执行时再次计算当前参数哈希current_args_hash approved_args_hash相等才有资格继续。不相等时系统应该拒绝当前调用记录参数漂移返回已批准的精确参数要求按原样恢复如果确实要修改参数创建新的行动并重新评估。它不应该为了“流程顺畅”自动修改审批记录也不应该悄悄为新参数创建第二张审批单让 Agent 在审批循环里空转。第九步使用稳定幂等身份派发审批单的一次性消费解决的是“批准不能被重复使用”业务幂等解决的是“副作用不能被重复形成”。两者不能互相替代。如果进程在“消费批准”后、“收到业务结果”前崩溃恢复逻辑必须继续同一个任务和同一个业务请求身份而不是重新创造一次退款。高保证场景还需要持久任务状态、可靠派发或 Outbox 等机制缩小批准状态和外部调用之间的崩溃窗口。第十步业务系统执行最终授权即使工具、主体、参数和审批完全一致业务系统仍然可能拒绝订单已经退款剩余可退金额发生变化操作人权限已被撤销当前租户或门店不匹配业务状态已不允许继续同一业务幂等键已经成功处理。批准证明的是有人在某个时刻同意了这项行动它不能证明这项行动在任何未来时刻都必须成功最终 Authority 仍然属于业务系统。六、审批有效期为什么只是问题的一部分很多团队发现审批可能被长期复用后会增加expires_at这是有价值的但它只解决时间窗口不解决参数漂移。一份 30 分钟内有效的批准如果只绑定工具名仍然可能在第 2 分钟授权错误金额一份永不过期但严格绑定精确参数的批准也可能因为业务状态变化而不再适合执行。因此至少需要同时区分内容一致性执行的是不是批准的那项行动 时间新鲜度批准是否仍在有效窗口 业务新鲜度当前业务状态是否仍允许执行三者分别由参数绑定、运行时策略和最终业务校验承担。七、五种常见但危险的实现1. 把批准挂在整个会话上session.approved true这会让同一会话后续所有高风险操作都有机会借用一次批准。2. 只批准自然语言摘要“用户同意退款”摘要适合帮助人阅读却不能替代结构化参数。自然语言遗漏的字段往往正是决定真实后果的字段。3. 批准后让模型重新生成参数如果模型只收到“已经批准请继续”它会重新推理而不是恢复一份不可修改的执行请求。4. 发现参数不同后自动更新审批单这等于让行动提出方在批准之后修改被批准内容。正确做法是拒绝漂移参数需要修改时重新建立决策。5. 把审批成功当成业务授权成功审批人可以同意行动但业务系统仍必须校验对象、租户、余额、状态和权限。上游批准不能覆盖业务不变量。八、审计日志至少要证明什么一次涉及人工审批的 Agent 行动审计记录不应只有refund approved至少应该能够关联发起请求的人或系统Agent 实际代表的可信主体任务与请求身份工具、能力范围和风险审批前的参数快照或受保护引用参数摘要审批人、决定、时间与决定幂等键批准被何时、由哪个执行者消费最终派发参数是否匹配业务系统最终接受或拒绝的结果重试、去重、超时和恢复事件。需要注意的是日志字段齐全不等于证据天然可信。如果同一个可能被攻陷的 Runtime 既能伪造审批结果又能改写唯一的审计记录这份日志更多是运维记录而不是独立可验证证据。更高保证等级下可以让审批权威签发决定、使用只追加存储并由业务边界重新验证关键绑定。九、一个最小执行伪代码下面的伪代码强调的是责任顺序而不是某个框架的固定 APIconstargsvalidateAndNormalize(toolSchema,modelArgs)constargsJsoncanonicalJson(args)consthashsha256(argsJson)constexactApprovalawaitapprovals.findApprovedUnused({jobId,subject,tool,argsHash:hash,})if(!exactApproval){constapprovedOtherArgsawaitapprovals.findApprovedUnused({jobId,subject,tool,})if(approvedOtherArgs){awaitaudit(tool_args_drift,{jobId,tool,expectedArgsHash:approvedOtherArgs.argsHash,actualArgsHash:hash,})returnrejectWithApprovedSnapshot(approvedOtherArgs)}returncreateApprovalIntent({jobId,requestId,subject,tool,args,argsHash:hash,})}if(!awaitapprovals.claimOnce(exactApproval.id)){returnreject(approval_already_consumed)}returnbusinessSystem.execute({subject,tool,args,stableRequestId,})在真实生产系统中还要继续处理审批决定来源认证决定回调幂等任务持久化派发崩溃窗口业务幂等超时状态查询敏感字段脱敏证据保留策略。伪代码没有消除这些问题但它至少避免了最危险的一步把一次批准误当成一段可自由改写的执行权限。十、ACC 与运行时应该怎样分工能力契约可以声明哪项操作需要可信主体操作风险是什么是否存在审批意图哪些参数条件会触发人工介入。这些是可以随能力一起发布的可移植语义。但能力声明不应该假装知道企业里谁有资格审批审批系统使用什么组织模型批准有效多久决定如何签名任务如何暂停和恢复业务对象此刻是否仍可操作。这些属于部署时运行策略、审批权威、执行协调和业务系统。因此一条清晰的分工是声明层 表达“这里需要外部决策” 运行时 冻结精确行动、暂停任务、绑定决定、恢复原请求 审批系统 判断谁有资格批准并签发决定 业务系统 执行最终对象级授权、状态校验和事务提交声明层保持可移植运行时负责兑现业务系统保留最终 Authority。十一、一个公开实现样本BailingHub 采用了一种具体做法工具参数先按照实际 JSON 传输语义规范化对象键递归排序数组顺序保留再计算args_hash审批记录绑定job_id tool args_hash决定回调重新核对approval_id / job_id / request_id / args_hash已批准记录只能被原子消费一次同一工具出现已批准但参数不一致的调用时记录tool_args_drift并拒绝执行运行时把已批准的精确参数返回给任务恢复链路副作用调用继续使用幂等账本业务系统仍然执行最终权限和业务状态判断。这只是一个可运行的工程样本不是唯一实现也不是 ACC 核心规范强制的内部结构。如果审批需要跨组织、跨 Runtime 或跨多个 Agent 传递还需要进一步处理标准化规范化、签名委托链、独立时间基准和可验证证据。这些问题不应该被一个args_hash掩盖。十二、上线前检查表批准对象批准是否绑定可信主体而不是模型生成的 user_id是否绑定精确工具或能力标识是否绑定任务和请求身份审批人是否能看到真正影响后果的结构化参数参数一致性参数是否先经过 Schema 校验和 JSON 规范化规范化规则是否对对象、数组、null和类型有明确语义执行前是否重新计算并比较参数摘要参数不一致时是否拒绝而不是自动更新批准审批决定谁有资格审批由可信企业系统决定吗决定回调是否有来源认证重复回调是否使用稳定decision_id去重批准是否只能被一个执行者原子领取一次执行与恢复批准后是否恢复同一个任务而不是创建新任务副作用调用是否使用稳定业务幂等键崩溃恢复是否会重复执行或丢失已批准行动超时后是否查询原任务状态而不是盲目重试最终授权与证据业务系统是否仍校验租户、对象、权限和实时状态审计记录能否对齐批准参数与真实派发参数敏感参数快照是否被适当保护和脱敏系统是否诚实区分运维日志与独立可验证证据结语Human-in-the-loop 的价值不是在人和 Agent 之间增加一次点击而是把机器提出的高后果行动重新交给可信的外部决策。要让这项决定真正有效系统必须保证人批准的行动 运行时恢复的行动 业务系统最终收到的行动只要工具、主体、参数、任务或业务状态发生变化原批准就不能被静默外推。这也是 Agent 进入真实业务系统后一个看似细小却决定安全上限的工程问题审批不是给 Agent 一次通行权而是只批准那一次被看见、被理解、被绑定的具体行动。你们现在的 Agent 工具审批是绑定到会话、任务、工具名还是已经绑定到精确参数快照如果任务在审批后重新推理系统如何证明最终执行的仍然是审批人当时看到的那一次调用