Skip to content

fix(worker): prevent repeated replans and pause dispatch on unknown outcomes - #6885

Open
songoow wants to merge 2 commits into
codex/split-research-host-launchfrom
codex/split-research-worker-readback
Open

songoow wants to merge 2 commits into
codex/split-research-host-launchfrom
codex/split-research-worker-readback

Conversation

@songoow

@songoow songoow commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

为什么需要这次修改

外部 worker 执行一次自动重规划后,命令正常返回不代表原重规划要求已经解决;命令中断、失败或超时,也不能证明原请求没有执行。若重启 worker、改变轮询 token 或收到新事件就直接再发请求,可能重复调用模型或重复产生效果。

本 PR 持久保留原重规划尝试:同一要求仍未解决时停止重复复核;原请求结果未知时暂停该执行通道的新派发,并在现有 Goal 概览显示状态。这里描述的是代码与合成回归覆盖的故障模型,没有声称已经读取或复现真实生产中的“重规划请求发出后中断”事故。

实际修改

  • 在外部 worker 自己的持久状态文件中,启动前原子保存 attempt、原 obligation(本次重规划要求)、registry / runtime / Goal / Agent 作用域,以及准入实际提供的原始结算身份;返回后保存 transport 结果。重启、轮询节奏变化和新观察不会清除原尝试。
  • 同一个配置的状态文件路径使用单消费者锁,避免本地 worker 并发重复派发;损坏或不可写的持久状态会拒绝新 wake。
  • 对已正常返回但仍未解决的原要求,显示 replan_paused;对未确认结果的原尝试,显示 replan_outcome_unknown。
  • Host poll receipt 保存的是 worker 观察,用于展示;它不是完整 attempt 的权威存储,也不授予执行权。 普通存活轮询不能抹掉未知或损坏记录。
  • history / run-history 通过现有 source-to-global projection owner 核实来源、配置和 runtime 关联。损坏、过期或关联不匹配的记录显示未知,不把旧操作展示成当前操作。
  • 在现有 Goal 概览显示暂停/未知原因、原工作与观察时间,提供“查看任务与依赖”,保留既有 Stop 路径。查看页面和导航不派发新工作,不证明 Goal 完成或 provider 停止计费。

暂停条件与继续执行的边界

情况 worker 行为 继续执行所需条件
命令正常返回(或合法的配置 stop exit code),但 fresh Core 决策仍要求同一作用域内的原语义重规划 replan_paused,不重复该要求的模型复核,结束当前 worker 调用 检查原工作,补充具体任务或相关证据,再运行 worker 并重新检查 Core 准入;单纯重启或改变轮询 token 不释放同一要求
原重规划失败、超时、输出超限,或启动记录保留但结果未确认 replan_outcome_unknown;同一作用域内暂停新派发,包括新的重规划要求和新的普通任务 先核实原请求及其结算;新任务、新事件或新 obligation ID 不能证明旧请求未执行
原准入提供了确切 settlement identity,且 Host 保留了它 读取原 Turn / obligation 的结算;pending 或身份不匹配时保持未知 只有原身份精确匹配且 settlement 已完整完成,才将该未知尝试标记为已核实返回;后续工作仍须通过当前 Core 准入
普通不透明 shell 命令没有绑定原始结算身份 无法自动证明原操作已完成,保持未知 通过原 Host 的恢复/核实路径处理并保留状态;本 PR 没有通用自动解锁或“一键恢复”能力,删除状态文件不是恢复方法
持久 worker 状态损坏、身份非法或写入失败 拒绝新 wake 恢复原始状态及其来源;普通 poll 或清空展示记录不能替代执行证据
Core 已给出 terminal / Stop 决定 Stop 优先,不再派发;保留未解决记录 后续生命周期由既有 Goal owner 决定,Stop 本身不证明旧请求已结算

replan_paused 只抑制同一语义重规划要求;一个已经正常返回的 attempt 不会阻止其他通过 Core 准入的普通工作,原记录仍保留。相比之下,started / unknown 的未确认尝试会挡住同作用域内后续新派发,这是本 PR 的保守取舍,可能需要人工核实并增加恢复成本。

现有 todo_lifecycle_settlement / receipt_bound_replan_settlement 保留各自原结算 owner,不被当成新的语义重规划机会;它们也不能绕过尚未核实的原尝试。所有这些 worker 观察都不会自动改变 Goal activation、完成 Todo 或扣除交付 quota。

作用范围与依赖

  • Base: fix(host): recheck Goal admission before process launch and turn dispatch #6884,codex/split-research-host-launch;本片提取 fix: preserve research progress across replans, waits and Host recovery #6838 的 external shell-worker 行为及配套展示。
  • observe-only 模式不消费重规划尝试;配置了 --wake-cmd 才涉及本片的派发控制。
  • 本地锁只约束同一配置状态路径。不同状态路径或分布式 Host 仍依赖已有共享执行 / lease 权威;这不是分布式 exactly-once 保证。
  • 精确 GoalRef 仅在真实提供时保留;legacy 可执行 Goal 仍按其真实别名作用域处理,不承诺重建同名 Goal 后的精确隔离。
  • provider 重试、token 上限、请求取消和完整费用仍由原 Host / provider owner 管理;本片不提供通用自动恢复策略。
  • 预期价值是减少重复派发并提高故障可见性。没有用同条件长程实验确认完成率、研究质量、总成本或人工介入净收益;“暂停了重复调用”不等于“任务已经推进”。

