具体问题与边界
Session 怎样一边运行多 Step Turn,一边继续响应 Interrupt,并保证只发一个终态?
Session 主循环只拥有 Operation 分派;独立活动 Task 拥有 Turn 执行;_run_owned_turn 独占 Completed/Aborted 终态。 本节不是把 Rust 改写成 Python;它从锁定提交的字段、调用顺序和测试行为提炼实现合同,再检查 Mini Codex 是否以 Python 的并发原语保持同一条不变量。
协议、类型与状态所有权
| 对象 | 创建/所有者 | 生命周期与作用域 | 是否持久化 |
|---|---|---|---|
_runner_task | Session.start/close | 跨多个 Turn | 否 |
_active_turn_task | Session 主循环创建,owned wrapper 清理 | 一个活动 Turn | 否 |
| CancellationToken | 每个 Turn 新建 | 主循环 cancel,各层检查 | 否 |
| terminal_sent | _run_owned_turn 栈帧 | 只服务一个 Turn | 否 |
正常路径
- 主循环不能直接
await _execute_turn:那会在模型或进程等待期间停止读取 Interrupt。 - 收到 UserInput 且没有活动 Turn时,新建 CancellationToken,并把
_run_owned_turn作为独立 Task。 - 第二个 UserInput 在活动 Turn 中得到 RuntimeErrorEvent。Mini Codex 不把它暗中排队,也不实现 Steer。
- Interrupt 只
cancel()当前 Token;模型传输、工具和进程管理器在各自等待点观察同一信号。 - owned wrapper 把正常、TurnCancelled、普通异常和防御兜底四条路径统一成恰好一个终态,随后清理活动指针并 set idle。
机制调用链
1. 主循环不能直接 `await _execute_turn`:那会在模型或进程等待期间停止读取 Interrupt
→ 2. 收到 UserInput 且没有活动 Turn时,新建 CancellationToken,并把 `_run_owned_turn` 作为独立 Task
→ 3. 第二个 UserInput 在活动 Turn 中得到 RuntimeErrorEvent
→ 4. Interrupt 只 `cancel()` 当前 Token;模型传输、工具和进程管理器在各自等待点观察同一信号
→ 5. owned wrapper 把正常、TurnCancelled、普通异常和防御兜底四条路径统一成恰好一个终态,随后清理活动指针并 set idle
这里最重要的不是类名,而是控制权何时转移:创建者决定 ID 和初值,状态所有者决定何时修改,跨越 await 的调用必须明确取消、失败和可见性边界。任何绕过这些边界的“便捷调用”都会让恢复或并发测试失去确定答案。
Python 风格伪代码
async def submission_loop():
while True:
sub = await input_queue.get()
match sub.operation:
case Shutdown():
cancel_active()
await join_active()
return
case Interrupt():
if active_token: active_token.cancel()
else: await emit_error(sub, "no active turn")
case UserInput() as op:
if active_task:
await emit_error(sub, "another turn is active")
else:
token = CancellationToken()
active_task = create_task(run_owned(sub, op, token))
async def run_owned(sub, op, token):
terminal = False
try:
await execute_turn(sub, op, token)
terminal = True # execute_turn emitted Completed
except TurnCancelled as exc:
await persist_aborted(exc)
await emit_aborted(exc)
terminal = True
except Exception as exc:
await emit_error_and_aborted(exc)
terminal = True
finally:
if not terminal: await emit_defensive_aborted()
clear_active_and_set_idle()
伪代码只保留设计职责;Mini Codex 的可运行版本见下方实现导航。它没有伪造官方源码中不存在的 Python API,也没有把路径策略写成 OS 沙箱。
失败、取消与恢复
| 故障或错误设计 | 会留下什么 | Mini Codex 的处理 |
|---|---|---|
| 直接 await 完整 Turn | Interrupt 无法被主循环消费 | 活动 Turn 使用独立 Task |
| 多个位置各自发终态 | 异常竞态产生 Completed + Aborted | 终态集中到 owned wrapper |
| 取消只 cancel asyncio Task | 子进程/HTTP 可能没有清理机会 | 共享协作式 Token 贯穿边界 |
| 异常后不清 active 指针 | 后续输入永远被判定为 busy | finally 清理并 set idle |
必须保持的不变量
一个 Session 同时至多有一个活动 Turn;一个 Turn 对客户端恰好产生一个 Completed 或 Aborted。
这条不变量同时约束正常路径、异常路径和恢复路径。只在 happy path 里得到正确输出,不足以证明该模块边界成立。
设计取舍
独立任务使中断可达,却引入 active 指针、idle 和关闭 join 的状态同步。Mini Codex 省略官方 Steer、Review 与 Realtime 等任务变体。
源码能够直接证明类型、分支、调用顺序和测试期望;关于工程动机的解释是基于这些事实的设计归纳,不冒充未公开承诺。
测试与复现实验
cd examples/mini-codex
uv run pytest -q -k 'test_interrupt_produces_one_aborted_terminal_event' -k 'test_single_sample_turn_emits_one_terminal_event'
uv run mypy src
本节对应的关键断言:
test_interrupt_produces_one_aborted_terminal_eventtest_single_sample_turn_emits_one_terminal_event
全量离线基线为 27 项通过;真实 Responses 测试需要显式环境变量,默认跳过。单项测试名用于定位,不代替对断言内容的解释。
官方源码导航
- codex-rs/core/src/session/mod.rs:
submission_loop、ActiveTurn 与取消/事件发送 - codex-rs/core/src/session/turn.rs:
run_turn的多轮采样控制 - codex-rs/core/src/tasks/mod.rs:任务启动、取消和终态职责
Mini Codex 对照
src/mini_codex/runtime/session.py:_run、_run_owned_turn、_execute_turnsrc/mini_codex/runtime/cancellation.py:共享取消 Token
Mini Codex 保留本节的状态所有权、顺序和失败反馈;省略的产品能力会在边界处明确列出,不能由测试通过外推为生产等价。
本节边界
已经证明:一个 Session 同时至多有一个活动 Turn;一个 Turn 对客户端恰好产生一个 Completed 或 Aborted。
尚未覆盖的生产问题由后续单元继续展开;公开版保留官方源码链接和可运行测试合同。
评论
登录后即可评论