雨天小六

读懂 Codex(10.2):Turn 稳定快照与 Step 动态快照

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

#Codex#Agent Runtime#软件架构#系统设计

具体问题与边界

一次 Turn 可能经历“模型请求工具—工具修改仓库—再次采样”。如果所有状态都在 Turn 开头冻结, 第二次采样看不到刚发生的变化;如果所有状态都随时读取全局最新值,同一个工具调用又可能被不同 Schema、权限或工作目录解释。Codex 因此把配置按稳定期分到 TurnContext 与 StepContext。

本节讨论时间一致性,不把某个具体字段当前归属包装成永恒规则。

状态所有权和刷新规则

状态典型生命周期为什么放在这一层何时刷新
model、cwd、审批/权限基线Turn构成用户本次请求的执行承诺新 Turn 或明确权限响应
ToolRouter/RegistryStep必须和本次模型所见 ToolSpec 同快照每次新采样前
MCP binding/revisionStep目录可刷新,但旧 Call 不能漂移下一 Step
World StateStep 比较工具可能改变文件与环境下一 Step 重捕获
CancellationTokenTurn取消整次任务链Turn 终态销毁
stream retry state单次请求尝试Retry 不能偷偷换工具合同请求结束或新 Step

正常路径

TurnContext 在多次 Step 之间稳定而 StepContext 在每次采样前重新捕获的时序图
图 10.2-1:Turn 固定用户承诺;每次采样前重新捕获工具、MCP 与 World State,并让工具 Future 保留原 Step。
  1. Session 根据用户输入与线程设置创建 TurnContext,确定模型、cwd、权限基线和本次取消范围。
  2. 第一次采样前捕获 StepContext:解析 required MCP、冻结 binding,并由同一计划构造 ToolSpec/Router。
  3. 模型返回 ToolCall;执行 Future 持有产生该 Call 的 StepContext,而不是回头查询全局最新 Registry。
  4. 工具修改文件或 MCP 目录刷新后,旧 Step 不被原地修改。
  5. 只有进入下一次模型采样,Runtime 才重新捕获 StepContext,并把 World State 变化编译进请求。

核心是“旧请求保持自洽,新请求看见新世界”。这比笼统的 immutable/mutable 二分更准确: StepContext 自身也是冻结快照,但下一 Step 会得到新实例。

Python 风格伪代码

@dataclass(frozen=True)
class TurnSnapshot:
    turn_id: str
    model: str
    cwd: Path
    permissions: PermissionProfile
    cancellation: CancellationToken

@dataclass(frozen=True)
class StepSnapshot:
    tool_plan: ToolPlan
    mcp_revision: int
    world_state: WorldState

async def run_turn(turn: TurnSnapshot) -> Terminal:
    previous_world: WorldState | None = None
    while not turn.cancellation.cancelled:
        step = await capture_step(turn, previous_world)
        response = await sample(compile_prompt(turn, step), step.tool_plan.specs)
        if response.tool_calls:
            await execute_calls(response.tool_calls, step.tool_plan.router, turn.cancellation)
            previous_world = step.world_state
            continue
        return Completed(response.message)
    return Aborted()

execute_calls 显式接收旧 Step 的 Router;这是防止目录刷新后“旧参数交给新实现”的关键,不是为了 方便测试而注入的可选对象。

失败与并发边界

错误的全局最新状态读取导致旧工具调用漂移的失败路径图
图 10.2-2:全局热更新可以服务下一 Step,但不能重新解释已经由旧 Schema 产生的 ToolCall。
失败设计竞态结果正确边界
Turn 开头捕获全部状态Patch 后模型仍看到旧 World State动态状态按 Step 重捕获
工具执行时查询最新 Registry旧 Call 被新 Handler/Schema 解释Future 持有原 Step Router
Stream retry 重建 Step同一次请求重试时工具面改变Retry 复用请求快照
每个函数直接读全局 cwd/权限同一 Turn 的副作用范围漂移从 TurnContext 传入
MCP refresh 原地修改 binding并发 Call 观察到部分更新新 revision 原子替换,旧引用继续有效

设计判断

决定状态放在哪一层的标准不是“变化快不快”,而是变化能否在当前协议单元中安全可见。模型、工具 Schema 和执行 Handler 构成一次采样的因果闭包;World State 只在下一次采样时需要更新。

双快照增加构造与传参成本,也会保留短期旧对象。换来的收益是可重放、可测试和无锁读取。对没有 动态工具、环境刷新或多次采样的简单 Agent,一个请求级快照即可,不必照搬两层结构。

证据与 Mini Codex

生产锚点为 session/turn_context.rssession/step_context.rssession/turn.rstools/spec_plan.rs。3.9、3.10、4.12 和 6.10 已验证字段生命周期与 MCP 刷新行为。

Mini Codex 的 WorldState/Patch 与 Registry 热更新测试验证“下一 Step 看新状态、旧 ToolPlan 不漂移”:

cd examples/mini-codex
uv run pytest -q -k 'world_state or registry_snapshot'

Mini 没有生产级 required-server 初始化、Provider capability 和远程执行环境,因此不证明字段集合完整。

本节边界

已经证明:稳定期必须与因果作用域对齐。下一节把同样的时间一致性应用到 Prompt 编译与缓存前缀。 详细源码映射由研究仓库中的配套索引维护。

阅读导航

上一节:10.1 · 下一节:10.3

评论


← 返回文章列表