Terminus 2 手册

待交接提示 → 用户步骤(或切分)

stage-4.5Pending Handoff Prompt → User Step (or Split)2 个函数

开场解释

这个 Stage 在解决什么问题:把“该不该把总结交接词真正写进对话记录”这件事,放到一个统一位置收口。前面阶段可能已经产出了 handoff prompt(交接提示词:让后续上下文接着当前总结继续工作的那段用户话),但此时还不能立刻落盘,因为要先看当前是线性历史模式,还是普通模式。这个 Stage 就是在响应已解析完之后,决定这段提示词该变成一个新的 user step(用户消息步),还是改成一次轨迹切分。

主流程

  1. 先看有没有待处理的 handoff prompt。

    这个值放在 reg-pending-handoff-prompt。如果这里是空,说明这轮没有要交接的内容,这个 Stage 就什么都不做,直接往下走。

  2. 如果有,就看是不是线性历史模式。

    self._linear_history 可以理解成一种“历史只能沿一条线往前写”的模式。这里分支的原因很重要:

    • 在线性模式里,不能简单把 handoff prompt 追加成一条新 user step,否则会把“总结前”和“总结后”的历史混在同一条轨迹里。
    • 在普通模式里,直接把它当成一条新的用户消息接上去就够了。
  3. 线性模式:调用 _split_trajectory_on_summarization()(按总结边界把当前轨迹切开,并开始一段新的延续轨迹)。

    这里的重点不是“再加一条消息”,而是“换一段历史”。这样做的意义是:总结前的长轨迹到这里收口;总结后的新轨迹从更干净的上下文继续。handoff prompt 在新轨迹里是隐含生效的,不需要再显式写成一步。

  4. 非线性模式:把 handoff prompt 追加到当前 live trajectory(当前正在增长的轨迹记录)里,来源记为 user

    这相当于明确告诉后面的 Agent:“现在把这段交接词当作用户刚说的话,按它继续。”

  5. 只有在真正消费了这个 handoff prompt 之后,才清掉寄存器。

    这样可以保证:前面阶段先“提出一个待处理交接”,这里再“按当前历史策略落地”。之所以要延后到这个 Stage 才写,是因为前面产出 prompt 时,还不知道最终应该“加一步”还是“切轨迹”。

状态流动

注:本 Stage 还依赖“live trajectory / 轨迹文件 / 历史模式”这类状态承载体,但输入材料里没有给出它们对应的 register id,所以这里明确按“非 register 状态”说明,不伪造寄存器名。

与前后 Stage 的衔接

上游在更早阶段已经把 handoff prompt 放进 reg-pending-handoff-prompt,而前一站 stage-4.4 Response Parse 则完成了本轮响应解析,让系统进入“该不该落地这段交接词”的时点。本 Stage 的产出不是新的模型响应,而是一次历史写入策略:要么往当前轨迹补一条 user step,要么在线性模式下切出一段新轨迹;做完后,后续阶段再按更新后的轨迹状态继续往下跑。

函数细节2

Terminus2._run_agent_loopterminus_2.py:1318–1354 ↗

汇总并落地交接提示的轨迹分支段

stage 上下文: 这段代码位于 stage-4.6,负责把前面 summarization 侧流程暂存下来的产物真正写入当前 trial 的 trajectory。触发条件是 self._pending_subagent_refsself._pending_handoff_prompt 至少其一非空。它与同 stage 的前后段形成顺序配合:前序路径负责产生这些 pending 寄存状态,本段负责消费并清空,后续阶段则继续记录 marker、assistant 输出与完成态。

这段代码在干什么

该段先检查 self._pending_subagent_refs,若存在,则向 self._trajectory_steps 追加一条合成的 system 步骤,用于声明已执行上下文摘要并保留子代理轨迹引用。随后检查 self._pending_handoff_prompt,并根据 self._linear_history 决定是调用 _split_trajectory_on_summarization(...) 立即切分轨迹,还是直接把该 prompt 作为 user 步骤追加到当前轨迹。两类 pending 状态在消费后都会被显式清空,因此本段的真实产出是 trajectory 状态更新与寄存器回收,而非返回值。

接口 · 参数 / IO

(self) -> None

  • 参数: self: ? — Terminus2 实例;提供 pending 状态、trajectory 容器与 linear-history 配置
  • 读状态: self._pending_subagent_refs, self._trajectory_steps, self._pending_handoff_prompt, self._linear_history
  • 返回: None;实际产出是向 self._trajectory_steps 追加步骤、可能触发轨迹切分,并清空对应 pending 状态。
  • 副作用: 向 self._trajectory_steps 追加一条 source="system" 的摘要/交接记录; 将 self._pending_subagent_refs 置为 None; 在非 linear-history 模式下,向 self._trajectory_steps 追加一条 source="user" 的 handoff prompt; 在 linear-history 模式下调用 self._split_trajectory_on_summarization(self._pending_handoff_prompt); 将 self._pending_handoff_prompt 置为 None

