Terminus 2 手册

主动摘要探测

stage-4.2Proactive Summarize Probe2 个函数

开场解释

这个 Stage 在解决一个很实际的问题:别等上下文快爆了才补救,而是提前看一眼,要不要现在就做摘要。 它是一次“预检”。位置上,它在真正问 LLM(大语言模型)之前,先判断当前对话离上下文上限还有多少余量。若空间已经很紧,它就先走一遍摘要旁路,把后续要交接的摘要材料先准备好;若还够用,就什么都不改,直接进入下一步。它的价值不是推进对话本身,而是提前控风险,避免后面因为上下文太长而被动收缩。

主流程

  1. 先看要不要进这个 Stage。

    只有两个条件都满足才会跑:

    • original_instruction(最初的用户任务,还保留着)
    • 开了 self._enable_summarize(允许摘要)

    少一个都不做,立刻落到 4.3。 这说明它不是每轮必跑,而是一个有条件的“容量探针”。

  2. 如果要跑,就先做一次容量判断。

    _check_proactive_summarization()(检查“现在是否该主动摘要”)会看:

    • 当前上下文上限 context_limit
    • 已经用了多少 token(给 LLM 的文本切分单位)

    本质上是在问:“剩余空间还安全吗?” 这里它自己就能做两种结论:

    • 不用摘要:余量还够,返回空结果
    • 现在就摘要:余量低于阈值,立刻走完整摘要旁路
  3. 如果真的触发摘要,就先把“交接材料”暂存起来。

    摘要旁路会产出两类东西:

    • handoff_prompt:后面要写进轨迹的“交接提示词”
    • subagent_refs:摘要里提到的 subagent(被主 Agent 调用的小 Agent)引用

    这个 Stage 只把它们放进 pending 寄存器里暂存,还不会立刻改动主轨迹。 这么设计是为了让本轮后面的 4.5 / 4.6 按统一位置去落盘、记账、切分历史。 也就是说,它现在只是“先备好材料”,不是“当场改历史”。

  4. 不管有没有摘要,这一轮通常都会继续往前走。
    • 没触发摘要:直接去 4.3 问 LLM
    • 触发了摘要:把交接材料暂存后,继续本轮后续 Stage;真正写入轨迹要等 4.5 / 4.6

    所以它的成功效果很明确:要么什么都不变,要么只准备待提交的摘要交接物;它不直接改当前对话状态。

状态流动

分支结果要点:

  • 门条件不满足:本 Stage 不做检查,立即落到 4.3。
  • 门条件满足并运行检查_check_proactive_summarization() 会自己决定“无需摘要”或“立刻摘要”。
  • 无需摘要:不写任何 pending 数据,继续到 4.3。
  • 立刻摘要:把交接材料写入 pending 寄存器,但此时仍只是暂存,还没有提交进主轨迹

与前后 Stage 的衔接

4.1 刚完成本轮入口判断,把循环带到这里;这个 Stage 接着做的是“在真正发问前,先看上下文还撑不撑得住”。它产出的不是给 4.3 直接消费的答案内容,而是给 4.5 / 4.6 之后统一落轨迹 的待提交摘要材料;若什么都没产出,4.3 就像平常一样继续问 LLM。

函数细节2

Terminus2._run_agent_loopterminus_2.py:1283–1294 ↗

主循环中的主动摘要探测段

stage 上下文: 该片段位于 stage-4.2,承担“是否提前触发摘要交接”的门控职责。只有 original_instruction 存在且 self._enable_summarize 为真时才进入;它本身不落轨迹,而是把 _check_proactive_summarization(...) 的结果暂存到 pending 状态,交由后续 stage-4.5 / 4.6 统一记入 trajectory。由于这是本 stage 当前唯一已翻译单元,它也构成该 stage 与 side-S1 摘要支流的直接连接点。

这段代码在干什么

这段代码在 agent loop 中可选地执行一次“主动摘要”探测:以当前 chat、初始用户指令 original_instructionself._session 为输入,调用 _check_proactive_summarization(...)。若该调用返回非空结果,则将返回的 promptsubagent_refs 分别写入 self._pending_handoff_promptself._pending_subagent_refs。真正的产出不是立即修改轨迹,而是为当前迭代后续阶段延迟提供 handoff 用户消息与摘要子代理引用。

