完成门控与循环控制
开场解释
这个 Stage 在解决“Agent 会不会太早宣布任务完成”的问题。它站在一轮循环的尾部,专门做最后一道门:如果模型(LLM,大语言模型)说任务完成了,不立刻信,而是要求它连续两轮都确认完成才真的结束。这样做的意义很直接:前一 Stage 刚拿到终端观察结果,这里最适合决定“是继续干,还是收尾返回”。它的职责不是产生命令,而是判定本轮结果是否足以结束任务,并决定下一轮输入是什么。
主流程
- 先看这一轮的结论是不是“任务已完成”。
这里接的是上一个 Stage 产出的
is_task_complete和observation。observation可能是普通终端输出,也可能是上一轮专门发出的“你确认真的完成了吗?”这类确认问题的回复。 - 如果这轮判定完成,但还只是第一次说完成,就不结束。
这一步的意思是:先把“完成声明”变成一次显式复核。系统会把下一轮
prompt设成当前的observation,让模型再看一眼刚才的结果,并继续循环。Terminus2._get_completion_confirmation_message()(生成“请再次确认是否真的完成”的提示)就是为这个双重确认服务的。 - 如果这轮判定完成,而且上一轮已经处在“待确认完成”状态,也就是连续第二次确认完成,就直接
return。这时这个 Stage 的合同很明确:终止循环,并把任务完成作为最终结果交出去。 关键点不是“完成过一次”,而是“连续两次都说完成”。
- 如果这轮没有满足完成条件,就走普通路径。
系统把下一轮
prompt设成当前observation(通常就是终端输出),然后继续进入下一轮循环。 换句话说,这个 Stage 负责把“刚观察到的东西”变成“下一轮模型要读的输入”。 - 这道门为什么要放在这里,而不是更早或更晚?
因为只有在命令已跑完、观察结果已拿到、这一轮轨迹已记下之后,系统才有足够信息判断“是否真的结束”。放早了,看到的是半成品;放晚了,又会把错误的完成声明带出循环,失去防线。
状态流动
- 读: 无 — 输入给出的 register 列表中未显式提供任何本 Stage 使用的寄存器名;因此无法按寄存器名校验
is_task_complete、was_pending_completion、observation、prompt等状态读写,只能描述逻辑分支。 - 写: 无 — 同上。代码语义上,本 Stage 会更新“是否处于待二次确认”以及“下一轮 prompt”,但这些 register-id 未出现在提供列表中,不能编造名称。
- 清: 无 — 提供的 register 列表中没有可被真实引用的清理项,无法验证是否有显式清空动作。
- 触发下游:
stage-4.9 Command Execute → Observation → Trajectory Step— 当未终止时,本 Stage 把下一轮输入设为当前observation,然后回到循环下一次迭代;下一次迭代会再次经过前面的计划/执行链路,并在执行后回到 stage-4.9 产出新的观察。 - 触发下游:
return(终止循环,不再进入下一轮 Stage)— 条件是is_task_complete == true且was_pending_completion == true,即连续第二次确认完成;此时本 Stage 直接交付“任务完成”的终止结果。 - 触发下游:
continue(发出下一轮 prompt)— 条件一:is_task_complete == true且was_pending_completion == false,表示第一次声明完成;产物是“把当前 observation 作为下一轮 prompt,并标记进入待确认状态”。 - 触发下游:
continue(发出下一轮 prompt)— 条件二:is_task_complete == false,表示未完成或确认失败;产物是“把当前 observation 作为下一轮 prompt,按普通路径继续”。如果前一轮已经进入过首次确认,但这轮没有再次确认完成,那么它不会终止,而是回退到正常循环。
与前后 Stage 的衔接
上游 [stage-4.9](stage-4.9.html) 刚产出两样关键东西:观察结果和这一轮是否可视为完成。本 Stage 不再碰执行细节,只负责把这两个信号变成二选一的结果:要么终止并返回完成,要么把 observation 改写成下一轮 prompt后继续进入下一次循环入口。
Terminus2._run_agent_loopterminus_2.py:1550–1561 ↗
迭代尾部的完成门控与续转控制
stage 上下文: 该片段位于
Terminus2._run_agent_loop的一次 episode 收尾处,承接前面阶段已得到的is_task_complete、was_pending_completion与observation。它先调用self._dump_trajectory()持久化当前轨迹,再依据完成状态决定是立即退出整个函数,还是把本轮observation送入下一轮作为新的prompt。作为本 stage 的首个已译单元,这里承担了 stage 标题所说的 completion gate 与 loop control 的最终落点。
这段代码在干什么
此代码段在每轮结束时先执行 self._dump_trajectory(),把当前运行轨迹落盘。随后检查 is_task_complete:若其为真且 was_pending_completion 也为真,则直接 return 结束 _run_agent_loop;若仅 is_task_complete 为真,则把 observation 赋给 prompt 并 continue 进入下一轮。其余情况下同样执行 prompt = observation,但不显式跳转,由外围循环按常规流转进入下一次迭代。
接口 · 参数 / IO
(self) -> None
- 返回: 无显式返回值;实际控制效果分支化:当
is_task_complete and was_pending_completion时经裸return提前结束整个_run_agent_loop,否则不返回,由外围循环继续执行。 - 副作用: 调用
self._dump_trajectory()触发轨迹持久化这一外部副作用; 读取局部/外层作用域输入is_task_complete、was_pending_completion、observation以决定控制流; 在两个非返回分支中执行prompt = observation,为后续迭代准备下一轮输入; 仅在“首次完成”分支执行continue,立即进入下一轮循环
执行流
- 先无条件调用
self._dump_trajectory(),在完成本轮 episode 后立即持久化当前轨迹。 - 检查
is_task_complete;若为假,则跳过完成门控,直接执行末尾的prompt = observation。 - 若
is_task_complete为真,再检查was_pending_completion;当两者同时为真时,执行裸return,当前函数立即结束。 - 若
is_task_complete为真但was_pending_completion为假,则将observation赋给prompt,随后执行continue,显式开始下一轮循环。 - 在未命中上述
return/continue的常规路径中,执行最终的prompt = observation,但此路径本身不强制跳转,只把控制权留给外围循环的正常迭代机制。
源码
self._dump_trajectory()
if is_task_complete:
if was_pending_completion:
# Task is confirmed complete (this is the second time task_complete was True), return
return
else:
# First completion attempt - ask for confirmation and continue
prompt = observation
continue
prompt = observation
Non-obvious 设计决策
- 轨迹持久化被放在完成判定之前:代码先执行
self._dump_trajectory(),再检查is_task_complete/was_pending_completion,因此即便随后走到提前return,本轮轨迹也已先落盘。 - 完成退出采用双条件门控而非单条件门控:只有
is_task_complete与was_pending_completion同时成立才return;单独一次is_task_complete不结束函数,而是改为设置prompt并continue。 - 同一个
observation在两个非返回分支都被复用为下一轮prompt,但控制语义不同:首次完成分支带有显式continue,普通分支只有赋值、没有额外跳转;这一差异在源码中是显式区分的。
上下游关系
- 调用方: Terminus2._run_agent_loop
- 核心被调用: Terminus2._dump_trajectory
- 配置/状态来源: 局部布尔量
is_task_complete; 局部布尔量was_pending_completion; 局部值observation - 结果去向: 提前
return到Terminus2._run_agent_loop的调用方; 局部变量prompt,作为下一轮 agent 输入; 外围while/迭代控制流中的continue分支;self._dump_trajectory()所对应的轨迹持久化目标
reg-pending-completion— 经was_pending_completion决定是否退出
Terminus2._get_completion_confirmation_messageterminus_2.py:486–507 ↗
生成完工二次确认提示文案
stage 上下文: 该函数在本 stage 中承担文案生成角色:根据
self._parser_name为当前响应格式产出一条完工确认消息。它本身不推进循环、不修改状态,只把终端快照terminal_output包装成要求再次确认的提示语。与同 stage 中负责门控与循环控制的兄弟逻辑相比,这里仅负责形成提示文本本身。
这段代码在干什么
函数接收 terminal_output: str,读取 self._parser_name,返回一条要求再次确认任务完成的字符串。成功路径下,返回值都以 Current terminal state:\n{terminal_output}\n\n 为共同前缀,随后接相同的确认警告语,只在最后要求重复给出的完成标记上区分 json 与 xml。函数无副作用;若 self._parser_name 既不是 json 也不是 xml,则直接抛出 ValueError。
接口 · 参数 / IO
(self, terminal_output: str) -> str
- 参数:
terminal_output:str— 插入提示文案中的当前终端状态原文 - 读状态:
self._parser_name - 返回: 返回一条确认消息字符串;成功时固定以
Current terminal state:\n{terminal_output}\n\n开头,并要求再次给出完成标记:json分支要求包含"task_complete": true,xml分支要求包含<task_complete>true</task_complete>;若解析器名不受支持则不返回而是抛出ValueError。 - 副作用: 无
执行流
- 读取
self._parser_name,先判断是否为字面值json。 - 若为
json,返回一条字符串:以前缀Current terminal state:\n{terminal_output}\n\n嵌入传入的终端输出,再附加确认警告,并明确要求在 JSON 响应中再次包含"task_complete": true。 - 若不是
json,继续判断是否为字面值xml。 - 若为
xml,返回同样以前缀Current terminal state:\n{terminal_output}\n\n开头、主体警告语相同的字符串,只把最后的完成标记改为<task_complete>true</task_complete>。 - 若两种格式都不匹配,则抛出
ValueError,错误消息中回显当前的self._parser_name,并提示可用值是json或xml。
源码
def _get_completion_confirmation_message(self, terminal_output: str) -> str:
"""Return the format-specific task completion confirmation message."""
if self._parser_name == "json":
return (
f"Current terminal state:\n{terminal_output}\n\n"
"Are you sure you want to mark the task as complete? "
"This will trigger your solution to be graded and you won't be able to "
'make any further corrections. If so, include "task_complete": true '
"in your JSON response again."
)
elif self._parser_name == "xml":
return (
f"Current terminal state:\n{terminal_output}\n\n"
"Are you sure you want to mark the task as complete? "
"This will trigger your solution to be graded and you won't be able to "
"make any further corrections. If so, include "
"<task_complete>true</task_complete> again."
)
else:
raise ValueError(
f"Unknown parser_name: {self._parser_name}. Use 'json' or 'xml'."
)
Non-obvious 设计决策
- 代码没有为未知
self._parser_name提供兜底格式,而是显式raise ValueError(...)。这是一种失败即暴露的取舍:配置错误会立即显现,避免静默生成与实际响应 schema 不一致的确认文案。 - 两个成功分支复用了几乎完全相同的消息骨架,包括共同前缀
Current terminal state:\n{terminal_output}\n\n和相同的确认警告语,只在最后的完成标记文本上分化。这表明该函数把“格式差异”收敛到最小范围,仅对 schema 必需的字面标记做分支处理。 - 函数直接把原始
terminal_output内插进返回文案,没有做裁剪、转义或摘要处理。由此保留了调用方传入的终端状态全貌,但也意味着输出内容完全依赖输入字符串原样呈现。
上下游关系
- 调用方: 外部调用方:任何需要基于当前终端输出生成完工确认消息的实例方法
- 核心被调用: 内建
ValueError构造与抛出 - 配置/状态来源:
self._parser_name:决定选择json分支、xml分支,还是进入异常路径;terminal_output参数:提供返回字符串中的终端状态正文 - 结果去向: 返回给调用方的一条确认消息字符串; 异常路径将
ValueError传播给调用方处理 - 同类 sibling: Terminus2._run_agent_loop:兄弟逻辑负责依据完成状态决定继续、确认或返回;本函数仅生成其所需的确认文案,不承担循环控制。