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时重新读取,而不是覆盖别人已经完成的状态。
等待批准时,Worker去了哪里?
业务等待应持久化,不占用运行线程空转。
合成教学数据 · 未运行真实模型静态推演与完整规格
I11 等待批准时,Worker去了哪里?
关联章节:M05 · 交互类型:state-machine · 状态:提供完整状态/验收规格,UI待实现
教学目标
业务等待应持久化,不占用运行线程空转。
初始状态
{
"status": "RUNNING",
"lease_held": true,
"approval": "pending",
"version": 1
}
操作控件
进入等待
尝试claim
批准并恢复入队
重复批准
取消
切换非法转移
状态变化规则
RUNNING→WAITING_APPROVAL保存提案并释放lease。
等待中claim拒绝;有效批准通过事务转换QUEUED并写Outbox。
重复批准不创建第二次唤醒;终态不恢复执行。
黄金案例与反馈
wait
输入:
[
"wait",
"claim"
]
期望:
{
"status": "WAITING_APPROVAL",
"lease_held": false,
"claim": "NOT_RUNNABLE"
}
等待时间不是工具执行时间。
approve-twice
输入:
[
"wait",
"approve",
"approve"
]
期望:
{
"status": "QUEUED",
"new_dispatch_messages": 1
}
恢复与去重须在同一受版本保护的事务里。
terminal
输入:
[
"cancel",
"approve"
]
期望:
{
"status": "CANCELLED",
"new_dispatch_messages": 0
}
旧批准不能复活终态任务。
完成条件
演示等待释放资源
两次批准只唤醒一次
反馈层次
提交预测后显示结果;允许跳过预测但记录跳过,不阻止阅读
结果同时显示状态变化、原因与被拒绝动作;不是只给红绿灯
第一次错误给边界提示,第二次提供最小反例;参考推导始终可主动打开
重置恢复固定初始状态并保留历史尝试供用户手动清除
无障碍与静态替代
全部操作有原生button/input/select替代,不依赖拖拽
图与动画提供同步文字状态表;支持prefers-reduced-motion
状态改变用aria-live=polite摘要,不逐字朗读长日志
焦点不跳走;错误提示关联输入;颜色不是唯一编码
无JavaScript时直接展示初始输入、规则和黄金案例表,读者仍能完成纸面推演。状态动画仅作解释,不能成为获取答案的唯一途径。
持久化与重置
使用版本化键agent-course:1:I11,仅保存本地学习状态。支持单卡重置和全站清除;存储失败时以内存模式继续,不阻断阅读。
必须避免的误导
恢复与取消分支为项目规格,不能称现有RunLedger内核已完整实现。
执行边界
纯本地教学状态机;不执行任意代码,不连接真实资金/模型/宿主Shell。
3. 审批到底批准了什么
一个approved=true布尔值没有说明订单、金额、币种、收款目标或政策版本。它可以被错误地复用在不同操作上。
课程提案包含:租户、用户、订单、动作、服务端计算的金额、币种、订单版本、政策版本、operation_id和过期时间。将规范化后的关键字段做摘要,是为了检测批准对象是否发生变化;摘要不是授权本身。
授权还需要服务端持久记录:谁批准、通过什么认证上下文、何时批准、批准哪个proposal、是否已撤销。网页计算出相同哈希,不能自行把审批变成有效。模型生成一段“用户已批准”的文本也不算。
由于JSON键顺序、浮点和Unicode可能导致不同序列化结果,本课程金额用整数分、字段集合固定、序列化顺序固定。生产系统需要明确的规范化方案。摘要只包含业务语义字段,不包含随重试变化的attempt_id。
这份批准还能用吗?
批准绑定具体意图与有效期,而不是一句“用户同意”。
合成教学数据 · 未运行真实模型静态推演与完整规格
I10 这份批准还能用吗?
关联章节:M05 · 交互类型:approval-binding · 状态:提供纯逻辑参考函数,UI待实现
教学目标
批准绑定具体意图与有效期,而不是一句“用户同意”。
初始状态
{
"approved": {
"order_id": "A100",
"amount_cents": 10000,
"order_version": 1,
"policy_version": "policy-v2",
"operation_id": "op-1",
"expires_at": 130
},
"now": 110,
"same_principal": true,
"already_committed": false
}
操作控件
改金额
改订单版本
推进时钟
更换当前用户
切换已执行记录
检查是否可新执行/重放
状态变化规则
先检查身份;再检查参数是否绑定同一意图。
尚未提交时now>=expires_at拒绝。
已有同参确认结果时只返回旧结果,不再执行,因此允许过期后的重放读取。
黄金案例与反馈
expired
输入:
{
"now": 130,
"already_committed": false,
"same_digest": true,
"same_principal": true
}
期望:
{
"status": "APPROVAL_EXPIRED"
}
有效期边界为严格小于。
replay
输入:
{
"now": 500,
"already_committed": true,
"same_digest": true,
"same_principal": true
}
期望:
{
"status": "RETURN_STORED"
}
这不是重新授权一个过期副作用。
changed
输入:
{
"same_digest": false,
"already_committed": false,
"same_principal": true,
"now": 110
}
期望:
{
"status": "DIGEST_MISMATCH"
}
新金额需要新的提案和批准。
完成条件
正确区分新执行与旧结果重放
演示金额/身份/时间任一失配拒绝
反馈层次
提交预测后显示结果;允许跳过预测但记录跳过,不阻止阅读
结果同时显示状态变化、原因与被拒绝动作;不是只给红绿灯
第一次错误给边界提示,第二次提供最小反例;参考推导始终可主动打开
重置恢复固定初始状态并保留历史尝试供用户手动清除
无障碍与静态替代
全部操作有原生button/input/select替代,不依赖拖拽
图与动画提供同步文字状态表;支持prefers-reduced-motion
状态改变用aria-live=polite摘要,不逐字朗读长日志
焦点不跳走;错误提示关联输入;颜色不是唯一编码
无JavaScript时直接展示初始输入、规则和黄金案例表,读者仍能完成纸面推演。状态动画仅作解释,不能成为获取答案的唯一途径。
持久化与重置
使用版本化键agent-course:1:I10,仅保存本地学习状态。支持单卡重置和全站清除;存储失败时以内存模式继续,不阻断阅读。
必须避免的误导
页面哈希只能说明绑定,不代替真实认证;本卡是逻辑模拟。
执行边界
纯本地教学状态机;不执行任意代码,不连接真实资金/模型/宿主Shell。
逻辑参考
web/reference/engines.mjs中的approvalDecision;对应测试在web/reference/engines.test.mjs。UI可复用或等价移植,但不得改变黄金结果。
4. 审批前后都有检查
创建提案时,服务端读取订单并计算可退金额。批准时,检查主体有权批准该提案且尚未过期。执行时,再检查订单版本、余额和政策前置条件。
为什么批准后还要检查?用户可能同时在另一设备完成退款;客服可能修改订单;政策可能失效。检查发生在提交副作用的事务或外部服务原子操作里,不能只在几秒前读一次后就盲写。
如果条件变化,返回STALE_PROPOSAL,重新生成需要再次确认的提案。不要静默把用户批准的100元改成120元再执行,也不要无条件把先前批准迁移到新的收款对象。
5. 取消、拒绝、超时是不同事件
拒绝审批表示不允许这个提案执行,任务可以回到解释或结束。审批超时表示授权不再有效,可生成新提案但需要新确认。取消任务表示停止后续执行,不过已经提交的外部操作不一定能够撤销。
UI应区别“已收到取消请求”“执行已停止”“存在待核对操作”。本课程不把取消等同于分布式事务回滚。若外部退款已成功,再执行所谓“反向退款”是一个新的业务操作,需要新的权限和政策,不是自动补偿按钮。
6. 框架的中断并不是任意Python续跑
LangGraph的Checkpointer与Store分别面向线程内图状态和跨线程数据,内存实现不代表进程重启后仍保留。S24
更容易考错的是interrupt恢复语义:官方文档说明,恢复可以从触发中断的节点开头重新执行,节点中interrupt之前的代码可能再次运行。因此不要在前面放未经保护的发邮件、扣款或生成新业务意图键的操作。S25
正确的比较方式不是“框架A能暂停,框架B不能”,而是检查保存边界、重执行范围、外部副作用处理、代码版本变更和迁移能力。课程先实现显式状态机,再通过对照表学习框架的语义。
7. 长任务需要交接协议,不只是摘要
Coding Agent跨多个上下文窗口时,需要知道目标、已经做了什么、哪些测试还没过,以及工作区处于哪个版本。一个散文式摘要可能遗漏“测试已修改”或“依赖只装了一半”。
课程交接对象包括:目标与不可变约束、当前代码或数据版本、已完成的可验收里程碑、失败测试、待验证假设、下一步建议、重要artifact引用。后继执行器应重新验证环境,而不是把交接文字当可信事实。
Anthropic关于长时间运行Agent的文章展示了初始化和逐步推进、保留交接材料的做法。我们提取的是增量验收的原则,不把其中某一模型的行为视为永远成立。S06
8. 什么时候使用固定工作流
身份校验、审批、扣减预算、审计和提交写操作适合确定性工作流。开放式检索、判断是否需要补充材料、组织解释可能交给模型。混合设计能够让模型有灵活性,同时不让它决定哪些授权条件可以跳过。
不要为了“看起来更Agent”把所有业务规则改成Prompt;也不要为了可预测性把所有自然语言变化硬编码成数百个分支。先找清楚变化来自哪里:数据、表达方式,还是业务政策本身。
9. 本章实验
L05的第一部分实现审批对象与状态机,并测试:重复批准、其他用户批准、过期、金额改变、订单版本改变、等待时重启。第二部分在M06把它服务化。
过关标准是:没有一个模型输出字段能够直接授权写操作;任何批准都能追溯到具体提案;等待不占用长事务;恢复路径可被确定性测试覆盖。
K06 · 长任务交接靠显式工件,不靠模型“记住”
K06 长任务交接靠显式工件,不靠模型“记住”
类型:长任务Harness · 关联章节:M05 · 阅读方式:先做下面的推演,再看参考讨论。
原文在说什么
原文讨论长时间运行Agent跨上下文/会话推进任务的困难,以及通过初始化、进度工件与验证组织工作。
对本课程的具体启发(课程推论)
在本课程中,功能列表、操作ID、测试结果和待办状态是持久工件。重开会话时读取经过验证的事实,不要求模型从一段随意摘要恢复所有执行语义。
两分钟推演
设计一次Coding任务交接包,最多六个字段。分别标记哪个字段是权威状态、哪个只是可疑的模型自述。
参考讨论
包含任务目标、基础commit、补丁/工件引用、可信测试结果、未完成项、环境/预算状态。模型写的“已全部完成”不是可信测试结果。
不能从这里推出什么
进度文件本身也可被错误修改。重要状态要可核验,长期任务还需处理外部环境变化。
原始来源
- S06 · Effective harnesses for long-running agents(Anthropic;核查 2026-10-27)
本卡是原创中文概括与教学延伸,不是全文翻译,也不表示原作者认可本课程。不要把原文中的结果替换成本项目实测结果。
独立阅读 →K17 · Interrupt恢复不是任意Python指令续跑
K17 Interrupt恢复不是任意Python指令续跑
类型:框架语义 · 关联章节:M05 · 阅读方式:先做下面的推演,再看参考讨论。
原文在说什么
LangGraph文档描述持久化与interrupt,并指出恢复时节点会重新执行到中断处,节点内早于interrupt的代码可能再次运行。
对本课程的具体启发(课程推论)
把有副作用动作放在会重复执行的区域时,要有幂等边界。不要把“暂停图执行”误解为保存任意Python栈并从下一字节继续。
两分钟推演
节点中先发送邮件再interrupt等批准,恢复时会发生什么?给出两种修复。
参考讨论
邮件可能再次发送。将发送移到审批后的独立幂等节点,或使用稳定业务键去重;不要仅用进程内布尔变量标记已发。
不能从这里推出什么
不同框架和版本语义可能不同,必须看原文和测试;不能把本卡推广为所有工作流引擎。
原始来源
- S24 · Persistence(LangChain / LangGraph;核查 2026-10-27)
- S25 · Interrupts(LangChain / LangGraph;核查 2026-10-27)
本卡是原创中文概括与教学延伸,不是全文翻译,也不表示原作者认可本课程。不要把原文中的结果替换成本项目实测结果。
独立阅读 →