AI Agent · 执行边界贯通 · 约90–120分钟 · Python 3.11+标准库
“请关闭重复工单”怎样走到一张可信回执?
用户要关闭CASE-2044。模型回答“已处理”,你却不知道是否有重复证据、是否审批、是否真正提交;更糟的是服务已经写入,客户端刚好超时。再让模型判断“重试还是失败”并不能找回这个事实。这一课把一条请求拆成观察、提案、可信审批与持久回执,亲自跑通,再破坏其中一条边界。
先修是Python函数、字典、异常和assert。实验没有外部模型、真实工单或云账号;ScriptedSelector用固定选择替身测试执行器。SQLite真实保存本地资源、幂等记录与回执,但不代表分布式服务。我们学习的是接入概率模型后仍必须成立的执行合同,模型语义表现另行评测。
第一步先决定:是否需要Agent?
如果流程已固定为“查状态→核对重复→审批→关闭”,确定性工作流就能完成。它也能分支、重试和等人。Agent增加的选择,是模型根据当前观察决定下一步查什么、是否有足够证据、何时结束;这不赋予模型修改权限和预算的权力。接一个模型只为让固定步骤变得更随机,通常不值得。
本实验将Selector接口留在运行时外侧;当前用固定替身让你离线制造故障。将来可以把JSON模型客户端注入这个接口,仍要保留动作白名单、资源范围、观察校验和停止守卫。原版Ch04解释选择循环,Ch06解释可靠工具;这里把“只到提案”和“真正执行”接起来。
先跑到提案,别急着签成成功
下载完整ZIP,解压进入flow.py所在目录。先预测:读两次后证据一致,缺审批能否写?然后运行:
cd proposal-receipt
python3 -B flow.py run --scenario missing --out runs/wait
python3 -B -m unittest discover -s tests -v
Windows可用py -3。首次状态为MISSING_APPROVAL:读取2次、写尝试0次、提交0次。它不是任务失败,而是在缺授权时正确停下来。18条测试通过只证明这些合成合同。proposal.json保存待确认动作,trace.json保存最近轨迹,runs.jsonl保留历次记录。详细边界和排错见README。
| 已知事实 | 候选动作 | 实际结果与边界 |
|---|---|---|
| 只有资源ID | get_case | 工具按可信tenant读到OPEN、version1 |
| 当前可处理,但是否重复未知 | get_registry | 重复依据CASE-1000;两类工具的ID、状态、版本和重复依据一致 |
| 已有重复依据 | propose_close(note=重复件) | 形成具体提案;缺审批停止,写0次 |
两个读工具在本包共享合成SQLite源,只为测试控制流;真实来源需各自的更新时间、版本和权威性。只返回200不代表观察正确:资源/租户/状态都要核对。动作里夹一个approved:true、换另一个resource_id或扩展工具名,执行器会拒绝。模型候选只是数据,不是命令。
审批批准的是哪个具体动作?
提案包含operation_id、tenant、resource_id、expected_version和note。把它规范化计算args_digest,审批信封签operation_id、digest与expires_at。签的是“对这一版本的这一工单写这一note”,不是给模型一张可任意写的通行证。参数变成“误报”,旧审批立刻失效;恰到过期时刻也不能执行。
你主动执行下面的本地命令,教学Authority才对已有不可变提案签发信封。模型选择器不能调用它。随后写入口复核签名、摘要、有效期;SQLite事务只在状态仍OPEN、version仍1时关闭并写回执:
python3 -B flow.py approve --out runs/wait
结果应为DONE、committed_writes=1。这里的公开DEMO_KEY和固定教学时钟1000是fixture,不是生产密钥/身份网关。真实签发必须置于受信服务;tenant来自已认证身份,不能相信客户端自报。version保护还依赖源系统每次相关事实变化都递增版本,不遵守这一合同就不能靠数字检查防覆盖。
超时回答的是“没拿到响应”,不是“没写上”
新目录运行after-commit:SQLite已经在同一事务里关闭资源并保存回执,再抛UnknownWrite模拟响应丢失。异常不会把after_commit事实透露给调用方;可靠写入逻辑只能lookup原operation_id。查到回执就返回同一事实,不再发送。before-commit则核对无回执,再沿原号有界重投。
python3 -B flow.py run --scenario after-commit --out runs/after
python3 -B flow.py run --scenario before-commit --out runs/before
前者本地write_attempts=1、committed_writes=1;后者尝试2次、提交仍1次。幂等要由接收方保存唯一操作结果,不能只靠客户端不点两次。相同操作号换payload应返回IDEMPOTENCY_CONFLICT;另一项真实意图必须分配新号。同一意图的重试沿用旧号,模型不能在重试中生成UUID。
真实lookup可能暂时查不到,服务也可能仍在处理中。本包lookup同步,因此它只证明这个契约下的恢复,不证明最终一致API。未知结果应保留unknown、等待或人工核对,不能换号或直接宣称失败。图里最后一格是完成证据,不是对所有服务作“恰好一次”的保证。
有审批,为什么仍然可能写不进去?
python3 -B flow.py run --scenario changed-args --out runs/changed
python3 -B flow.py run --scenario stale-state --out runs/stale
python3 -B flow.py run --scenario held-state --out runs/held
python3 -B flow.py run --scenario expired --out runs/expired
python3 -B flow.py run --scenario conflict --out runs/conflict
python3 -B flow.py run --scenario forged --out runs/forged
python3 -B flow.py run --scenario loop --out runs/loop
参数变了、过期、伪造approved字段都在提交前拒绝,write_attempts为0。stale-state在审批后保留OPEN,却把重复依据变成CASE-2000、version变成2:旧动作确实获准,但version条件拒绝覆盖新事实,写尝试1、实际提交0。另有held-state场景检查HELD状态不会被关闭。conflict在第二条观察后HANDOFF,甚至不形成提案。两次get_case不能代替get_registry;即使状态都OPEN,版本或重复依据不同仍停止,不能只数读了两次。loop在第三次相同候选执行前NO_PROGRESS,不能靠加预算掩盖没有新事实。
调试时先问哪一道边界最早失败。摘要不等去找参数变化;STATE_CHANGED去读权威资源版本;UnknownWrite去查外部回执。调整Prompt无法修复一笔已经提交的业务事实。为每个失败保留正常授权对照,确保修复不是“一律不写”。
换一个进程,再拿同一张回执
退出后用原来的runs/after目录再执行相同命令:
python3 -B flow.py run --scenario after-commit --out runs/after
新进程从ledger.sqlite3核对已有回执,返回同一receipt_id;新write_attempts为0、累计committed_writes仍1。记录证明状态不只藏在聊天历史或进程内存。注意每个新故障要用新目录:已有回执会直接对账成功,不能拿它假装经过了这次故障。程序保留runs.jsonl方便比较前后两次。
迁移到库存预留,规则不能只改名
你独立设计“在仓库A预留SKU-7三件”:先说明是否需要模型选择,再定义库存观察、数量提案、可信审批、条件扣减与回执。提交库存足够、库存不足、审批后数量变了、提交后超时四条trace;至少实现库存不足和同意图重投两条可运行失败测试。本包没有实现库存,这部分不能只交设计就宣称运行通过。
再做工单红→绿:在自己的副本删除commit的version条件和对应SQL绑定参数,stale-state测试应红;恢复约束后重跑18条。交付原始失败、修复与正常对照,以及一张“Request/Observation/Proposal/Approval/Receipt各由谁拥有”的表。只解释最终DONE无法证明没有覆盖别人的修改。
做完再看参考条件
库存审批绑定warehouse/SKU/qty/version/operation;提交时available≥qty且版本匹配。两个仓库不能混用证据;同意图多次投递最多一次实际扣减;没有回执不宣称成功。工单的OPEN/duplicate_of不适用于库存语义。模型候选由执行器检验,真实事实由工具/数据库提供,审批来自可信人审流程,完成来自回执。未实现的模型接入/租户网关/远程lookup应写成待验证边界。
接下来读持久化与恢复及轨迹评测,把非法动作、无证据、超时和正确等待分别记成用例。职业方向是选读分支,不是理解Agent的前置条件。
LangGraph官方Idempotency说明:带着“未完成任务可能重执行”读这一段;画出checkpoint落盘与外部写入之间的危险窗口,解释为什么还需服务端去重与核对。