雨天小六

读懂 Codex(3.12):`run_turn` 的准备阶段、采样阶段和收尾阶段

· 更新于 2026-08-02 · 专栏:读懂 Codex

#Codex#Agent Runtime#生命周期#软件架构

run_turn 不是简单的 while-model-call。一次调用先建立模型可见世界,再执行零到多次采样,最后把 last agent message 或错误交回 Task wrapper。持久化终态和清 ActiveTurn 不属于它,而属于更外层的统一任务生命周期。

三阶段边界

run_turn 的准备、循环采样和返回阶段
图 3.12-1:准备阶段只执行一次,Step 捕获和采样循环可重复;TurnComplete 在 `run_turn` 返回之后由 Session 发送。

准备阶段按顺序完成:预采样 compact;从用户输入解析 required MCP servers 和 plugin mentions;捕获 first StepContext;记录 world-state/reference context;建立 skill/plugin injections;执行 session-start 和 input hooks;记录原始输入及 injection;更新 connector selection、PreviousTurnSettings 与 analytics。

这些动作有严格先后。首 Step 必须先于 world-state 记录,因为后者要保存模型真正看到的环境和工具上下文;输入 hooks 必须先于采样,因为它们可能要求停止或补充上下文;PreviousTurnSettings 在历史记录后用于下一轮差异注入。

async def run_turn(session, turn, input, cancel):
    await maybe_pre_sampling_compact(turn, cancel)
    required, plugins = await resolve_mentions(input, cancel)
    first_step = await capture_step(turn, required, cancel)
    world = await record_reference_world_state(first_step)
    injections = await build_skills_plugins(first_step, input, plugins, cancel)

    if await run_session_start_hooks(turn): return None
    if await hooks_and_record_inputs(turn, input): return None
    await record_injections_and_resolved_settings(injections)

    return await sampling_loop(turn, first_step, world, cancel)

循环阶段每次做什么

循环在允许时排空 pending input并先运行 hooks;记录 rollout/token/time reminder;取得 first 或重新捕获 StepContext;若世界状态变化则追加 diff;从 ContextManager 克隆并按模型输入模态编译历史;构造 metadata;进入 run_sampling_request

采样函数内部再分两层:外层负责 retryable stream error,复用同一个 ModelClientSession;内层消费 Responses stream、投影增量、调度工具并等到 response.completed

run_turn、run_sampling_request 与任务 wrapper 的职责边界
图 3.12-2:`run_turn` 决定是否再采样,`run_sampling_request` 管一次请求及其重试,Task wrapper 管 Rollout flush 和公开终态。
async def sampling_loop(turn, first_step, world, cancel):
    next_step = first_step
    while True:
        pending = await drain_if_allowed()
        await hooks_and_record_inputs(turn, pending)
        step = next_step or await capture_step(turn, required_by(pending), cancel)
        world = await record_world_diff_if_changed(world, step)
        prompt_input = history.for_prompt(turn.model.input_modalities)

        result = await run_sampling_request(step, prompt_input, cancel.child())
        if await should_compact_and_continue(result):
            next_step = await compact_and_prepare_continuation(step)
            continue
        if not await needs_follow_up(result):
            return result.last_agent_message

错误收尾不是一个动作

预采样 compact 的普通失败会发 turn-error lifecycle 并返回 None;TurnAborted 原样向外传播。required server 或 Step 捕获若被取消,会先确保输入 hooks/记录符合约定再返回。采样期 InvalidImageRequest 映射成 BadRequest 和用户可理解错误;其他 CodexErr 发 Error 事件并结束本次 run_turn,允许 Thread 继续接收下一轮。

run_turn 最终只返回 Option<String>。它不发送 TurnComplete、不计算整轮 Token delta、不清 ActiveTurn。Task wrapper 先 flush,on_task_finished 再执行这些公共动作。这样 Compact/Review/UserShell 也能复用相同终态规则。

评论


← 返回文章列表