# M05 工作流、人工审批与长任务

> 本章产出：显式任务状态机、可持久化审批对象、跨会话交接记录。核心问题不是“如何暂停函数”，而是“暂停时什么事实必须留下”。

## 1. 当用户两小时以后才点批准

同步函数里等待用户输入的演示很简单。真实用户可能关掉浏览器，Worker可能重启，订单可能在等待期间变化。若你一直保持数据库事务和工作线程，资源会被大量等待任务占住；若只把批准标志存在内存，重启就丢失。

因此等待不是睡眠，而是一个持久状态。任务进入`WAITING_APPROVAL`后保存提案、到期时间和恢复位置，释放执行资源。新的审批事件到来后再调度一次短执行。

## 2. 为run建立显式状态机

本课程主要状态为：`QUEUED`、`RUNNING`、`WAITING_USER`、`WAITING_APPROVAL`、`RECONCILING`、`CANCELLING`、`SUCCEEDED`、`FAILED`、`CANCELLED`、`EXPIRED`。

`RECONCILING`表示外部副作用结果未知，需要查询操作状态或人工处理；它不是“失败后重试”的别名。终态不可被迟到的旧Worker随意改回运行态。`WAITING_USER`通常等待补充信息，`WAITING_APPROVAL`必须绑定一个具体操作。

每次转移包含旧版本、目标状态、触发事件和原因。执行`UPDATE ... WHERE version=expected_version`，更新行数为0时重新读取，而不是覆盖别人已经完成的状态。

<!-- interaction:I11 -->

## 3. 审批到底批准了什么

一个`approved=true`布尔值没有说明订单、金额、币种、收款目标或政策版本。它可以被错误地复用在不同操作上。

课程提案包含：租户、用户、订单、动作、服务端计算的金额、币种、订单版本、政策版本、operation_id和过期时间。将规范化后的关键字段做摘要，是为了检测批准对象是否发生变化；**摘要不是授权本身**。

授权还需要服务端持久记录：谁批准、通过什么认证上下文、何时批准、批准哪个proposal、是否已撤销。网页计算出相同哈希，不能自行把审批变成有效。模型生成一段“用户已批准”的文本也不算。

由于JSON键顺序、浮点和Unicode可能导致不同序列化结果，本课程金额用整数分、字段集合固定、序列化顺序固定。生产系统需要明确的规范化方案。摘要只包含业务语义字段，不包含随重试变化的attempt_id。

<!-- interaction:I10 -->

## 4. 审批前后都有检查

创建提案时，服务端读取订单并计算可退金额。批准时，检查主体有权批准该提案且尚未过期。执行时，再检查订单版本、余额和政策前置条件。

为什么批准后还要检查？用户可能同时在另一设备完成退款；客服可能修改订单；政策可能失效。检查发生在提交副作用的事务或外部服务原子操作里，不能只在几秒前读一次后就盲写。

如果条件变化，返回`STALE_PROPOSAL`，重新生成需要再次确认的提案。不要静默把用户批准的100元改成120元再执行，也不要无条件把先前批准迁移到新的收款对象。

## 5. 取消、拒绝、超时是不同事件

拒绝审批表示不允许这个提案执行，任务可以回到解释或结束。审批超时表示授权不再有效，可生成新提案但需要新确认。取消任务表示停止后续执行，不过已经提交的外部操作不一定能够撤销。

UI应区别“已收到取消请求”“执行已停止”“存在待核对操作”。本课程不把取消等同于分布式事务回滚。若外部退款已成功，再执行所谓“反向退款”是一个新的业务操作，需要新的权限和政策，不是自动补偿按钮。

## 6. 框架的中断并不是任意Python续跑

LangGraph的Checkpointer与Store分别面向线程内图状态和跨线程数据，内存实现不代表进程重启后仍保留。[S24](../../references/SOURCES.md#s24)

更容易考错的是interrupt恢复语义：官方文档说明，恢复可以从触发中断的节点开头重新执行，节点中interrupt之前的代码可能再次运行。因此不要在前面放未经保护的发邮件、扣款或生成新业务意图键的操作。[S25](../../references/SOURCES.md#s25)

正确的比较方式不是“框架A能暂停，框架B不能”，而是检查保存边界、重执行范围、外部副作用处理、代码版本变更和迁移能力。课程先实现显式状态机，再通过对照表学习框架的语义。

## 7. 长任务需要交接协议，不只是摘要

Coding Agent跨多个上下文窗口时，需要知道目标、已经做了什么、哪些测试还没过，以及工作区处于哪个版本。一个散文式摘要可能遗漏“测试已修改”或“依赖只装了一半”。

课程交接对象包括：目标与不可变约束、当前代码或数据版本、已完成的可验收里程碑、失败测试、待验证假设、下一步建议、重要artifact引用。后继执行器应重新验证环境，而不是把交接文字当可信事实。

Anthropic关于长时间运行Agent的文章展示了初始化和逐步推进、保留交接材料的做法。我们提取的是增量验收的原则，不把其中某一模型的行为视为永远成立。[S06](../../references/SOURCES.md#s06)

## 8. 什么时候使用固定工作流

身份校验、审批、扣减预算、审计和提交写操作适合确定性工作流。开放式检索、判断是否需要补充材料、组织解释可能交给模型。混合设计能够让模型有灵活性，同时不让它决定哪些授权条件可以跳过。

不要为了“看起来更Agent”把所有业务规则改成Prompt；也不要为了可预测性把所有自然语言变化硬编码成数百个分支。先找清楚变化来自哪里：数据、表达方式，还是业务政策本身。

## 9. 本章实验

L05的第一部分实现审批对象与状态机，并测试：重复批准、其他用户批准、过期、金额改变、订单版本改变、等待时重启。第二部分在M06把它服务化。

过关标准是：没有一个模型输出字段能够直接授权写操作；任何批准都能追溯到具体提案；等待不占用长事务；恢复路径可被确定性测试覆盖。

<!-- knowledge:K06 -->
<!-- knowledge:K17 -->
