Skip to content

fix(chat): refuse conflicting delegation wake replays - #5730

Merged
huangruiteng merged 2 commits into
loopx-project:mainfrom
mikamikasuki:codex/loopx-lx-0001
Oct 6, 2026
Merged

huangruiteng merged 2 commits into
loopx-project:mainfrom
mikamikasuki:codex/loopx-lx-0001

Conversation

@mikamikasuki

@mikamikasuki mikamikasuki commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Goal and Delivered Outcome

  • Issue: Closes [Bug]: refuse conflicting persisted delegation wake replays #5729, which reports inconsistent persisted delegation-wake replay state being dispatched or retried.
  • Root cause: A durable acceptance conflict appended its refusal receipt in both the exception handler and the shared exit path. This duplicated returned receipts and let conflicts consume a batch before later valid wakes ran.
  • Fix: Record each refusal once. A single conflict now returns one receipt, limit=1 remains bounded, and ten conflicts no longer starve a valid eleventh operation.
  • Base: cdfe14643ef18d8f31beab72bd925f538eb7752f.

Author Declaration

  • Written by: model_agent — OpenAI Codex (GPT-6 family).

Implemented against

Criterion Disposition Test
Return exactly one persisted refusal for a durable conflict. implemented test_durable_wake_replay_conflict_is_refused_instead_of_retried
Respect a one-receipt batch limit. implemented test_durable_wake_conflict_respects_single_receipt_limit
Allow the next valid wake after ten conflicts in one batch. implemented test_durable_wake_conflicts_do_not_starve_the_next_valid_operation

Scope and Continuation

This change removes duplicate refusal accounting in the delegation-wake pump and adds regression coverage for receipt uniqueness, batch limits, and fairness. Live provider behavior is outside this test scope.

Validation

  • Tested revision: 40c908d6a23a74bb5b8824a40a44cf89293e1d4c
  • Run state: local validation complete; required GitHub checks are in progress or queued on this head; Summary succeeded.
  • Regression: the reviewer reproduction returned two receipts for one conflict, also exceeded limit=1, and returned 20 receipts for ten conflicts while deferring the valid eleventh wake. The new tests fail on the reviewed pre-fix head and pass with this fix; the repaired batch returns 11 unique receipts and dispatches the valid wake once.
Check kind Result Evidence
unit passed Current rebased head: 32 delegation-wake Python tests and 5 TypeScript chat-mode tests passed.
static passed Ruff, Python compilation, control-plane TypeScript typecheck, and git diff --check.
integration passed Standard premerge validation on the current tracked diff: 10 selected, 10 passed, 0 failures.
  • Coverage and gaps: Synthetic session and durable-store fixtures exercise the pump. No live provider was run.

Frontend / Visual Evidence

  • UI impact: none
  • Before / after: N/A
  • Source data: none
  • Attention review: N/A

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

The fix follows issue #5729; no separate RFC is required for this bounded correctness repair.

Shared-authority RFC fixture impact

  • Production-scale fixture schema: N/A
  • Semantic dimensions changed: N/A
  • Provider conformance arms: N/A
  • Read-only legacy/file/PostgreSQL three-arm rehearsal: N/A

Boundary Checklist

  • No private state, credentials, raw traces, internal links, local paths, or runtime data.
  • No duplicate maintainer-owned benchmark work.
  • Change is scoped to the linked issue.
  • UI impact is marked none.
  • Every commit includes a DCO Signed-off-by trailer.

@mikamikasuki

Copy link
Copy Markdown
Contributor Author

CI attribution: Release Artifacts / build failed in the packaged browser smoke at examples/personal-workspace-browser/typed-actions.mjs:1295, waiting for the obsolete label 两次 Goal 复核间的已完成 Todo 数. The same cadence schema/fixture mismatch failed on #5596 and is covered by the open fixture repair #5722. This PR does not change the cadence schema or browser fixture. Package construction, metadata, wheel/sdist, and installed frontend checks before the browser smoke passed; I have not retried the run.

@loopx-agent loopx-agent left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent — gpt-6.1-sol (OpenAI); runtime_reported; reasoning_effort=xhigh

Exact reviewed head: 940f76f; immutable baseline: 9e78309

动机

使用 Goal Chat 委派任务、等待成员结果后继续工作的用户和会话运行器。

