循环工程:准入与停止
Loop 不是定时调一次 prompt。它有七项准入条件、五层停止语义和一条消息队列续行规则——「定时调用一个 Prompt」不构成 Loop,idle 也不等于完成。
Loop 不是「定时调用一个 Prompt」。一次定时调用没有持久游标、没有幂等键、没有独立验证,失败既不能恢复也不能审计——它不构成 Loop。真正的循环工程要同时回答两组问题:凭什么让任务循环起来(准入),以及凭什么算停了(停止语义)。while (hasToolCalls) 在 demo 里够用,一进生产就不够:结束要在好几个不同的地方分别确认。
Harness 管一次,Loop 管多次
分工先划清:Harness 定义一次运行的能力、约束和恢复机制;Loop 管理多次运行之间的触发、状态、验证和持续改进。一个完整的 Loop 走五步:Discover → Execute → Verify → Persist → Schedule——发现任务、执行、验证结果、把状态存到对话之外、再次调度 dsh-pi-stop。
其中两条硬规矩:执行状态必须保存在对话之外(内存里的状态进程一挂就没);每个工作项要有稳定的幂等键(同一项被重复触发时不产生重复副作用)。Loop 也不许改自己的 P0——自动修改自己的审批规则和安全边界,等于把刹车也交给油门 agent-trend-2026。
七项准入,缺一项就退回
| 条件 | 要求 |
|---|---|
| 重复性 | 任务周期性出现或可由稳定事件触发 |
| 可验证性 | 结果可自动检查、看环境状态或有明确人工 Gate |
| 可操作性 | Agent 有受控的读取、修改、测试和交付能力 |
| 可恢复性 | 执行可隔离、可重试、可回滚、可停止 |
| 经济性 | 单轮成本和单位成功成本可预算 |
| 可治理性 | 有 Owner、权限、审计、Kill Switch 和升级路径 |
| 可沉淀性 | loop 失败也应留下夹具、规则或技能,而非只留聊天记录 |
任一关键条件缺失,就退回单次 Agent、Workflow 或人工流程。这张表和 多 Agent 什么时候才值得 的准入条件是同一类工具:不是劝你别建,是逼你在建之前把每一项答出来。准入之后还有第二层问题——loop 里面装什么、会怎么坏,见 循环工程:控制面与失败模式。
「停」是五件不同的事
一个真实故障把五层拍在桌上:Agent 接工单建 PR 遇到 504——页面看不到 PR,但服务端可能已经创建;盲目重试可能建出重复 PR,就此收工又可能埋掉真失败;PR 建好了 CI 还在排队,「模型说已完成」算完成吗 dsh-pi-stop?
这时候「停」必须分层确认:① 模型流结束,只说明本次请求收到结束信号——可能是 max-tokens、报错或取消;② 工具批次返回,工具停了不代表这轮停了,模型还要看结果;③ turn 结束,工具结果已交回、无待处理回复、收件箱无新消息;④ driver idle,暂无下一条 turn;⑤ Goal 结束,持久状态落定为 complete / blocked / paused。idle 不是完成——外层的 Goal Driver 下次检查仍可能 followup 把它叫回来。平时一路顺利看不出差别,一旦 504、取消、进程崩溃、CI 排队同时发生,日志说不清「到底停在哪一层」,恢复就变成猜。
续行写消息队列,不合并布尔值
多个插件、多个 hook 都想表达「先别停」,怎么合并?布尔回调是最糟的答案:一堆 should_continue 标志位互相打架,谁也说不清是谁在什么时机续的行。正确做法是让续行意图走消息队列:插件要续行,就向下一 step / turn 的收件箱写一条消息,核心循环在 stopping hook 之后重读收件箱,再决定停还是继续 dsh-pi-stop。
好处有两个:多插件的续行语义不需要合并布尔值,每次续行意图都留下可审计记录——出事时你查得出「是哪个插件在 turn 结束后又塞了一条消息」。这和 事件只在安全点被消费 是同一个设计:队列在边界被读,而不是在执行中途横插一脚。
结束原因分开记,截断的参数不可执行
每层结束的原因要独立记录,且分清六种:completed / blocked / max-tokens / aborted / error / interrupted。两个最容易混的:aborted 是收到取消后主动清理,interrupted 是进程消失后由恢复层补上的状态——副作用审计和重试决策完全不同,混用会把你引向「对可能已建出的 PR 再重试一次」。turn 级的结束原因要保留到 turn 结束:第 1 步撞了 max-tokens、后续步骤补完,turn 仍记 max-tokens,只盯最后一个 step 会把截断看成成功 dsh-pi-stop。
还有一条写操作的安全线:max-tokens 截断要发生在工具参数解析之前。半截 JSON 就算解析器补全、schema 校验通过,参数的意思可能已经变了——写操作的副作用落地就收不回来。状态说不清时先停,不盲目重试,这条判断又回到 故障要先分类再计数:分类在先,重试决策才有依据。
