待交接提示 → 用户步骤(或切分)
开场解释
这个 Stage 在解决什么问题:把“该不该把总结交接词真正写进对话记录”这件事,放到一个统一位置收口。前面阶段可能已经产出了 handoff prompt(交接提示词:让后续上下文接着当前总结继续工作的那段用户话),但此时还不能立刻落盘,因为要先看当前是线性历史模式,还是普通模式。这个 Stage 就是在响应已解析完之后,决定这段提示词该变成一个新的 user step(用户消息步),还是改成一次轨迹切分。
主流程
- 先看有没有待处理的 handoff prompt。
这个值放在
reg-pending-handoff-prompt。如果这里是空,说明这轮没有要交接的内容,这个 Stage 就什么都不做,直接往下走。 - 如果有,就看是不是线性历史模式。
self._linear_history可以理解成一种“历史只能沿一条线往前写”的模式。这里分支的原因很重要:- 在线性模式里,不能简单把 handoff prompt 追加成一条新 user step,否则会把“总结前”和“总结后”的历史混在同一条轨迹里。
- 在普通模式里,直接把它当成一条新的用户消息接上去就够了。
- 线性模式:调用
_split_trajectory_on_summarization()(按总结边界把当前轨迹切开,并开始一段新的延续轨迹)。这里的重点不是“再加一条消息”,而是“换一段历史”。这样做的意义是:总结前的长轨迹到这里收口;总结后的新轨迹从更干净的上下文继续。handoff prompt 在新轨迹里是隐含生效的,不需要再显式写成一步。
- 非线性模式:把 handoff prompt 追加到当前 live trajectory(当前正在增长的轨迹记录)里,来源记为
user。这相当于明确告诉后面的 Agent:“现在把这段交接词当作用户刚说的话,按它继续。”
- 只有在真正消费了这个 handoff prompt 之后,才清掉寄存器。
这样可以保证:前面阶段先“提出一个待处理交接”,这里再“按当前历史策略落地”。之所以要延后到这个 Stage 才写,是因为前面产出 prompt 时,还不知道最终应该“加一步”还是“切轨迹”。
状态流动
- 读:
reg-pending-handoff-prompt— 进入本 Stage 时先检查;若为None,表示没有待处理交接,本 Stage 走空操作路径,不写入轨迹,也不清这个寄存器以外的任何状态(它本来就为空)。 - 读:
reg-pending-handoff-prompt— 若非None,把其中的 handoff prompt 当作本 Stage 要消费的输入;后续要么写成 user step,要么用于切分轨迹。 - 读:
self._linear_history(历史模式标志,不是 register)— 用来决定消费方式:线性模式走“切轨迹”,非线性模式走“追加 user step”。这里是控制所依赖的状态,但不是寄存器。 - 写: live trajectory(当前轨迹容器,不是 register)— 仅在非线性模式下,追加
Step(source="user", message=handoff_prompt);目的是把交接提示词显式变成后续可见的一步用户消息。 - 写: trajectory files / continuation trajectory(轨迹文件与新的延续轨迹,不是 register)— 仅在线性模式下,由
_split_trajectory_on_summarization()按总结边界切开当前轨迹,并开始新的 continuation;目的是让总结后的历史从新段继续,而不是把 handoff prompt 直接塞进旧段。 - 清:
reg-pending-handoff-prompt— 仅在 handoff prompt 被真正消费之后清空;不论消费方式是“追加 user step”还是“切轨迹”,都会在完成后清掉,避免同一段交接词在后续循环里重复生效。
注:本 Stage 还依赖“live trajectory / 轨迹文件 / 历史模式”这类状态承载体,但输入材料里没有给出它们对应的 register id,所以这里明确按“非 register 状态”说明,不伪造寄存器名。
与前后 Stage 的衔接
上游在更早阶段已经把 handoff prompt 放进 reg-pending-handoff-prompt,而前一站 stage-4.4 Response Parse 则完成了本轮响应解析,让系统进入“该不该落地这段交接词”的时点。本 Stage 的产出不是新的模型响应,而是一次历史写入策略:要么往当前轨迹补一条 user step,要么在线性模式下切出一段新轨迹;做完后,后续阶段再按更新后的轨迹状态继续往下跑。
Terminus2._run_agent_loopterminus_2.py:1318–1354 ↗
汇总并落地交接提示的轨迹分支段
stage 上下文: 这段代码位于 stage-4.6,负责把前面 summarization 侧流程暂存下来的产物真正写入当前 trial 的 trajectory。触发条件是
self._pending_subagent_refs或self._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
执行流
- 若
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。 - 完成上述 system 步骤落地后,立即把
self._pending_subagent_refs设为None,避免同一批摘要引用在后续循环中被重复写入。 - 随后检查
self._pending_handoff_prompt;若该交接提示存在,则进入 handoff 处理分支。 - 当
self._linear_history为真时,不向当前self._trajectory_steps追加用户步骤,而是直接调用_split_trajectory_on_summarization(self._pending_handoff_prompt),把轨迹切分责任交给专门的 split 机制处理。 - 当
self._linear_history为假时,直接在当前轨迹中追加一条新的Step,其source="user",message为self._pending_handoff_prompt,从而把交接提示显式写入 live trajectory。 - 无论走哪条 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 切分结果
reg-pending-subagent-refs— 检查是否有待落地子代理引用reg-trajectory-steps— 追加摘要交接的 system 步骤reg-pending-subagent-refs— 写入轨迹后立即清空reg-pending-handoff-prompt— 检查是否有待消费交接提示reg-trajectory-steps— 非线性模式追加 user 交接步骤reg-pending-handoff-prompt— 切分或追加后统一清空
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_count、self._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
执行流
- 先以
self._summarization_count - 1作为 continuation index,调用_dump_trajectory_with_continuation_index(...),把摘要边界之前的当前 trajectory 段写出到对应文件。 - 随后基于现有
self._session_id生成 continuation 版本:先取-cont-之前的基础 session id,再拼接-cont-{self._summarization_count},并回写到self._session_id。 - 接着检查
if self._chat:;只有在聊天对象存在时,才会从聊天历史重建新的内存轨迹。 - 重建时使用
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.6 的
self._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 步骤”的分支;本函数实现前者的具体状态迁移。
reg-summarization-count— 决定落盘段号与续段编号reg-trajectory-steps— 重建为 copied 上下文步骤reg-chat-messages— 从现有聊天历史恢复续段上下文