Appearance
一次 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
精确定位
- 文件:
source/host/runner/turn-run-shell.ts - Symbol:
createTurnAgentRunContext() - 深入索引:Code Atlas:01-turn-lifecycle
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
精确定位
- 文件:
source/host/runner/turn-observation.ts - Symbol:
createTurnObservation()→turnStarted()→toolStarted()→toolCompleted() - 深入索引:Code Atlas:04-turn-observation
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:
codex-rs/core/src/session/session.rscodex-rs/core/src/session/turn.rscodex-rs/core/src/state/turn.rs
Symbols:
SessionSession::active_turnrun_turn(...)ActiveTurnRunningTaskTurnState
最小调用链
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 cycleObservable 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
- 明确
RunState是 outer execution owner,canonicalTurnState只表示单次 logical decision cycle;需要时另设ActiveRunControl承载 wait/cancel/pending interaction。 - 不把 Codex
TurnState名字直接映射为 ShadowTurnState;逐字段按 lifetime 归属。 - transition log 使用 Runtime event:
MODEL_OUTPUT_RECEIVED / TOOL_DISPATCHED / TOOL_RESULT_RECORDED / FOLLOWUP_REQUIRED / RUN_COMPLETED。 - 增加 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。
自测
- 一个 Run 从 RUNNING 直接跳到 COMPLETED,但仍有 pending ToolCall。哪条状态机不变量被破坏?
- Observation 到来后,谁负责决定下一状态并持久化 transition,为什么不能让模型自由写 status?