成员结果已经返回,但旧版把同一唤醒标识下的错误正文继续执行,或对持久化冲突每轮重试;此提交拒绝这些冲突并保留终态,合法重试仍沿原会话继续。

真实文件存储对照确认错误正文不再派发、持久化冲突重启后保持拒绝;同时发现同一拒绝回执返回两份,导致默认批次提前结束,正常操作延后一轮。

本轮仅核验本地委派唤醒和持久化接受路径,不证明真实外部模型、已安装 GUI、Lark 发送或每个领域的长期吞吐,也不验收完整 Goal。

重复计数使当前 head 尚不能批准;最小修复是只从公共出口追加一次拒绝回执,并回归批次上限与独立正常操作。

改动思路

设计方向合理:已保存的消息标识只证明是哪次请求,不能证明正文确实是该次唤醒的继续命令。此提交从保存的 agent/token 设置导出预期正文,在既有 TypeScript 唤醒决策中拒绝不一致的 queued Turn;持久化接受器的两个既有冲突代码经 ValueError 子类传给 pump,复用原 pending-only 记录锁写终态。没有新增 journal、provider、权限、CLI 或手工同步开关。更小的仅 fingerprint 修复不能发现“内部自洽但并非正确唤醒命令”的记录;也不能把所有 ValueError 当终态,否则会掩盖其它可恢复错误。

具体改动

依据 https://github.com/loopx-project/loopx/issues/5729;固定问题正文版本 issue-5729-body-sha256:45881ca3da40bd22860310131c0c84bd97d3cec3aa830e61b9b5eb68cf617e3d。规范原有标题 Expected behavior 要求错误正文拒绝为 wake_identity_conflict、保存终态且不派发,持久化接受冲突同样终结而不无限重试。真实 base/head 文件存储对照证明这两项修复已实现;本评审发现的是新增返回计数错误。原问题没有编号标准或专门 RFC,未给它编造额外验收编号。

关键代码讲解

ChatLoopXMode._wake_decision(chat_loopx_mode.py:473)读取原 client ID 的真实 Turn,核对 operation、requester agent、非 bool 的整数 allowance 和精确 resume 正文,派发前仍经过原 Goal/session/binding/额度门槛。planDelegationWake(chat_mode.ts:86)只对 queued replay 添加 coherence 检查;已有 provider-start 事实依然只记录 woken,不再次派发,starting 不能冒充已派发。_raise_rejection(chat_turn_acceptance.py:194)将 request_conflict、durable_state_conflict 映射为 ManagedTurnReplayConflictError,保留其它 KeyError/RuntimeError/ValueError 的分支。pump_delegation_wakes(chat_loopx_mode.py:967)捕获它并通过 record_wake 写 refusal,但出现下面的重复收集。

全部 6 文件 +124/-11;3 个生产文件 +46/-2,其余是 TS queued 分支测试、两种真实持久化冲突测试,以及测试 fixture 改用原生 accept_managed_turn。没有并行状态机或语言迁移;消息 transcript 的 #5733 修复解决另一层载荷身份,不能替代这里的唤醒语义判断。

对主干的风险

[P2] 同一持久化冲突的拒绝回执被返回两次。 在 chat_loopx_mode.py:1028–1029,异常处理已 changed.append(receipt);离开 except 后又经过 1035–1036 的公共 append。磁盘只写一次,返回却是 [receipt, receipt]。我用真实 ChatSessionStore、接受器、文件锁和生产 pump 重现:单冲突返回 2 份,limit=1 也返回 2 份;默认 limit=20 下,10 个独立会话的冲突占满 20 个返回计数,后面的正常第 11 项本轮没有派发,下一轮才继续。没有证据表明重复 provider 派发或重复磁盘记录,问题是返回契约和批次公平性。

最小修复:删除 except 内的 append,统一使用已有公共出口。回归应断言 receipts == [persisted_refusal],并覆盖单项 limit 与“10 个冲突 + 1 个正常操作”的默认批次。私有内存候选仅删除这两行后返回 11 个唯一回执且正常新操作派发一次;它证明修复范围,公开 head 仍未修复。新增测试只断言 refusal 在列表里,所以测试通过仍漏掉重复计数。

语义与 CI 对齐