执行流

  1. self._pending_subagent_refs 非空,则构造一条新的 Step 并追加到 self._trajectory_steps:其 source 固定为 "system"message 固定为“Performed context summarization and handoff to continue task.”,并把 self._pending_subagent_refs 包装进 ObservationResult(subagent_trajectory_ref=...) 后写入 observation.results
  2. 完成上述 system 步骤落地后,立即把 self._pending_subagent_refs 设为 None,避免同一批摘要引用在后续循环中被重复写入。
  3. 随后检查 self._pending_handoff_prompt;若该交接提示存在,则进入 handoff 处理分支。
  4. self._linear_history 为真时,不向当前 self._trajectory_steps 追加用户步骤,而是直接调用 _split_trajectory_on_summarization(self._pending_handoff_prompt),把轨迹切分责任交给专门的 split 机制处理。
  5. self._linear_history 为假时,直接在当前轨迹中追加一条新的 Step,其 source="user"messageself._pending_handoff_prompt,从而把交接提示显式写入 live trajectory。
  6. 无论走哪条 handoff 分支,最后都把 self._pending_handoff_prompt 设为 None,表示该 deferred prompt 已被消费。

源码

            if self._pending_subagent_refs:
                self._trajectory_steps.append(
                    Step(
                        step_id=len(self._trajectory_steps) + 1,
                        timestamp=datetime.now(timezone.utc).isoformat(),
                        source="system",
                        message="Performed context summarization and handoff to continue task.",
                        observation=Observation(
                            results=[
                                ObservationResult(
                                    subagent_trajectory_ref=self._pending_subagent_refs
                                )
                            ]
                        ),
                    )
                )
                self._pending_subagent_refs = None

            # Handle handoff prompt based on linear_history mode
            if self._pending_handoff_prompt:
                # If linear_history mode is enabled, split trajectory immediately WITHOUT adding handoff step
                # The handoff step will be added to the continuation trajectory during the split
                if self._linear_history:
                    self._split_trajectory_on_summarization(
                        self._pending_handoff_prompt
                    )
                else:
                    # For non-linear mode, add the handoff prompt as a user step
                    self._trajectory_steps.append(
                        Step(
                            step_id=len(self._trajectory_steps) + 1,
                            timestamp=datetime.now(timezone.utc).isoformat(),
                            source="user",
                            message=self._pending_handoff_prompt,
                        )
                    )
                self._pending_handoff_prompt = None

Non-obvious 设计决策

  • 对子代理摘要引用采用单独的合成 system 步骤记录,而不是把它们混入 handoff prompt 的 user 步骤中;这样做把“系统发生了 summarization”与“后续任务如何继续”分离成两类语义不同的轨迹事件,便于后续回放与审计。
  • 对 handoff prompt 采用 self._linear_history 分支,是一个显式的轨迹建模取舍:在线性历史模式下,不在当前文件中追加这条用户步骤,而是交由 _split_trajectory_on_summarization(...) 在切分边界处处理,以保持单条 trajectory 的时间线整洁;非线性模式则保留该 prompt 作为当前 live trajectory 的普通用户输入。
  • 两类 pending 状态都使用显式 truthy 检查并在消费后立刻清空,体现的是“一次生产、一次消费”的寄存器语义;若不及时置 None,后续迭代会重复物化同一摘要引用或同一 handoff prompt。

上下游关系

  • 调用方: Terminus2._run_agent_loop; _run_agent_loop 的主迭代流程在进入 assistant 响应生成前执行该段
  • 核心被调用: Step; Observation; ObservationResult; datetime.now; timezone.utc; self._split_trajectory_on_summarization
  • 配置/状态来源: self._pending_subagent_refs; self._pending_handoff_prompt; self._linear_history; self._trajectory_steps
  • 结果去向: reg-trajectory-steps; 当前 trial 的 trajectory 持久化内容; 后续 stage-4.9 / stage-5 的 trajectory dump; linear-history 模式下的 continuation trajectory 切分结果
寄存器交互
Terminus2._split_trajectory_on_summarizationterminus_2.py:1884–1909 ↗

线性历史摘要后的轨迹切分器

stage 上下文: 该函数服务于 stage-4.6 在线性历史模式下处理 pending handoff prompt 的分支。与同 stage 中直接把 handoff prompt 追加为 user 步骤的非线性路径不同,它不新增当前步,而是把摘要边界之前的轨迹封存,并为后续 continuation 重新建立内存中的轨迹。它由 _run_agent_loop 在消费 reg-pending-handoff-prompt 时触发,是该 stage 中“切分而非追加”的具体执行点。

这段代码在干什么

