Agent Harness 学习
← 返回对照矩阵

interrupt & resume

循环

中断与恢复

被打断之后,从哪里、凭什么接着跑?

横向读这一行

分歧在「恢复的最小粒度是什么」:openJiuwen 对齐到 ReAct 迭代边界,恢复即从记下的迭代号加一继续;Pi 对齐到消息角色,只要末尾能转成 user 或 toolResult 就能续跑;DeepSeek 干脆没有恢复逻辑,重放事件日志本身就是恢复。粒度越粗越容易保证一致性,越细越省重复工作——三家分别站在这条线的不同位置。

对比
openJiuwen带代码

中断状态记住迭代号,从下一轮继续

恢复的粒度是 ReAct 迭代。中断状态里存着被打断时的 iteration,恢复时先给挂起的工作流补记反馈,等所有工作流都跑完,再把 resume_iteration + 1 写进 ctx.extra 的 RESUME_START_ITERATION_KEY——主循环的 for range 正是从这个值起步(见「主循环形态」一行)。也就是说中断点被对齐到迭代边界,不会从半轮中间接续。

resume_iteration = interruption_state.iteration
logger.info(f"Resuming ReAct from iteration {resume_iteration + 1}")

# Step 1: record feedback for the current pending workflow
pending_wf_id = interruption_state.pending_workflow_id
pending_comp_id = interruption_state.pending_component_id

# ...(1760-1800 行:补记反馈、重放已完成工作流、检查是否再次中断)

# All workflows completed — continue ReAct loop from next iteration
ctx.extra[RESUME_START_ITERATION_KEY] = resume_iteration + 1
openjiuwen/core/single_agent/agents/react_agent.py:1757-1802@ fd6c47854201
Codex仅摘要

rollout 回放 + 线程存储

回放记录(rollout)、线程存储(thread-store)与状态(state)分成三个独立 crate;记忆流水线也从状态库里领取历史 rollout 作为输入。尚未做逐行走读。

重放事件日志即恢复

会话事件日志是唯一事实来源,恢复就是重放。沙箱模式那一格的源码注释把这条讲得最直白:override survives restart by replay,不需要任何追赶机制。尚未对恢复路径本身做逐行走读。

Pi带代码

从当前上下文续跑,用两条断言守住前置条件

恢复不是特殊模式,而是一个不追加新消息、直接从现有上下文继续的入口,主要用于重试。它只守两条前置条件:上下文不能为空,最后一条消息不能是 assistant——因为下一次请求要求末尾能转成 user 或 toolResult,否则厂商会直接拒。注释诚实地写明这一点无法在此处校验,因为 convertToLlm 每轮只调用一次。会话本身以 append-only 的 JSONL 落盘,恢复时从最近一次压缩条目开始重建上下文。

/**
 * Continue an agent loop from the current context without adding a new message.
 * Used for retries - context already has user message or tool results.
 *
 * **Important:** The last message in context must convert to a `user` or `toolResult` message
 * via `convertToLlm`. If it doesn't, the LLM provider will reject the request.
 * This cannot be validated here since `convertToLlm` is only called once per turn.
 */
export function agentLoopContinue(
	context: AgentContext,
	config: AgentLoopConfig,
	signal: AbortSignal | undefined,
	streamFn: StreamFn,
): EventStream<AgentEvent, AgentMessage[]> {
	if (context.messages.length === 0) {
		throw new Error("Cannot continue: no messages in context");
	}

	if (context.messages[context.messages.length - 1].role === "assistant") {
		throw new Error("Cannot continue from message role: assistant");
	}
packages/agent/src/agent-loop.ts:56-76@ bfb004d4418f

back to course / 回到课程

矩阵展示的是「各家怎么做」。这个问题本身为什么存在、有哪些经典权衡,在课程里讲:

2

Agent 循环

收集上下文 → 行动 → 检查 → 重复,直到满足停止条件