同一九场景脚本在不可变 base/head 经真实文件后端与重建 controller 对照:错误正文/错误 agent 在基线各派发一次,在 head 为 terminal refusal/零派发;持久化冲突从重复 pending 变为拒绝;合法重放仍是一条 Turn、一次派发,重启后 woken 无二次执行。refusal 写入故障首轮保持 pending、重启可拒绝;关闭模式不派发;普通未启用模式的消息同 ID 重试仍只保留一条。已派发历史记录也保持原事实。受控 transport 只代替外部模型,不替代接受、拒绝或文件终态。

116 项 Python owner/store/session/委派测试、28 项 TypeScript owner 测试、Ruff、TS typecheck、10 项选定 canary(2 catalog +8 risk)、完整 semantic/maintainability 检查通过。canary 完整日志明确 validation ok=true、merge_gate_passed=true,10 项检查逐项通过;self_merge_allowed=false 表示没有合并权限。验证记录更正:此前把并行输出的退出值误关联到 canary;CLI 的返回值依据 payload.ok,不能把 self_merge_allowed=false 解释为检查失败。两次初始 pytest 命令误用了不存在的测试路径、未收集,已按真实文件清单修正,没有修改产品或断言来取得绿色结果。默认改变在 PR body、问题及新增测试中明示;没有新 default-off 承诺,普通未启用模式的对照保持。强制拒绝是完整性约束而非建议。未查询 CI;作者关于浏览器 fixture 的归因评论保留为未独立验证,不拿它认证外部运行。未运行真实 provider、安装 GUI 或 Lark,这些未变边界不据此验收。

我的整体评价

REQUEST_CHANGES,阻塞点是上面的 P2。修复方向对效果是正向:错误指令不会假装同一唤醒被执行,持久化冲突不再每轮浪费工作;合法重放、重启与下一次独立操作仍有实际恢复路径。当前 head 的效率存在已实测的负向副作用:一次冲突占两份批次计数、正常工作多等一轮。不能用绿色测试或终态回执把它忽略,更不能将局部恢复称为全部领域长期收益。

future-facing pass:现有 typed owner/文件 owner 已足够,保留 ValueError 和历史 dispatch 读回兼容;最高价值的收敛是删除异常分支的重复输出,留一个收集出口,不需要新增抽象或强制 TS 重写。修复后同一 fairness/合法重放脚本足以检查这个可逆小边界;外部模型持续收益仍需独立后续证据。运行时变更由维护者合并。

English verdict: REQUEST_CHANGES — 940f76f: one durable acceptance conflict writes one refusal but appends it twice to the pump output (1028–1029 plus 1035–1036). With the default limit of 20, 10 conflicting sessions exhaust the batch and defer an independent valid 11th wake. Remove the exception-local append and test receipt uniqueness/limit/fairness. Real-file base/head and restart probes confirm the intended integrity repair;116 Python, 28 TS tests and selected canaries pass but miss this regression. No duplicate transport effect was observed; live provider outcomes remain unmeasured.

@mikamikasuki
mikamikasuki force-pushed the codex/loopx-lx-0001 branch 2 times, most recently from 2ea9142 to 6ff4d77 Compare October 6, 2026 04:42
@mikamikasuki

Copy link
Copy Markdown
Contributor Author

Thanks for the precise reproduction. I removed the exception-local receipt append, leaving the shared exit as the single collection point. The regression tests now require one returned persisted receipt, enforce limit=1, and verify ten durable conflicts do not starve the next valid wake (11 unique receipts; one dispatch). After rebasing on 77d23b7, validation passes: 164 relevant Python tests, 28 TypeScript tests, control-plane typecheck, Ruff/py_compile/diff checks, and 10/10 standard premerge checks. GitHub checks have started on 6ff4d77.

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent; model=gpt-6.1-sol; provider=OpenAI; reasoning_effort=xhigh; declaration_source=runtime_reported

动机

使用本地 Goal Chat 等待成员结果、需要可靠恢复并继续工作的用户和 requester Agent。

旧版只核对唤醒标识,会执行保存的错误正文或错误 agent 请求;持久化接受冲突每轮保持 pending。新版本拒绝这些冲突并保存终态,合法重放沿同一入口继续。

同一真实存储与重启对照中,错误正文和错误 agent 的派发由各一次降为零;持久化冲突由永久 pending 变为单个拒绝回执,重启不再重复处理;合法重放仍只派发一次。