Author Declaration / 实现依据

验收标准 实现 / 验证入口
同状态路径重启/并发不重复派发未确认的重规划 external_scheduler_worker.py、worker continuity 回归
只有确切原始结算能解除未知尝试,后续工作重新准入 read_heartbeat_settlement、File / SQLite CLI 原回执恢复回归
worker 观察保持来源关联,普通 poll 不擦除损坏记录 host_poll_receipts.py、history / run-history、观察恢复回归
owner 能看到 paused / unknown、查看任务并使用已有 Stop Goal overview、packaged research-worker-readback browser scenario

Validation Binding

  • Published revision: c49bc360fb7a6ae25ef4e8f52a5bb07fbe76f15e。
  • Tested tree: 155aee2eed76c978577d6ca05b5a96a319f57081。
  • Input classes: synthetic, public_fixture;使用隔离 Goal / backend,受控或模拟 Host 边界,不调用付费模型。
  • 原作者记录:改动 Python Ruff / diff-check passed;本地 Dashboard build 曾主动中断,未计通过。
  • 当前 head 的补充针对性复核:
    • uv run --extra test python -m pytest tests/test_external_worker_replan_continuity.py tests/test_host_poll_receipts.py -q:71 passed。
    • uv run --extra test python -m pytest tests/control_plane/test_external_worker_replan_cli.py -k 'real_unresolved_replan_dispatches_once or real_original_receipt_recovery' -q:6 passed, 14 deselected;覆盖 File / SQLite 真实 CLI、返回后防重发、原结算核实后恢复下一任务。Host 工作受控,不能当作真实模型效果证据。
    • 首轮 CLI 复核的 File 用例通过,3 个 SQLite 用例被本机 Node 25.2.1 内置的未合格 SQLite runtime 拒绝;切换到合格 Node 22.22.3 后,同一选择集合 6 项全部通过。环境拒绝与后续通过分别保留。
  • 当前 head 已观察到两项浏览器 CI 失败,仍待处理:
    • chat-bundle-browser (1):新增 research-worker-readback 场景结束后清理临时目录时报 ENOTEMPTY。该次运行未完成验证,不能计为该检查通过。
    • dashboard-browser (1):worker-readback 场景已输出行为检查结果,随后 goal-hierarchy 的“不得读取无关 Goal 状态”断言失败;该失败尚未归因,整项检查仍为失败。
  • Run state: 上述针对性复核 finished;完整 CLI / backend / packaged browser 集合及当前 CI 失败尚未全部完成或解决,不将部分通过计为整片验收完成。
  • PostgreSQL、付费模型、线上/长程对照实验及完整成本比较:not_run。正式 review / merge readiness 另行判定。

复现 packaged 场景的入口如下,命令列出不代表已通过:

npm --prefix apps/presentation/dashboard run build
LOOPX_PERSONAL_WORKSPACE_SCENARIO=research-worker-readback LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 node examples/personal-workspace-browser-smoke.mjs

Frontend / Visual Evidence

以下图片随本片原样保留,来源是原 #6838 的合成场景;它们描述该 UI 设计,不是本次新 head 的重新截图。

Before: 桌面 · 移动。

After / first viewport — unknown 桌面:

Worker unknown desktop

After — unknown 移动:

Worker unknown mobile

Paused: 桌面 · 移动。

  • UI impact: changed。
  • Source data: synthetic。
  • Attention review: 在现有 Goal 概览集中展示观察状态与原任务入口,技术标识按需展开;unknown 保持可见,Stop 走已有 canonical 路径。
  • Craft review: 保留来源中的同组桌面/移动设计;本次新构建的视觉复核仍待完成。

Boundary Checklist

  • worker、readback、完整 UI 和回归随片交付。
  • 不把只读观察当作执行或达成证明。
  • 描述、图片和源码只含公开/合成信息。
  • 无已有双语 RFC 对变更;提交含 DCO sign-off。

恢复验证补充(2026-10-19)

  • Current validation head: 9d905ca3866444ee58836790a53959ab2849a2b1。
  • 本段的实际测试源码绑定:B4 fixture 修复提交 ca8d3732a2847c9e76e3d1f989e084e7158deb27,tested tree 24afb2e36986222fca85c5c6a54a80ad6ea9cfc9;修复基于原发布 B4 c49bc360fb7a6ae25ef4e8f52a5bb07fbe76f15e。
  • Before 使用 tree 155aee2eed76c978577d6ca05b5a96a319f57081,与原发布 B4 完全一致。上文的 71 项单测、6 项 CLI、旧截图和旧 CI 结论继续保留各自原版本绑定,不因这个 head 更新自动变成新 head 的全量通过。

