雨天小六

读懂 Codex(3.7):Task trait/策略怎样包装 Regular、Review、Compact 和 UserShell

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

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

SessionTask 是一次 Turn 的执行策略,不是 Tokio Task 本身。真正的 Tokio Task 由 Session 统一创建;Regular、Review、Compact 和 UserShell 只提供“怎样运行、怎样在取消后做专用收尾”。这层间接性让终态、统计、Rollout 和 ActiveTurn 清理集中在同一处。

两个 Task 概念必须分开

名称含义
SessionTaskCodex 业务策略 trait
AnySessionTask把 trait 的 async 返回装箱后用于动态分派
Tokio tasktokio::spawn 创建的实际异步执行单元
RunningTask保存策略对象、Tokio handle、Token 和 TurnContext 的运行记录
SessionTask 策略经 AnySessionTask 适配后由 Tokio task 执行
图 3.7-1:业务 Task 描述工作方式,Session 负责把它放入统一 Tokio 生命周期;`RunningTask` 才是两层之间的所有权桥。

SessionTask 的接口刻意很小:kind() 用于状态、Steer 和遥测,span_name() 用于 trace,run() 返回可选最终消息或错误,abort() 提供取消后的专用清理。AnySessionTask 用 boxed future 消除泛型,使 RunningTask 可以保存 Arc<dyn AnySessionTask>

class SessionTask(Protocol):
    def kind(self) -> TaskKind: ...
    def span_name(self) -> str: ...
    async def run(session, turn, input, cancel) -> Optional[str]: ...
    async def abort(session, turn) -> None: pass

async def start_task(task, turn, input):
    cancel = CancellationToken()
    done = Notify()
    handle = tokio_spawn(run_and_finish(task, turn, input, cancel, done))
    active_turn.task = RunningTask(task, handle, cancel, turn, done)

四类工作怎样落在这个壳里

RegularTask 的 kind 是 Regular,真正运行多轮 run_turn。CompactTask 根据 feature/provider 选择 token-budget、本地、remote 或 remote-v2 压缩。ReviewTask 启动受限制的一次性子 Codex,过滤和重投影事件,并在正常或 abort 时退出 Review Mode。

UserShell 值得单独说明:它有 SessionTask 实现和统一终态外壳,但在 TaskKind 层归入 Regular,而不是新增 UserShell 枚举。它不经过模型采样,直接发命令项事件、执行用户命令并持久化输出。这说明 TaskKind 是客户端/生命周期需要的粗分类,不是每个实现类型的一一枚举。

Regular、Review、Compact 和 UserShell 共享 Task 外壳但采用不同执行策略
图 3.7-2:四种策略共享 start/abort/finish 外壳;差异留在 `run` 和可选 `abort`,其中 UserShell 在分类上仍属于 Regular。

spawn_task 与 start_task 的区别

spawn_task 是“替换式启动”:先以 Replaced 原因 abort 旧任务、清连接器选择,再调用 start_task。start_task 假定调用者已经满足单主任务条件,负责记录起始时间和起始 Token、吸收 mailbox 输入、发 turn-start lifecycle、创建 CancellationToken 与 Tokio handle,并把 RunningTask 写入 ActiveTurn。

async def spawn_task(new_task):
    await abort_all_tasks(reason=REPLACED)
    await clear_connector_selection()
    await start_task(new_task)

async def run_and_finish(task, ctx, cancel, done):
    result = await task.run(ctx, cancel.child_token())
    await session.flush_rollout()
    if not cancel.is_cancelled():
        await session.on_task_finished(ctx, result)
    done.notify_waiters()

先 flush 再进入统一完成处理,保证普通 items 在终态计算前尽量落盘。取消者若已经拿走 ActiveTurn 并取消 token,后台 task 不再自己发终态,避免与 abort 路径竞争。

为什么 Review 需要覆盖 abort

多数 Task 的默认 abort 是空操作,因为取消 token 和中止 handle 已足够。ReviewTask 还向父 UI 投影 EnteredReviewMode;若中断时不发退出语义,界面会永久留在审查模式。因此它覆写 abort,调用 exit_review_mode(..., None)

评论


← 返回文章列表