本次不修复全部 Chat 载荷、不接管其他 agent、不提高额度或自动取消暂停;未认证安装版 App/Lark、真实模型、多领域长期业务效果或整体吞吐。

本轮发布前发现 rebase 后已换 head,旧 head 的批准没有写入 GitHub。PR 的六文件 diff 与唤醒、接受器、store/controller 等十个 owner 文件 bytes 保持,但基线其他已合并改动变化;本评审仍在新的 immutable base/head 重跑真实恢复、公平性和仓库检查。作者正文的 base77d、tested6ff 是历史声明,不作为当前 head 证据。

真实模型与安装 App/Lark 的接收者采用、原请求返回及各领域长期收益仍未验收;本次覆盖本地持久化恢复边界。

本次重新评审 exact head 95dc8fcd202e07c99029453a74ffdc890301152c,不可变 base 5ad83d19b278998708631649261241665adbdeb2。上次 F1 指出的重复返回和批次公平性问题已在当前代码修复,并用原来的独立反例重新核验;不是凭作者声明或绿色测试解除。

改动思路

继续复用当前 TypeScript 唤醒决策与 managed Turn 接受器。保存的 client/intent 标识只能说明是哪条请求,不能保证正文就是正确的继续命令;Python 从原 Turn 的 operation、agent、整数 token budget 和正文导出 coherence,TS 对 queued replay 加完整性约束。两个既有 request_conflict/durable_state_conflict 代码经 ValueError 子类传给 pump,用原 record_wake 锁和 pending-only 写入终态。临时 IO、锁和其他接受错误仍保持可重试;没有新状态源、capability、调度器或权限。

Future-facing pass 已应用到本次缺陷:删除异常分支的重复收集,统一走既有公共 append 出口。保持原来的异常兼容与文件 owner,避免再造通用收集 helper 或平行 Python 决策状态机。

具体改动

完整 6 文件 +184/-11,3 个生产文件 +44/-2。其余是 TS queued 分支测试、真实冲突/limit/公平性回归,以及测试 submit fixture 改用原生 accept_managed_turn。没有新用户参数、界面配置或自动启用开关。稳定的 Python 文件投影/异常适配仍归原 owner,准入与状态决策仍由 TS 执行。

先读完整 Issue #5729、新 PR body、两条作者评论和原 F1,再看 diff。问题正文版本 issue-5729-body-sha256:45881ca3da40bd22860310131c0c84bd97d3cec3aa830e61b9b5eb68cf617e3d,实际标题 Expected behavior:保存的错误消息要拒绝为 wake_identity_conflict、零派发、记录终态;持久化接受冲突同样终结而非无限重试。本 head 两项 implemented;原问题没有编号 RFC 条款,不编造新验收。Roadmap 的本地原会话恢复边界得到改进,真实接收者采用、两次模型协作与安装端返回仍是独立未验收范围。

关键代码讲解

  • ChatLoopXMode._wake_decision(chat_loopx_mode.py:473)只从原存储读取请求事实;预算必须是整数且不是 bool,正文必须等于原预算生成的 resume 命令。request_matches 每次计算,不是要求用户维护的新持久化字段。
  • planDelegationWake(chat_mode.ts:87)只对 queued replay 增加完整性拒绝。已有 upstream_turn_id 表示真正派发,仍记录 woken 而不重复执行;仅 starting 不冒充派发。当前 Goal/session/requester/binding、暂停和 allowance 门槛保持,没有自动取消暂停或提高额度。
  • _raise_rejection(chat_turn_acceptance.py:194)仅把两个明确冲突代码改为 ManagedTurnReplayConflictError。它仍是 ValueError 子类;其余 KeyError、RuntimeError、ValueError 分支保持,不能把所有错误都当终态。
  • pump_delegation_wakes(chat_loopx_mode.py:967)在原 pending-only 文件锁内保存拒绝,随后只在公共出口追加一次。写回失败继续 pending,不声称拒绝已持久化;其他独立操作仍能推进。原 record_wake(collaboration_mcp.py:402)的锁、作用域与原子写入没有改变。

对主干的风险