接口 · 参数 / IO

(self, original_instruction, chat) -> None

  • 参数: self: ? — Terminus2 实例,提供摘要开关、会话对象与 pending 状态寄存; original_instruction: ? — 原始用户任务指令;作为主动摘要探测的前置条件与输入之一; chat: ? — 当前主对话对象;传入 _check_proactive_summarization(...) 进行上下文容量判断
  • 读状态: self._enable_summarize, self._session
  • 返回: 无显式返回;真正产出是按条件写入 pending handoff / subagent 引用,供本次迭代后续 stage 消费。
  • 副作用: 调用 self._check_proactive_summarization(chat, original_instruction, self._session); 写入 self._pending_subagent_refs; 写入 self._pending_handoff_prompt

执行流

  1. 先同时检查 original_instructionself._enable_summarize;只有两者都满足时,才进入主动摘要探测分支。
  2. 以当前对话 chat、原始指令 original_instruction 和会话 self._session 调用 await self._check_proactive_summarization(...),得到 proactive_summary_result
  3. 对返回值做真值判断;只有结果非空时,才将其解包为 prompt, subagent_refs
  4. subagent_refs 暂存到 self._pending_subagent_refs,用于稍后补写一条 system 级轨迹步骤。
  5. prompt 暂存到 self._pending_handoff_prompt,用于稍后作为用户侧 handoff 消息进入当前迭代的轨迹处理。

源码

            if original_instruction and self._enable_summarize:
                proactive_summary_result = await self._check_proactive_summarization(
                    chat,
                    original_instruction,
                    self._session,
                )
                if proactive_summary_result:
                    prompt, subagent_refs = proactive_summary_result
                    # Store subagent_refs to add a system step later
                    self._pending_subagent_refs = subagent_refs
                    # Also store the handoff prompt to add as a user step
                    self._pending_handoff_prompt = prompt

Non-obvious 设计决策

  • 该段以双条件门控(original_instructionself._enable_summarize)限制主动摘要探测范围,避免在无原始任务语义或功能开关关闭时进入额外的摘要判定路径。
  • 代码对 _check_proactive_summarization(...) 的结果采用显式“仅在非空时写入 pending 状态”的策略,而不是无条件覆盖;这保留了“未触发摘要”这一状态的空值语义,避免后续 stage 将空摘要误当成一次真实 handoff。
  • 这里不直接把结果写入 trajectory,而是先写入 self._pending_*;这种延迟提交把“是否触发摘要”与“如何在轨迹中落盘”分离,使 stage-4.5 / 4.6 能用统一路径处理主动与回退两类摘要触发来源。

上下游关系

  • 调用方: Terminus2._run_agent_loop
  • 核心被调用: Terminus2._check_proactive_summarization
  • 配置/状态来源: self._enable_summarize; self._session; original_instruction; chat
  • 结果去向: self._pending_handoff_prompt; self._pending_subagent_refs; stage-4.5 对 subagent refs 的轨迹记录; stage-4.6 对 handoff prompt 的轨迹记录或历史切分处理
寄存器交互
Terminus2._check_proactive_summarizationterminus_2.py:956–980 ↗

按上下文余量判定是否触发主动摘要

stage 上下文: 该函数是 stage-4.2 中实际执行“主动摘要探测”的判定点。它不直接修改 Terminus2 的挂起寄存器,而是根据当前 chat 的估算 token 余量决定是否调用 _summarize(...),并把结果作为返回值交给上层。与同 stage 已翻译的 _run_agent_loop 相比,前者负责接收并保存结果,本函数只负责判定、调用与容错。

这段代码在干什么

函数读取模型上下文上限与当前 chat 的总 token 估算值,计算 free_tokens,并与 self._proactive_summarization_threshold 比较。若余量低于阈值,则记录一条 debug 日志并异步调用 _summarize(chat, original_instruction, session);成功时返回 (summary_prompt, subagent_trajectory_refs),失败时记录 error 日志后返回 None。若余量未低于阈值,也直接返回 None

接口 · 参数 / IO

