fix(turn): resume preserved sessions on failed-turn retry - #3262
Conversation
Signed-off-by: ObVious55 <ningk6@mail2.sysu.edu.cn>
8e36348 to
e3bed82
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
PR #3262 全量双语评审 — Resume Preserved Sessions on Failed-Turn Retry
精确评审头(Exact Head): 3262@e3bed8223077d9ef1229e76b6436e77104560a02
Base: main | 类型: runtime behavior(6 files, +362/-8) | 作者: ObVious55
详细中文评审
动机
Codex CLI Turn 超时后,LoopX 已把观察到的 session binding 持久化;但 --resume-turn-key --retry-failed-turn 会重放原始 journaled plan,其 session action 仍是 start_new,Codex adapter 因此把“已保留的 binding”判为 session drift 并拒绝重试——可恢复的超时实际无法恢复。PR 让 failed-Turn 重试在验证当前 Goal/Agent/Todo binding 后,把 start_new 请求对账为 resume,并保持 identity drift fail-closed。
改动思路
把“可恢复 host 失败”显式接入 turn journal:BuiltInHostError 新增 recovery_kind="resume_session"(仅当超时时观察到并保留了 session),_run_host_runner 把它带进 host observation,_host_result_stage 写入 journal["host_recovery"];重试时 _reconcile_failed_turn_retry_request 校验 schema/kind、要求 failed_phase=host_execute、解析当前 binding,再经 reconcile_failed_turn_session_request 把 start_new 替换为 resume(binding_status=failed_turn_recovery)。任何绑定不匹配都在执行 host 之前抛错,不产生额外 host 执行或 durable 副作用;_store_codex_cli_session 同时兼容无 os.fchmod 平台(temp 文件 + os.replace + chmod 兜底)。
具体改动
loopx/cli_commands/turn.py(+14/-2):为 codex-cli host 注入session_binding_resolver(codex_cli_session_binding)。loopx/control_plane/turn_driver/codex_cli.py(+11/-3):超时且观察到 session 时抛recovery_kind="resume_session";session 存储兼容无fchmod平台(fd 关闭逻辑修正)。loopx/control_plane/turn_driver/driver.py(+39):新增reconcile_failed_turn_session_request——用当前 binding 重新_session_plan,要求 action=resume,把 journaledstart_new重写为resume并打failed_turn_recovery标记。loopx/control_plane/turn_driver/executor.py(+67/-3):HOST_RECOVERY_*常量、BuiltInHostError.recovery_kind、host observation 透传、journal 写入/清理、_reconcile_failed_turn_retry_request与run_loopx_turn_once接入(重试前对账,成功后清host_recovery)。- 测试(+231):CLI 端到端(
start_new → resume、恢复后 status=stopped、spend 0)与 executor 级(host/writeback/spend/scheduler 计数、identity drift 拒绝、fchmod缺失回退)。
关键行为讲解
recovery_kind只在“观察到 session”时设置:无 session 的超时仍走普通失败,不会伪造可恢复路径。- 重试对账:先校验
host_recoveryschema/kind 与failed_phase=host_execute,再解析当前 binding;start_new→resume重写,resume保持原样,action 非法则拒绝。 - fail-closed:binding 与 goal/agent/todo 不符时在 host 执行前抛
ValueError(测试断言 host 只调用 1 次、writeback/spend 均为 0)。 - 原子持久化:
os.fchmod不可用时先写 temp、os.replace、再chmod 600;fd 关闭逻辑避免泄漏。
正向路径
Turn 超时 → 观察到并保留 session binding → journal 记 host_recovery=resume_session → 重试解析 binding → 请求改 resume → host 继续同一 session → 正常结算(executor 测试:host 2 次、writeback/spend/scheduler 各 1 次)。
负向路径
无 session 超时 → 无 recovery_kind → 不重写;binding 漂移 → host 执行前拒绝且无副作用;schema/kind 非法或 failed_phase 不符 → 拒绝;fchmod 缺失 → atomic 存储回退。
对主干的风险
无阻断项。 commit e3bed8223 含 Signed-off-by(Sign-off check pass);GitHub build/dependency-review pass,pytest 评审时刻 pending。exact head 上 test_loopx_turn_driver.py + test_loopx_turn_executor.py 80 passed,覆盖本 PR 全部行为面。
非阻断(P2):合并前确认全量 pytest 变绿;host_recovery 是新增 journal 协议字段,建议在 release notes 注明 failed-Turn 恢复语义。
验证矩阵(exact head 实测):
| 场景 | 结果 |
|---|---|
test_loopx_turn_driver.py + test_loopx_turn_executor.py |
80 passed |
| commit sign-off | 存在 → Sign-off check pass |
| GitHub checks | build/dependency-review pass;pytest pending |
| 改动范围 | 6 files, +362/-8,turn driver/executor/CLI |
我的整体评价
根因定位准确:journaled plan 的 start_new 与已保留 binding 冲突导致可恢复超时无法恢复。修复把 recovery 作为一等 journal 状态接入,重试时先对账后执行,identity drift 在 host 调用前 fail-closed,且补了无 fchmod 平台的原子持久化——实现收敛、测试覆盖正反路径。无阻断项。结论:APPROVE。
English Verdict
Verdict: APPROVE — exact head 3262@e3bed8223077d9ef1229e76b6436e77104560a02.
Summary: A focused fix that lets failed-turn retries resume a preserved Codex CLI session: timeouts with an observed session now record a typed host_recovery, and --retry-failed-turn reconciles the journaled start_new request to resume against the current Goal/Agent/Todo binding, failing closed on identity drift before any host execution or durable effects. Session persistence also handles platforms without os.fchmod. 80 focused tests pass at the exact head; Sign-off, build, and dependency-review are green, with pytest pending at review time.
Summary
start_newrequest toresumeos.fchmodis unavailableRoot cause
When a Codex CLI Turn timed out, LoopX preserved the observed session binding.
However,
--resume-turn-key --retry-failed-turnreloaded the original journaled plan, where the session action was stillstart_new. The Codex adapter then detected the preserved binding as session drift and rejected the retry before invoking the Host.As a result, a recoverable timeout could not actually resume the preserved session.
Behavior change
Failed-Turn reconciliation is now enabled only when all of the following are true:
retry_failedhost_executeresume_sessionrecovery metadataWhen these conditions hold, the retry request is changed from
start_newtoresume.Missing or mismatched bindings fail before another Host invocation.
Measured results
start_newonlystart_new → resumeos.fchmodThe successful recovery path performs exactly one retry Host invocation, one successful durable writeback, one quota spend, and one scheduler transition.
Safety and scope
host_executephaseValidation
python -m pytest -q tests/test_loopx_turn_executor.py tests/test_loopx_turn_driver.py80 passedgit diff --checkpassedImplements GH-C92.
Closes #3228