Skip to content
预计 1 分钟右侧目录可快速定位

一次 Run 如何被推进:Lifecycle、State Machine 与 Node Transition ​

前置:Observation;Reliability 中的 Side-effect / Retry 基础

一个 Run 有 4 个模型 Turn,其中第 2 Turn 产生 3 个 Tool Step;某 Tool Step 重试不应被误算成新的用户 Run。审批请求持久化成功后 Run 才进入 WAITING;用户批准后由 resume transition 回 RUNNING。

Mental Model ​

text
Run created → build context → model decision → validate/schedule → execute → persist observation/state → repeat/finish

任何可恢复 Runtime 都必须把 transient Python control flow 转成 durable facts:哪些节点完成、哪些副作用提交、在哪里 checkpoint、为什么取消/失败。

Run / Turn / Step Lifecycle ​

这些层级主要用于回答“某个预算、状态和错误到底属于哪一层”。

text
Run   = 一次端到端任务
Turn  = 一轮 model decision + observation
Node  = Graph 中一个可调度执行单元
Step  = Runtime 内部推进动作(项目可自定义)

例如一个 Run 可以有 5 个 Turn,而其中一个 Turn 触发 3 个并行 Node。max_turns 应限制模型循环,Node retry 应绑定具体 execution attempt;两者不能混成一个计数器。

状态也应由 Runtime 的真实事件推进,而不是模型一句“已经完成”就直接把 Run 改成 SUCCEEDED。

🔍 源码对照:Turn 开始前先构造 Runtime-owned Context ​

精确定位

ts
async function createTurnAgentRunContext(input) {
  const privacyMode = await input.inference.resolvePrivacyMode()
  const session = createProviderSession(input.modelId)
  const profileSnapshot = prepareAgentProfileSnapshot()
  const toolSession = wrapExecutorWithTurnMiddleware(session)

  return {
    privacyMode,
    session,
    toolSession,
    scope: buildTurnScope(input, profileSnapshot),
    dispose: releaseUncommittedResources,
  }
}

对照理解

  • 一次 Turn 开始前,源码把 provider session、privacy、profile snapshot、Tool executor middleware、scope 和 shell-watch wiring 一次性组成 TurnAgentRunContext。
  • 这说明 Runtime 的 Turn Context 不只是 messages;它还包含执行身份、权限/隐私、资源 owner 和 cleanup 责任。
  • dispose() 是显式生命周期边界:未 commit 的 claim/resource 必须释放。

源码说明

这一组文件适合作为 Single Turn 调用链,而不是逐文件背 API。turn-observation.ts 可以直接看到 turn-started、tool-started、tool-completed 事件和 pending/done/failed activity;与 composition/run-shell/settle 一起阅读时,重点是 Runtime 如何把“推理、执行、观察、结束判定”拆成可验证阶段。

工程边界

Grok Bot 的 Turn 定义、settle 细节和文件粒度不一定等于 Shadow Harness 的 Domain Language。我们必须自己固定 Run/Turn/Node/Attempt 语义。

Shadow Harness 对照

  • State transition 只接受 Runtime event/fact,不接受模型自述直接写状态。
  • Turn settlement 是显式 decision:final、wait、retry、continue、fail 等必须可测试。

验证

用 ScriptedModel 依次产生 tool call→observation→final;记录完整 transition trace,并故意提交一个 terminal→RUNNING 的非法 transition,StateMachine 必须拒绝。

Runtime State Machine ​

状态必须由程序事件推进。状态机描述程序事实,例如 CREATED→RUNNING→WAITING→SUCCEEDED/FAILED/CANCELLED。迁移由 Runtime 触发并验证 invariant;模型可以建议“任务完成”,但不能直接把 Run 状态写成 SUCCEEDED。

🔍 源码对照:Tool 生命周期先变成 Runtime Fact ​

精确定位

ts
function toolStarted(name) {
  lastTool = name
  recordToolActivity({ status: "pending", name })
  emit({ type: "tool-started", name, at: now() })
}

function toolCompleted(name, failed = false) {
  recordToolActivity({
    status: failed ? "failed" : "done",
    name,
  })
  emit({ type: "tool-completed", name, at: now() })
}

对照理解

  • Observation 不是“把 Tool 返回文本塞回 messages”这么简单;Runtime 先记录生命周期和状态事实,再决定哪些内容进入下一轮 Context。
  • pending → done/failed 是 Runtime 能验证的事实,和模型对结果的自然语言解释是不同层。
  • 同一个 Observation owner 还跟踪 await、MCP stall、后台任务和 TTFT,因此它更像 Turn 级的事实采集器。

Node State Transition ​

推荐 PENDING→READY→RUNNING→terminal;WAITING APPROVAL/BLOCKED 是有语义的非终态。terminal state 不应回退。

Transition 应原子记录 attempt/version,防止并发 scheduler 重复领取。

最小实现 / 数据结构 ​

python
while ready:
    batch = scheduler.take_ready(limit=max_concurrency)
    results = await executor.run_batch(batch)
    for result in results:
        state = reducer.apply(state, result)
    await checkpoint_store.save(state)

在 Shadow Harness 中怎么落地 ​

RunState/NodeState/ExecutableGraph/Scheduler/CheckpointStore 分离;状态转换和 dependency satisfaction 必须 deterministic。

Failure Modes ​

允许非法 Runtime State Transition ​

症状: 已 SUCCEEDED 的 Run 被写回 RUNNING,或未经过 WAITING 就直接 RESUME。

为什么危险: 审计和恢复失去单一事实,多 worker 可能同时拥有 Run。

处理: 显式 state machine + optimistic version/lease;transition 统一由 Runtime 提交。

如何验证 ​

Case断言
允许非法 Runtime State Transition枚举非法 transition,StateStore 必须拒绝并记录 conflict。

关联源码 ​

Codex 源码对照:Runtime Lifecycle / Turn Mapping ​

要回答的问题 ​

Codex 的 Session、active Turn、sampling loop 分别由谁拥有?如何避免把 Codex Turn 和本知识库 canonical Turn 强行画等号?

精确定位 ​

Tag: rust-v0.150.1

File:

Symbols:

  • Session
  • Session::active_turn
  • run_turn(...)
  • ActiveTurn
  • RunningTask
  • TurnState

最小调用链 ​

text
Session
→ active_turn: ActiveTurn
   → RunningTask { turn_context, cancellation_token, ... }
   → TurnState { pending approvals/input, tool_calls, ... }
→ run_turn(...)
   → sampling
   → optional tool execution
   → observation/result
   → next sampling when follow-up is needed
   → completion

对应知识库时:

text
Codex run_turn lifecycle
≈ outer Runtime operation containing N logical decision cycles

canonical Turn
= one logical Model Decision → Action/Observation cycle

Observable Fact ​

Session 持有 active_turn: Mutex<Option<ActiveTurn>>。state/turn.rs 明确把 TurnState 注释为 “Mutable state for a single turn”,其中保存 pending approvals、pending permissions、pending user input、granted permissions、tool call count 等 active-turn state。与此同时,session/turn.rs::run_turn() 内部是 sampling loop:tool call 会导致后续 sampling,而不是立即结束 Codex Turn。

这说明 Session lifetime、active Codex Turn lifetime 和 single model decision cycle 至少是三个不同粒度。

Design Inference ​

Runtime state machine 应按 owner + lifetime + transition authority 建模,而不是按外部框架的类型名照抄。

状态 transition 应由 Runtime event 驱动。例如 tool result 到达后,Runtime 决定是否需要 next sampling;模型不能通过一句“任务已完成”直接把 active execution state 写成 COMPLETED。

Codex-specific Trade-off ​

Codex rust-v0.150.1 允许一个 Session 同时最多有一个 running task,并把 pending approval/input 等挂在 ActiveTurn/TurnState 下。这是当前 concurrency / interaction model 的产品实现,不是所有 agent platform 的必然限制。

Shadow Harness Delta ​

  1. 明确 RunState 是 outer execution owner,canonical TurnState 只表示单次 logical decision cycle;需要时另设 ActiveRunControl 承载 wait/cancel/pending interaction。
  2. 不把 Codex TurnState 名字直接映射为 Shadow TurnState;逐字段按 lifetime 归属。
  3. transition log 使用 Runtime event:MODEL_OUTPUT_RECEIVED / TOOL_DISPATCHED / TOOL_RESULT_RECORDED / FOLLOWUP_REQUIRED / RUN_COMPLETED。
  4. 增加 invariant test:仍有 pending tool/approval 时禁止 terminal completion;一次 Run 内允许多个 model sample / canonical Turn。

不要过度推导 ​

源码能证明 Codex 当前 owner/lifetime 分层,不能证明 Shadow Harness 也必须限制“一 Session 一 running task”,也不能证明 Codex TurnState 与本知识库同名对象语义等价。

不按类型名直接建立概念映射,应比较 lifecycle、ownership 和 state transition。

完整映射见 OpenAI Codex Source Map。

自测 ​

  1. 一个 Run 从 RUNNING 直接跳到 COMPLETED,但仍有 pending ToolCall。哪条状态机不变量被破坏?
  2. Observation 到来后,谁负责决定下一状态并持久化 transition,为什么不能让模型自由写 status?

Sources ​

AI Engineering · Agent · Harness · Backend Systems