雨天小六

读懂 Codex(3.20):TurnComplete、TurnAborted 与恰好一个终态

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

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

一个已登记 RunningTask 最终只能由正常完成路径或显式 abort 路径之一拥有终态。源码没有用一个单独的 terminal_sent 布尔值实现,而是通过 ActiveTurn 所有权转移、CancellationToken 和 task handle detach 构成结构性互斥。

两条终态路径

正常路径中,Task wrapper 得到 run() 结果、flush,并在根 token 未取消时调用 on_task_finished。完成函数在 ActiveTurn 锁内取走 task;若 task 已被 abort 路径取走,得到 None 并直接返回。它随后统计、选择 TurnComplete 或由返回的 TurnAborted error映射 TurnAborted,发送后按 TurnState Arc 身份清 ActiveTurn。

Abort 路径先取走整个 ActiveTurn,所以正常 wrapper 后来即使结束,也因 token cancelled 不调用 finish;即使发生竞态进入 finish,也找不到 task。abort 自己在 handle_task_abort 中发 TurnAborted。

正常完成和中断通过 ActiveTurn 所有权竞争终态
图 3.20-1:谁成功取走 RunningTask/ActiveTurn,谁拥有终态;另一条路径因为 token 或空槽位退出。
async def task_wrapper(result, token):
    await flush_rollout()
    if not token.is_cancelled():
        await on_task_finished(result)

async def on_task_finished(result):
    async with active_turn.lock() as active:
        task = active.take_task_if_present()
    if task is None:
        return                     # abort 已拥有终态
    await emit_one_terminal(map_result(result))
    await clear_active_only_if_same_turn_state()

async def abort_all():
    active = await take_entire_active_turn()
    if active and active.task:
        await handle_task_abort(active.task)  # 这里发 TurnAborted

task result 的映射

Ok(Some(message))Ok(None) 都产生 TurnComplete,只是 last_agent_message 不同。Err(CodexErr::TurnAborted) 在自然完成收口中映射成 Interrupted 的 TurnAborted。其他意外错误会 warning;采样期可报告错误通常已经写入 TurnContext.terminal_error 或发 Error event,最终仍用 TurnComplete 携带 error 字段,让客户端得到一个闭合生命周期。

这说明 Error Event 不等于终态。一次 Turn 可以先发 Error,再发 TurnComplete(error=…);客户端应等待 Complete/Aborted,而不是收到任何错误就自行关闭状态机。

Turn 从 Running 到 Complete 或 Aborted 的单终态状态机
图 3.20-2:Error 是运行事件,Complete/Aborted 才是终态;终态之后旧 Turn 不再接受 Steer 或审批答复。

Arc 身份检查防止清掉后继任务

on_task_finished 保存本轮 Arc<Mutex<TurnState>>。发送终态后再锁 ActiveTurn,只有当前槽位 task 已为 None 且 Arc::ptr_eq 仍指向这份 TurnState 才设 None。若某条边界路径已安装新的 state,旧完成函数不得清除它。

清理成功后才发 Thread Idle lifecycle、清 guardian circuit breaker并尝试启动 pending work。这样 Idle 表达“当前 ActiveTurn 确实为空”,不是“某个旧 Tokio task 刚返回”。

评论


← 返回文章列表