该函数在摘要发生后,把当前已记录的 trajectory 按 continuation 边界切开:先将当前段落持久化,再派生新的 self._session_id,最后依据现有 self._chat.messages 重建新的 self._trajectory_steps。输入参数 handoff_prompt 在签名中出现,但本函数体内并未实际使用;真正依赖的是 self._summarization_countself._session_id 以及 self._chat 的消息历史。它的产出完全体现为状态变更与落盘副作用,而不是返回值。

接口 · 参数 / IO

(self, handoff_prompt: str) -> None

  • 参数: handoff_prompt: str — 来自 stage-4.6 的交接提示词;此函数签名接收但函数体内未直接使用
  • 读状态: self._summarization_count, self._session_id, self._chat, self._trajectory_steps
  • 返回: 返回 None;真正产出是当前 trajectory 分段落盘、self._session_id 更新,以及 self._trajectory_steps 被替换为带 copied 标记的上下文步骤。
  • 副作用: 调用 _dump_trajectory_with_continuation_index(...) 持久化当前轨迹段; 写入新的 self._session_id; 在 self._chat 存在时重置 self._trajectory_steps

执行流

  1. 先以 self._summarization_count - 1 作为 continuation index,调用 _dump_trajectory_with_continuation_index(...),把摘要边界之前的当前 trajectory 段写出到对应文件。
  2. 随后基于现有 self._session_id 生成 continuation 版本:先取 -cont- 之前的基础 session id,再拼接 -cont-{self._summarization_count},并回写到 self._session_id
  3. 接着检查 if self._chat:;只有在聊天对象存在时,才会从聊天历史重建新的内存轨迹。
  4. 重建时使用 self._chat.messages[:-1] 而不是全部消息,将除最后一条响应外的历史转换为步骤,并通过 mark_as_copied=True 将其标记为复制上下文,然后整体替换 self._trajectory_steps

源码

    def _split_trajectory_on_summarization(self, handoff_prompt: str) -> None:
        """Split trajectory on summarization when linear_history is enabled.

        Saves current trajectory segment and resets for continuation with full linear history.

        Args:
            handoff_prompt: The handoff prompt containing answers
        """
        # Save current trajectory segment before creating a continuation
        # When _summarization_count is 1, dump to trajectory.json (continuation_index = 0)
        # When _summarization_count is 2, dump to trajectory.cont-1.json (continuation_index = 1)
        self._dump_trajectory_with_continuation_index(self._summarization_count - 1)

        # Create new session_id for continuation
        self._session_id = (
            f"{self._session_id.split('-cont-')[0]}-cont-{self._summarization_count}"
        )

        # After dumping the trajectory till the summarization stage, reset trajectory by
        # converting from chat messages (excluding the last response which will be added
        # by the normal agent loop flow). Mark all these steps as copied context since they
        # were already present in the previous trajectory segment.
        if self._chat:
            self._trajectory_steps = self._convert_chat_messages_to_steps(
                self._chat.messages[:-1], mark_as_copied=True
            )

Non-obvious 设计决策

  • continuation 文件编号故意使用 self._summarization_count - 1,使第一次摘要仍落到主 trajectory.json,后续才进入 cont-N 命名;这体现的是“先封存当前段,再为下一段编号”的边界定义,而不是把当前摘要次数直接映射到当前落盘文件。
  • session_id 生成时先做 split('-cont-')[0],说明 continuation 命名总是回到基础 session id 再追加当前摘要次数,避免在已有 -cont- 后继续级联嵌套,保持续段标识稳定且可预测。
  • 重建 self._trajectory_steps 时显式排除最后一条 chat message(self._chat.messages[:-1]),因为该响应会由正常 agent loop 流程另行追加;若此处一并纳入,会导致续段中出现重复记录。
  • 复制历史步骤时使用 mark_as_copied=True,明确区分“从前一段继承的上下文”与“本续段新产生的步骤”;这不是功能正确性所必需的最低实现,但有助于后续轨迹解释与审计。

上下游关系

  • 调用方: Terminus2._run_agent_loop 在线性历史且存在 pending handoff prompt 时调用
  • 核心被调用: Terminus2._dump_trajectory_with_continuation_index; Terminus2._convert_chat_messages_to_steps
  • 配置/状态来源: self._summarization_count 决定落盘 continuation index 与新 session_id 后缀; self._session_id 提供 continuation 派生的基础标识; self._chat.messages 提供续段初始上下文来源; stage-4.6self._linear_history 分支决定本函数是否被选用
  • 结果去向: 新的 trajectory 段文件写入磁盘,供后续 trial 记录与 stage-5 汇总使用; 更新后的 self._session_id 影响后续 continuation 轨迹命名; 替换后的 self._trajectory_steps 成为后续 agent loop 继续追加步骤的基础; 与兄弟路径相比,它避免在当前 live trajectory 中直接追加 Step(source="user", message=handoff_prompt)
  • 同类 sibling: Terminus2._run_agent_loop:该兄弟函数负责在 stage-4.6 判定走“切分轨迹”还是“直接追加 user 步骤”的分支;本函数实现前者的具体状态迁移。
寄存器交互