![[从零开始搭一个企业级 Agent Harness] 2/10:为什么 Agent 总是过早宣布完成?](/_next/image?url=%2Fblog%2Fagent-harness-covers%2Fpart-02.webp&w=3840&q=75&dpl=dpl_HKDZtjNTn1U2zk8pGrYRnYGkyGR9)
从零开始搭一个企业级 Agent Harness2/10:为什么 Agent 总是过早宣布完成?
对话窗口里,Agent已经写下“处理完成”。支付后台的交易列表里,却找不到退款编号(refund ID);邮件列表最右边,状态仍是“草稿”。
一句话先走到了终点,真实世界还停在半路。
上一章的最小循环把模型的 final 当作程序终点。现在要把这两个终点拆开:模型可以结束发言,任务只有在证据齐全以后才能结束。
先把“帮我处理”写成可以验收的终点
用户最初只说:“帮我处理重复扣款。”这句话给了方向,没有给出完成条件。
我先把这句话拆开。先确认客户、订单和两笔重复交易,再确定最多退多少、哪些动作不在范围内、什么情况必须停下来等人。最后还要写清支付系统到哪个状态才算退款成功,通知邮件到哪个状态才算送达。
查清交易以后,这张订单多扣了42美元。我把前面的问号和这个金额一起写成任务契约,最小版本至少包含下面几项:
| 字段 | 这笔退款里的值 |
|---|---|
| 目标 | 退回这张订单多收的42美元 |
| 数据范围 | 当前客户与这张订单 |
| 不允许做的事 | 取消原订单、修改其他账单 |
| 不变量 | 退款总额不得超过重复扣款 |
| 允许工具 | 查订单、创建退款、发送邮件 |
| 验收证据 | 支付退款编号、CRM记录、邮件投递记录 |
执行途中要修改目标,就产生一份新版本。原来的金额、范围和批准不会被悄悄覆盖。这样一来,Agent不再面对一句可以随意解释的自然语言,而是面对一条有边界、有终点的跑道。
任务契约:在执行前固定目标、边界、能力、交付物和验收标准的结构化说明。
不变量:执行过程中始终不能被破坏的条件,例如退款总额不能超过实际重复扣款。
终点写清以后,还要知道走到了哪里
退款任务可以拆成三项:创建一次退款,把退款编号写进CRM,把通知邮件投递给订单所有者。
Agent每完成一步,运行系统就在独立账本里改状态,并附上证据引用。这里的“独立”,是指状态不由聊天文字决定。支付接口只返回“请求已接收”,退款项仍然是“正在处理”(RUNNING);支付后台出现匹配的退款编号,并达到任务契约要求的最终状态,才变成“通过”(PASSED)。邮件还在草稿箱,就不能因为正文已经写好而标记投递完成。
这份进度账本不放在聊天摘要里。上下文压缩、模型切换或服务重启以后,它仍然保留每个工作项的状态、尝试次数、错误和证据。
模型负责建议下一步,运行系统负责握住那支改状态的笔。
Anthropic在长任务软件工程实验中使用结构化功能清单,每次只完成一项,实际测试通过以后才标记完成。退款不需要照搬软件开发格式,但可以沿用同一原则:终点先定义,进度独立保存,通过必须有证据。
进度账本:独立于聊天文本保存工作项、状态、尝试次数、错误和证据引用的运行记录。
最后签字的人,不能只听执行者自述
执行Agent最容易拿到的“证据”,是自己刚写出的内容。它读一遍退款邮件,金额和语气都正确,于是宣布通知完成。可这只能证明草稿存在,不能证明邮件系统已经投递。
这时需要一个独立的核验程序,也就是Verifier。它沿任务契约去三个事实源取证:到支付系统查询这张订单是否恰好有一笔42美元退款,而且状态已经达到契约要求;到客户工单系统(CRM)确认写入同一个退款编号;到邮件系统确认收件人与投递状态。金额、状态和数量用确定性规则检查,语气与解释质量再交给模型评分或人工审阅。
Verifier使用只读权限。它可以看退款记录,没有再次退款的能力;它读取权威系统,也不会和执行Agent围着同一份摘要互相确认。
三道门分别检查计划、过程和结果。计划门回答“该交的东西齐不齐”;过程门回答“工具、审批和预算有没有越界”;结果门直接读取支付、CRM与邮件系统,回答“真实世界是否已经变化”。三道门都通过,任务才进入 COMPLETED。
核验程序(Verifier):根据预先确定的标准和环境证据,独立检查任务是否真正完成的组件或角色。
业务真值(Ground truth):来自目标系统、测试结果或人工确认的可核实事实。
模型说“完成”以后,状态机才开始收尾
模型提交最后答案时,Harness不关闭任务,而是把状态从“正在执行”推到“正在核验”(RUNNING → VERIFYING)。
如果缺少用户输入,任务停下来提问;高风险退款没有批准,停下来等待审批;支付系统仍在处理,就继续等证据;外部结果无法确认,转入对账;预算耗尽,则停在 STOPPED_WITH_EVIDENCE,把已有进度和证据交给人。
一个布尔值只有“完成”或“没完成”两个答案,无法说明卡在哪里。状态机则列出允许出现的状态,以及每个状态下一步能去哪。于是每个“还没完成”都有明确原因:是在等用户、等批准、等支付结果,还是在对账。任务不会在等待支付结果时重新生成退款,也不会在审批缺失时用一段流畅回答掩盖阻塞。
2026年的进展:框架开始区分“这一轮结束”和“任务成功”
Anthropic在2026年1月9日发布的Agent评测指南里,把执行过程与最终结果明确分开。Agent写下“已经退款”、选择了哪些工具,属于执行过程;支付系统中的退款记录才是外部结果。这个区分正好对应前面的过程门和结果门。
在OpenAI Agents API quickstart里,turn.completed 只表示当前这一轮结束,session.idle 只表示现在没有继续运行,都不能替业务系统宣布任务成功。OpenAI的执行轨迹评分可以检查工具选择、参数、把任务交给另一个Agent的过程和政策执行;退款编号仍然由Verifier到支付系统查询。
需要在退款前停下来等人时,LangGraph可以保存当前节点,再通过 interrupt() 等待批准。这里的节点,就是流程中的一个步骤。审批者关掉网页,隔天回来,应用仍然载入同一笔订单、金额和动作。恢复时程序会重新进入暂停所在的节点处理批准结果,因此真正的退款调用放在它后面的独立节点,并始终使用同一个幂等键。OpenAI Agents SDK提供批准与拒绝控制,Restate可以把等待状态持久保存。它们减少暂停、恢复和循环控制代码,任务契约与业务验收仍由团队定义。
批准必须绑定原始动作。审批者要修改金额,系统拒绝旧调用,生成新的动作编号,再重新经过政策和审批;它不会拿旧批准去放行一笔已经变化的退款。
回到开头那句“处理完成”
现在Agent再次提交最后答案,运行系统先打开任务契约。支付项缺少退款编号,继续等待;CRM项没有记录,退回执行;邮件仍在草稿箱,也不能通过结果门。
直到支付、CRM和邮件各自拿出匹配证据,状态才从 VERIFYING 进入 COMPLETED。这时“处理完成”不再是一句模型生成的文字,而是一组程序可以复查的事实。
终点终于稳住了。任务接下来跑得更长,历史、工具结果和文件会不断增加。下一篇处理新的问题:最重要的规则明明还在上下文里,Agent为什么还是会忘?
新文章,直接发到你的邮箱。
AI、工程与实验,每月 1–2 封。
无垃圾邮件,随时取消。