修复限定在 tests/control_plane/research_worker_readback_fixture.py 和 examples/personal-workspace-browser/research-worker-readback.mjs:fixture 等待已接受的 HTTP 请求结束后再退役其 Effect runtime;JS 等待 Python child 的 close 事件并确认零退出码,然后才删除临时根。下面的因果 probe 实证了 HTTP 请求尚未结束就开始 runtime 清理的缺陷。

恢复检查 结果与边界
Before:原 B4 tree 上保留一个处理中真实 HTTP read,再触发退出 缺陷复现:runtime_cleanup_started 早于 request_finished,HTTP 返回 500;Python 仍以 0 退出。
After:修复 tree 上执行相同因果 probe passed:HTTP 返回 200,request_finished 早于 runtime_cleanup_started,Python 以 0 退出。
startAuthority("paused") → close() 首轮 failed,保留:原 runtime 退出检查在五秒门槛内返回 stopped=false,Python 退出码为 1;新增 JS 检查正确暴露了这个失败。
Paused 详细诊断重跑 passed,34.771 秒:通过仅观察原返回值和时序的 Python/Node wrapper 记录退役过程;child close=0,临时根删除。原五秒 runtime 门槛保持,实际 runtime 退役约 1.006 秒。
startAuthority("unknown") → close() passed,27.888 秒:未加载诊断 observer,实际 child close=0,临时根删除。

Paused 诊断重跑中,shutdown 应答约 0.824 ms,listener 关闭,compile-cache flush 约 545 ms,随后 locator 删除并退出;Python 的各次进程组观察约 15–38 ms,最后正确接受已无法继续写入的 zombie 进程组。这些是这一次通过运行的观察;首轮 stopped=false 的根因仍未确定,后续通过不抹掉该失败。 本次修复没有修改共享 runtime owner,也没有增加五秒退出门槛、六十秒启动门槛或目录删除重试。

本批使用 Node v24.19.0,输入仍为 synthetic, public_fixture,Host 调用受控。Python 语法、node --check 和 git diff --check 通过。两边复用同一份已存在的 bundle,仅提供真实 JSON HTTP fixture 的启动资源;本批没有重新构建或渲染当前 head 的前端,不构成完整 packaged browser / UI 资格,也不是模型效果或成本证据。

Run state: 本段限定恢复检查已完成,含上表明确保留的一次 runtime 清理失败;当前 head 的完整 packaged 场景及集成树视觉复核仍待执行。上文两项浏览器 CI 失败仍是对应原运行的失败,本段不能把它们改记为通过。合并资格另行按当前 head 的完整检查判定。

本轮发布链与集成验证

  • 本 PR 当前发布 head:9d905ca3866444ee58836790a53959ab2849a2b1;tree:24afb2e36986222fca85c5c6a54a80ad6ea9cfc9。
  • 完整拆分现为 12 个普通 PR。实际 source refs、head/tree 和依赖祖先已逐一读回;每片 base 为真实已发布前片,完整顺序与后续状态集中维护在 #6838。
  • 统一集成分支 commit d1dc8ebfe,tree cb8b1777655d7f37e3571b5087d6cd4db286dbbd,组合本次观察的 main 45d742088a3f7896a0a008761f68d2437398bcd1 与上述 12 个实际 source heads。LoopX supplied-candidate 收据为 in_sync、drift 为空;远端集成 head 已读回。
  • 完整性:集成 tree 等于冻结的完整来源与同一 main 合并后,加 A1 的 v2 测试断言迁移及 B4 的请求 drain/退出检查修复。唯一合并冲突是 chat_store.py 指标基线,按自动合并文件实测的 2067 行结算;没有新增集成专用产品逻辑。
  • 静态资格:逐切片 RFC 双语检查 12/12 通过;拆分基线后的唯一历史提交 DCO/作者匹配 14/14 通过。集成 tree 的 control-plane TypeScript 检查 passed(50.754 秒),标准 Chat 打包构建(包含 Dashboard TypeScript)passed(131.930 秒)。构建在同 tree 的本地候选上运行,随后核对与上述远端 tree 完全一致。
  • 当前 packaged UI:Outcome 恢复场景首轮在原 90 秒 fixture 启动窗口内未就绪(总计 92.463 秒),尚未进入浏览器;原失败保留,正在定位。Worker readback 与 research-history 的当前集成场景待串行验证。本段不将构建成功写成浏览器通过。
  • 本轮实际路径使用 synthetic 本地 authority/native HTTP,没有外部模型或正式 benchmark。每项历史/本轮来源版本仍按上文分别记载;CI 和 review 的合并门禁单独判定。

Signed-off-by: Codex <codex@openai.com>

Signed-off-by: song <liusongstep@gmail.com>
@loopx-agent

loopx-agent commented Oct 19, 2026 via email

Copy link
Copy Markdown
Collaborator

@songoow songoow changed the title fix(worker): retain durable replan attempts and expose recovery state fix(worker): prevent repeated replans and pause dispatch on unknown outcomes Oct 19, 2026
Join fixture HTTP handlers before retiring their owned Effect runtimes. Wait for the authority process close event and require a successful exit before deleting its disposable root.

Signed-off-by: Codex <codex@openai.com>
Signed-off-by: song <liusongstep@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants