Terminus 2 手册

完成门控与循环控制

stage-4.8Completion Gate + Loop Control2 个函数

开场解释

这个 Stage 在解决“Agent 会不会太早宣布任务完成”的问题。它站在一轮循环的尾部,专门做最后一道门:如果模型(LLM,大语言模型)说任务完成了,不立刻信,而是要求它连续两轮都确认完成才真的结束。这样做的意义很直接:前一 Stage 刚拿到终端观察结果,这里最适合决定“是继续干,还是收尾返回”。它的职责不是产生命令,而是判定本轮结果是否足以结束任务,并决定下一轮输入是什么

主流程

  1. 先看这一轮的结论是不是“任务已完成”。

    这里接的是上一个 Stage 产出的 is_task_completeobservationobservation 可能是普通终端输出,也可能是上一轮专门发出的“你确认真的完成了吗?”这类确认问题的回复。

  2. 如果这轮判定完成,但还只是第一次说完成,就不结束

    这一步的意思是:先把“完成声明”变成一次显式复核。系统会把下一轮 prompt 设成当前的 observation,让模型再看一眼刚才的结果,并继续循环。 Terminus2._get_completion_confirmation_message()(生成“请再次确认是否真的完成”的提示)就是为这个双重确认服务的。

  3. 如果这轮判定完成,而且上一轮已经处在“待确认完成”状态,也就是连续第二次确认完成,就直接 return

    这时这个 Stage 的合同很明确:终止循环,并把任务完成作为最终结果交出去。 关键点不是“完成过一次”,而是“连续两次都说完成”。

  4. 如果这轮没有满足完成条件,就走普通路径。

    系统把下一轮 prompt 设成当前 observation(通常就是终端输出),然后继续进入下一轮循环。 换句话说,这个 Stage 负责把“刚观察到的东西”变成“下一轮模型要读的输入”。

  5. 这道门为什么要放在这里,而不是更早或更晚?

    因为只有在命令已跑完、观察结果已拿到、这一轮轨迹已记下之后,系统才有足够信息判断“是否真的结束”。放早了,看到的是半成品;放晚了,又会把错误的完成声明带出循环,失去防线。

状态流动

与前后 Stage 的衔接

上游 [stage-4.9](stage-4.9.html) 刚产出两样关键东西:观察结果这一轮是否可视为完成。本 Stage 不再碰执行细节,只负责把这两个信号变成二选一的结果:要么终止并返回完成,要么把 observation 改写成下一轮 prompt后继续进入下一次循环入口。

函数细节2

Terminus2._run_agent_loopterminus_2.py:1550–1561 ↗

迭代尾部的完成门控与续转控制

stage 上下文: 该片段位于 Terminus2._run_agent_loop 的一次 episode 收尾处,承接前面阶段已得到的 is_task_completewas_pending_completionobservation。它先调用 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 赋给 promptcontinue 进入下一轮。其余情况下同样执行 prompt = observation,但不显式跳转,由外围循环按常规流转进入下一次迭代。

接口 · 参数 / IO

(self) -> None

  • 返回: 无显式返回值;实际控制效果分支化:当 is_task_complete and was_pending_completion 时经裸 return 提前结束整个 _run_agent_loop,否则不返回,由外围循环继续执行。
  • 副作用: 调用 self._dump_trajectory() 触发轨迹持久化这一外部副作用; 读取局部/外层作用域输入 is_task_completewas_pending_completionobservation 以决定控制流; 在两个非返回分支中执行 prompt = observation,为后续迭代准备下一轮输入; 仅在“首次完成”分支执行 continue,立即进入下一轮循环

执行流

  1. 先无条件调用 self._dump_trajectory(),在完成本轮 episode 后立即持久化当前轨迹。
  2. 检查 is_task_complete;若为假,则跳过完成门控,直接执行末尾的 prompt = observation
  3. is_task_complete 为真,再检查 was_pending_completion;当两者同时为真时,执行裸 return,当前函数立即结束。
  4. is_task_complete 为真但 was_pending_completion 为假,则将 observation 赋给 prompt,随后执行 continue,显式开始下一轮循环。
  5. 在未命中上述 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_completewas_pending_completion 同时成立才 return;单独一次 is_task_complete 不结束函数,而是改为设置 promptcontinue
  • 同一个 observation 在两个非返回分支都被复用为下一轮 prompt,但控制语义不同:首次完成分支带有显式 continue,普通分支只有赋值、没有额外跳转;这一差异在源码中是显式区分的。

上下游关系

  • 调用方: Terminus2._run_agent_loop
  • 核心被调用: Terminus2._dump_trajectory
  • 配置/状态来源: 局部布尔量 is_task_complete; 局部布尔量 was_pending_completion; 局部值 observation
  • 结果去向: 提前 returnTerminus2._run_agent_loop 的调用方; 局部变量 prompt,作为下一轮 agent 输入; 外围 while/迭代控制流中的 continue 分支; self._dump_trajectory() 所对应的轨迹持久化目标
寄存器交互
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 为共同前缀,随后接相同的确认警告语,只在最后要求重复给出的完成标记上区分 jsonxml。函数无副作用;若 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": truexml 分支要求包含 <task_complete>true</task_complete>;若解析器名不受支持则不返回而是抛出 ValueError
  • 副作用: 无

执行流

  1. 读取 self._parser_name,先判断是否为字面值 json
  2. 若为 json,返回一条字符串:以前缀 Current terminal state:\n{terminal_output}\n\n 嵌入传入的终端输出,再附加确认警告,并明确要求在 JSON 响应中再次包含 "task_complete": true
  3. 若不是 json,继续判断是否为字面值 xml
  4. 若为 xml,返回同样以前缀 Current terminal state:\n{terminal_output}\n\n 开头、主体警告语相同的字符串,只把最后的完成标记改为 <task_complete>true</task_complete>
  5. 若两种格式都不匹配,则抛出 ValueError,错误消息中回显当前的 self._parser_name,并提示可用值是 jsonxml

源码

    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:兄弟逻辑负责依据完成状态决定继续、确认或返回;本函数仅生成其所需的确认文案,不承担循环控制。