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。

一次请求中的三轮选择;每轮观察改变可做的事
已知事实候选动作实际结果与边界
只有资源IDget_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”,不是给模型一张可任意写的通行证。参数变成“误报”,旧审批立刻失效;恰到过期时刻也不能执行。

提案、审批和实际提交是三个不同边界Proposal:待确认这一步写入0次Authority:可信代码签参数与有效期条件事务:查现场OPEN且version匹配Receipt:持久事实同意图重投同回执审批通过仍需检查现场事实;回执丢失时核对原操作号,不能猜失败。
“请求被批准”与“请求已提交”分开。写入口由代码控制,完成来自真实回执。

你主动执行下面的本地命令,教学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落盘与外部写入之间的危险窗口,解释为什么还需服务端去重与核对。