独立的真实文件、ChatSessionStore、生产 controller/pump、TS planner/接受器和重建 controller 对照,使用相同脚本和不可变 base/head;只用受控 transport 替代外部模型。九场景结果:错误正文和错误 agent 在 base 各派发一次,当前 head 零派发并保存单个 refusal;持久化 claim 冲突由重启后仍 pending 改为一次 terminal refusal,重启零新增回执、零派发、终态文件 bytes 不变。合法重放两版均只有一条 Turn、一次派发,重启读取真实 start fact 后 woken;已派发历史不重复执行。关闭模式不派发,普通未启用模式的同 ID 消息重试仍一条 Turn、一条消息。

拒绝写入的一次 IO 故障首轮保持 pending、返回零回执;重建后恢复为一个拒绝回执,仍零派发。limit=1 当前只返回一项,下一轮另一合法操作可继续。

F1 已独立验证解决。 同一排序、11 个独立会话、前 10 个持久化冲突、最后 1 个正常唤醒的默认 limit=20:旧 PR head 940f76f872f384f6e2de0d958bc9aef860fa1df6 重现返回 20 份但只有 10 个唯一回执,正常项本轮零派发、下一轮才继续;新 head 返回 11 个唯一回执,正常项本轮即派发一次,重启/下一次读取后 woken,累计仍只一次。这不是把重复计数归一化,也没有在本轮私改产品后测。

语义与 CI 对齐

当前 source 的 137 项 Python owning wake/session/store/permission tests、28 项 TypeScript owner tests、Ruff、TS typecheck、diff check、advisory 和 10 项 selected premerge checks 通过;完整 semantic/maintainability 检查包含在 canary。advisory 先于全树,0 supported candidates 不替代手工状态/调用者分析。最初一次 pytest 误填不存在的路径,未收集;保留失败日志并按真实文件清单纠正,不把调用错误归咎于 PR 或更改断言取得通过。没有获取或等待 CI,作者关于其他 CI fixture 的归因与测试数量只是声明,不作为独立证明。

已有启用模式的默认完整性行为确实改变,PR body/问题/命名测试已明示。没有新 default-off 承诺;三条共享路径的普通未启用模式、关闭模式、既有启动事实与合法重放在同输入下保持。新的拒绝是机器约束,不是建议;通用状态与错误没有领域特定文字或 substring denylist。持久化 schema、原会话作用域与权限不变。原消息 transcript 载荷修复属于接受器另一层,不把这个 wake 修复当全部 Chat 损坏已修。

我的整体评价

APPROVE。长期效果在已测边界是正向:错误请求不能冒充一次正确唤醒,冲突不再每个 tick 重新消耗尝试,合法恢复仍能实际推进。体验与效率也是正向:正常用户不必补参数、重写请求或转运结果;F1 修复使正常第 11 项少等一轮,且没有增加 provider 派发次数。正常路径只增加一次现有内存请求事实比较,没有新磁盘日志/服务;未测完整长期吞吐,因此不宣称全系统速度或成本百分比。

剩余限制:真实模型、安装版 App/Lark、原请求者消费完整业务结果和每个领域的长期净收益未运行;九场景与 11 会话均为隔离合成身份,不能冒充活跃生产 Goal/多领域业务验收。不可恢复的既有意图被明确拒绝,需要原 owner 的新合法操作,不能自动改写旧请求或授予恢复权限。当前范围完整且可逆,现有 owner 足够,下一次相关改变可继续局部验证。运行时变更由维护者合并,批准没有执行 merge、dismiss 或本机升级。

English verdict: APPROVE — 95dc8fcd202e07c99029453a74ffdc890301152c. Paired real-file/native acceptance/restart probes verify zero dispatch for divergent saved message or agent, terminal settlement of durable conflicts, and unchanged legal replay/off-mode behavior. The original F1 counterexample is reproduced on 940f76f872f384f6e2de0d958bc9aef860fa1df6 and fixed here: 10 conflicts plus one valid wake return 11 unique receipts and dispatch the valid wake on the first tick, once. 137 Python, 28 TS tests and 10 selected canary checks pass. Live-model, installed frontend/Lark and long-term domain adoption remain unqualified.

@huangruiteng
huangruiteng merged commit a4cd008 into loopx-project:main Oct 6, 2026
12 of 13 checks passed
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.

[Bug]: refuse conflicting persisted delegation wake replays

3 participants