(self, chat: Chat, original_instruction: str, session: TmuxSession) -> tuple[str, list[SubagentTrajectoryRef] | None] | None

  • 参数: chat: Chat — 当前对话历史;用于估算总 token,并在需要时传给 _summarize(...); original_instruction: str — 原始指令文本;仅在触发摘要时转交给 _summarize(...); session: TmuxSession — tmux 会话对象;仅在触发摘要时转交给 _summarize(...)
  • 读状态: self._llm, self._proactive_summarization_threshold, self.logger
  • 返回: 当 free_tokens < self._proactive_summarization_threshold_summarize(...) 成功时,返回 (summary_prompt, subagent_trajectory_refs);其余情况返回 None
  • 副作用: 调用 self._llm.get_model_context_limit() 读取模型上下文上限; 调用 self._count_total_tokens(chat) 估算当前对话 token 总量; 在阈值触发时写入一条 debug 日志; 在阈值触发时异步调用 self._summarize(chat, original_instruction, session); 若摘要过程抛出异常,写入一条 error 日志

执行流

  1. 先通过 self._llm.get_model_context_limit() 取得 context_limit,再调用 self._count_total_tokens(chat) 得到 current_tokens,据此计算 free_tokens = context_limit - current_tokens
  2. free_tokensself._proactive_summarization_threshold 比较;只有在余量低于阈值时,才进入主动摘要分支。
  3. 进入摘要分支后,先用 self.logger.debug(...) 记录一条包含 free_tokens 的调试日志,说明即将执行主动摘要。
  4. 随后在 try 中等待 self._summarize(chat, original_instruction, session) 完成;若成功,解包得到 summary_promptsubagent_trajectory_refs,并立即以二元组形式返回。
  5. 如果 _summarize(...) 抛出任意异常,则由 except Exception as e 捕获,并通过 self.logger.error(...) 记录错误信息。
  6. 当未触发阈值,或触发后摘要失败时,函数都走到结尾并返回 None

源码

    async def _check_proactive_summarization(
        self, chat: Chat, original_instruction: str, session: TmuxSession
    ) -> tuple[str, list[SubagentTrajectoryRef] | None] | None:
        """Check if we should proactively summarize due to token usage.

        Returns:
            tuple: (summary_prompt, subagent_trajectory_ref) if summarization occurred, None otherwise
        """
        context_limit = self._llm.get_model_context_limit()
        current_tokens = self._count_total_tokens(chat)
        free_tokens = context_limit - current_tokens

        if free_tokens < self._proactive_summarization_threshold:
            self.logger.debug(
                f"Proactively summarizing. Free tokens: approximately {free_tokens}"
            )
            try:
                summary_prompt, subagent_trajectory_refs = await self._summarize(
                    chat, original_instruction, session
                )
                return (summary_prompt, subagent_trajectory_refs)
            except Exception as e:
                self.logger.error(f"Error in proactively summarizing: {e}")

        return None

Non-obvious 设计决策

  • 触发条件使用“剩余上下文余量”free_tokens < self._proactive_summarization_threshold,而不是等待真正超限;这是代码中显式采用的前置判定策略,使摘要在接近容量边界时就可发生。
  • 摘要调用被包裹在 try/except Exception 中,且异常只记录日志、不再向外抛出;这表明该函数将主动摘要视为可失败的辅助路径,而不是必须成功的硬性步骤。
  • 返回值设计为“摘要结果二元组或 None”的可空协议,而不是在函数内写入某个 self._pending_* 字段;因此本函数的职责被限定为判定与产出结果,不在本地完成持久化。

上下游关系

  • 调用方: Terminus2._run_agent_loop
  • 核心被调用: self._llm.get_model_context_limit; self._count_total_tokens; self._summarize; self.logger.debug; self.logger.error
  • 配置/状态来源: self._proactive_summarization_threshold; self._llm
  • 结果去向: 返回给上层调用者的 (summary_prompt, subagent_trajectory_refs); 返回给上层调用者的 None
  • 同类 sibling: Terminus2._run_agent_loop:兄弟函数负责接收本函数返回值并进一步处理;本函数自身只做阈值判定、日志记录与 _summarize(...) 调用。