退款任务停在“等待批准”,主管第二天点了同意。昨天执行任务的进程早已退出,系统应该怎样继续?答案不能是把线程保留一天,也不能只往聊天历史里加一句“已经同意”。
人工介入是一个新的、带身份的外部输入。它需要定位具体任务和提案,检查是否仍然有效,再触发合法状态转换。
四类输入不能都叫 approved
澄清补充缺失信息,例如选择订单;审批授权某个具体动作;编辑修改候选内容;接管把后续处理权交给人。它们的恢复规则不同。
用户补充金额后,需要重新校验余额;主管批准后,需要检查批准绑定的金额是否未变;人工编辑邮件后,需要重新验证正文;接管后,自动 Worker 不应继续与人竞争操作。
如果把所有输入都压成 true,程序无法知道发生了哪一种变化,也无法决定哪些旧检查已经失效。
等待应保存什么
保存 proposal ID、任务 ID、规范化动作指纹、任务版本、提出时间、到期时间、预期输入 Schema 和当前状态。再向界面返回可展示内容,释放执行资源。
界面必须显示实际动作,包括目标、金额和币种。批准“退款 299 元”不能被模糊成“继续处理”,否则人无法判断授权范围。
状态保存与通知发布之间可以使用 Outbox。用户暂时收不到通知不应导致提案消失,用户也应能从待办列表查询它。等待阶段不占用一个正在执行模型调用的 Worker,但系统仍需存储、定时器和事件处理资源。
先检查决定是不是合法数据
JSON 布尔 false 与字符串 "false" 不同。Python 的 bool("false") 为 True,所以不能直接用通用真值转换接收审批。
# 这个校验只处理类型;身份、提案绑定和过期仍由后续业务入口验证。
def parse_decision(value):
if type(value) is not bool:
raise ValueError("decision_must_be_boolean")
return value
批准应来自可信界面或服务端认证流程。模型参数中的 approved=true 不能冒充审批记录。配套程序以命令行模拟可信输入,不连接真实身份服务,会明确标注这一简化。
参数变化为什么使批准失效
提案 P17 绑定金额 29900 分,用户随后修改为 39900 分。旧批准不应继续适用。比较动作指纹和任务版本,可以拒绝这一冲突并重新提议。
hash 只是相同内容的识别方式,不是签名或认证。攻击者知道动作内容,也能计算同样 hash;是否有权批准仍要由身份和权限系统判断。
如果人工编辑的是不影响支付的报告措辞,是否必须重新批准全部动作,可以依据业务契约细分。关键是明确哪些字段决定授权对象,不要粗暴地把每次文字修改都当成同一个动作,也不要全部忽略。
重复点击和迟到决定怎样处理
同一个 decision ID 重复提交,应返回原处理结果,不再次执行支付。不同决定同时到达,则通过提案版本或条件更新让其中一个明确冲突,而不是采用“最后写入者获胜”静默覆盖。
审批已过期后收到批准,系统应拒绝继续执行并显示当前状态。任务已经取消后收到批准,也不能复活任务。若任务正处于结果未知的支付阶段,则应先处理对账,不直接套用普通取消逻辑。
多个并行审批还需要明确每份决定对应哪个 interrupt 或 proposal。用同一个 thread ID 不足以区分多个待回答问题。恢复入口应匹配具体提案,而不是把第一个收到的布尔值交给任意暂停点。
恢复时仍要重新检查当前条件
昨天批准时订单可退,今天可能已经有另一笔退款占用了余额。执行前需要重新核验当前业务状态与权限,不能只相信历史批准。
这不意味着每次恢复都强迫用户再批准一次。只要原批准仍有效、参数未变且当前条件满足,就可以继续;只有影响授权或业务可行性的变化才需要新的处理。
在 LangGraph 中,interrupt 所在节点会重新进入,批准后的真实副作用应放在明确节点中,并继续使用幂等操作标识。框架帮忙交接输入,不替应用建立订单权限。
人工接管以后,机器怎样退出
接管应改变任务所有权或允许的执行模式,通知并取消自动分支,阻止新增副作用。已发出的请求需要记录和核对,不能把接管理解成撤销过去。
用户界面应展示“等待补充”“等待批准”“人工处理中”等不同状态,并提供当前可执行的操作。好的体验依赖明确的运行状态,不能只靠模型生成一句解释。
配套refund_cli.py下载将提交、批准和推进拆成独立命令。下一篇讨论收到有效决定后,哪个 Worker 获得继续执行的权利。