Skip to content

refactor(quota): coalesce live projection packet rendering - #4775

Merged
huangruiteng merged 2 commits into
loopx-project:mainfrom
songoow:codex/coalesce-live-packet-rendering
Sep 20, 2026
Merged

huangruiteng merged 2 commits into
loopx-project:mainfrom
songoow:codex/coalesce-live-packet-rendering

Conversation

@songoow

@songoow songoow commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

When turn-start reads and a pending capability intent both modify a live decision, the projection stage renders protocol_action_packet twice. The intermediate packet has no reader. This implements PR-04 from discussion #4738.

The two existing helpers now report whether rendering is needed. The live entrypoint renders once at the stage exit, before unsettled-host recovery. Interaction construction and precedence remain in their existing order; base quota still returns a complete packet.

Validation:

  • 802 tests passed: full architecture suite plus live decision, prompt upgrade, repository delivery, periodic intent and TurnEnvelope suites, using real TypeScript bridges and isolated filesystem fixtures.
  • New active/paused × reads × absent/pending/invalid-intent cases compare the complete payload and action signature document against eager rendering. The combined case fails on the base with two render calls and passes here with one. Real host recovery is exercised with and without reads.
  • Ruff and diff checks pass. Two existing prompt-upgrade tests fail on both base and head when pytest's temporary path makes a command shorter than their asserted 360 characters; the full run above uses an explicit longer temporary directory.

Owning boundary: existing quota live composition, no new capability/provider or state rule. CLI and managed-Turn callers share this entrypoint; no frontend companion change is needed because payload, commands and signature input are unchanged. The related simplification is the removal of the two helper-owned packet writes; interaction construction is intentionally retained because it has its own ordering contract. No throughput improvement is claimed from the removed render. PR-05 field retirement and PR-07/08 construction changes remain separate discussion slices. Maintainer merge required.

Signed-off-by: song <22676124+songoow@users.noreply.github.com>

@huangruiteng huangruiteng 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.

精确 head review:#4775 refactor(quota): coalesce live projection packet rendering

  • 审阅对象:562ca620ecfa32f694daca905245d80514cef7b9(base a233ed84c,作者 @songoow)
  • 变更面:loopx/control_plane/quota/live_decision.py(+16/-11)、tests/control_plane/test_effect_turn_live_quota_decision.py(+107/-1)
  • 结论:无阻断发现,APPROVE(附残余风险,见第五节;合并决定留给 maintainer)

动机

build_live_quota_should_run_decision 是 loopx quota should-run、loopx turn、loopx turn-decision、scheduler-followup 四条入口共用的活决策组装点。它先做 turn-start required-reads 投影,再做 pending capability-intent 优先级投影,而这两个 helper 各自在内部写了一次 payload["protocol_action_packet"]。当一次唤醒同时命中「需要新读 operator inbox」和「有待消费的 periodic-report intent」时,包体被构建两次,第一次的值在任何消费者读取之前就被覆盖。

我在 base 上用这个 head 自带的新测试做了反证:合并场景下记录到 2 次构建(期望 1 次);其余 12 个参数组合在 base 上都是 1 次或 0 次,全部通过。所以「重复构建」是真实存在的,并且被新测试精确捕获——这条测试不是装饰。

改动思路

把「谁负责渲染 packet」从两个 helper 收回到唯一知道全部投影输入的调用帧:

  • 两个 helper 的返回值由 None 改成 bool("本 helper 是否改动了 payload"),不再自己渲染;
  • 入口用一个 packet_changed 累加两次返回值,在投影阶段出口(interaction.update(projections) 之后、apply_unsettled_host_turn_recovery_if_required 之前)渲染一次;
  • recovery 路径自己仍会渲染(quota/unsettled_host_turn.py:346),所以恢复回合不依赖这个 flag;
  • 新增测试用「eager 顺序重建」作为参照,断言合并后的 payload 与 quota_action_signature_document 与旧顺序完全一致。

具体改动

  • _project_turn_start_required_reads:无投影时 return False,改动后 return True,删除末尾的 payload["protocol_action_packet"] = ...。
  • _apply_pending_capability_intent_precedence:非 pending、或 projection 缺少 summary/command 时 return False,否则 return True,同样删除内部渲染。
  • build_live_quota_should_run_decision:新增 packet_changed 累加与一次性条件渲染,附一行说明为什么渲染必须留在阶段出口。
  • 测试:新增 test_live_projection_stage_renders_one_complete_packet,参数化为 active/paused × reads on/off × intent absent/pending/invalid,同时校验渲染次数、完整决策、签名文档,并与 eager 顺序逐字段比对;既有 recovery 测试增加 reads 参数化以覆盖恢复输入需要完整包体的分支。

对主干的风险

  1. 渲染点后移:packet 现在在 interaction.update(projections) 之后渲染。我直接探测了 build_protocol_action_packet:改变 interaction_contract 不会改变 packet 输出(protocol_action_packet_fields 不读取该字段),新测试的深度相等断言也覆盖了这条路径。未观察到 drift。
  2. 是否存在中间包的消费者:dispatch_interaction_projection_hooks(registrations) 只接收注册表,producer 以无参方式调用,拿不到 payload;其余所有 packet 读取点都在阶段之后。base 上那一份中间包在树内确实没有读者。
  3. 行为变化披露:这是行为保持型重构,payload 文本、CLI 输出、签名输入都不变,只有构建次数从 2 降到 1;PR body 与内联注释都写清了这一点。
  4. 同一讨论的其他切片(字段退役 PR-05、构造调整 PR-07/08)与另外两处 packet 写入点(quota/should_run.py、quota/should_run_packet.py,本来各自只渲染一次)都不在本切片内,边界干净。

残余风险:两个 helper 的返回值现在具备语义,但只有这个入口消费它;树内另一个直接调用者(periodic-report pending-intent 测试)只断言 payload 效果,不会察觉 flag 被丢弃。另外我的验证是本地的:我跑了拥有该文件的两个套件,加上 hook / pending-intent / agent-context / repository-delivery 相邻套件(97 tests),做了 base 反证,并在精确 head 上读了一次远端检查;我没有复跑作者声称的 802 测试,也没有在真实 daemon 唤醒上观察。

我的整体评价

正向且比例合适。它修的是一个真实但危害有界的缺陷:一次唤醒重复构建同一份、被签名的操作包,并且把一份陈旧中间值留在路径上——今天没有读者,但任何未来插入两个投影之间的读者都会静默拿到「intent 生效之前」的包。修法是让两个 helper 返回它们本来就隐含知道的事实,把渲染集中到唯一的拥有者,没有新模块、新配置、新状态、新 CLI 面;测试用 eager 顺序作为参照而不是照抄实现,且能在 base 上失败,说明它证明的是不变量而非当前输出。

未发现阻断问题(no blocking finding)。唯一建议(非阻断):如果将来投影阶段增加第三个来源,请让它并入同一个累加器,并保留「阶段出口渲染一次」的注释与测试。

English verdict: APPROVE - the base really does render protocol_action_packet twice on the reads+pending-intent path and the intermediate value has no reader in the tree; the change moves that single render to the owning stage exit with no payload, CLI or signature drift (verified by base/head render-count counterfactual, deep payload equality against the eager composition, a direct probe showing the packet does not depend on interaction_contract, and 97 localized tests plus green checks at 562ca62). It is a proportionate refactor, not a behavior change; merging remains a maintainer decision.

huangruiteng
huangruiteng previously approved these changes Sep 20, 2026

@huangruiteng huangruiteng 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.

精确 head review:#4775 refactor(quota): coalesce live projection packet rendering

  • 审阅对象:562ca620ecfa32f694daca905245d80514cef7b9(base a233ed84c,作者 @songoow)
  • 变更面:loopx/control_plane/quota/live_decision.py(+16/-11)、tests/control_plane/test_effect_turn_live_quota_decision.py(+107/-1)
  • 结论:无阻断发现,APPROVE(附残余风险,见第五节;合并决定留给 maintainer)

动机

build_live_quota_should_run_decision 是 loopx quota should-run、loopx turn、loopx turn-decision、scheduler-followup 四条入口共用的活决策组装点。它先做 turn-start required-reads 投影,再做 pending capability-intent 优先级投影,而这两个 helper 各自在内部写了一次 payload["protocol_action_packet"]。当一次唤醒同时命中「需要新读 operator inbox」和「有待消费的 periodic-report intent」时,包体被构建两次,第一次的值在任何消费者读取之前就被覆盖。

我在 base 上用这个 head 自带的新测试做了反证:合并场景下记录到 2 次构建(期望 1 次);其余 12 个参数组合在 base 上都是 1 次或 0 次,全部通过。所以「重复构建」是真实存在的,并且被新测试精确捕获——这条测试不是装饰。

改动思路

把「谁负责渲染 packet」从两个 helper 收回到唯一知道全部投影输入的调用帧:

  • 两个 helper 的返回值由 None 改成 bool("本 helper 是否改动了 payload"),不再自己渲染;
  • 入口用一个 packet_changed 累加两次返回值,在投影阶段出口(interaction.update(projections) 之后、apply_unsettled_host_turn_recovery_if_required 之前)渲染一次;
  • recovery 路径自己仍会渲染(quota/unsettled_host_turn.py:346),所以恢复回合不依赖这个 flag;
  • 新增测试用「eager 顺序重建」作为参照,断言合并后的 payload 与 quota_action_signature_document 与旧顺序完全一致。

具体改动

  • _project_turn_start_required_reads:无投影时 return False,改动后 return True,删除末尾的 payload["protocol_action_packet"] = ...。
  • _apply_pending_capability_intent_precedence:非 pending、或 projection 缺少 summary/command 时 return False,否则 return True,同样删除内部渲染。
  • build_live_quota_should_run_decision:新增 packet_changed 累加与一次性条件渲染,附一行说明为什么渲染必须留在阶段出口。
  • 测试:新增 test_live_projection_stage_renders_one_complete_packet,参数化为 active/paused × reads on/off × intent absent/pending/invalid,同时校验渲染次数、完整决策、签名文档,并与 eager 顺序逐字段比对;既有 recovery 测试增加 reads 参数化以覆盖恢复输入需要完整包体的分支。

对主干的风险

  1. 渲染点后移:packet 现在在 interaction.update(projections) 之后渲染。我直接探测了 build_protocol_action_packet:改变 interaction_contract 不会改变 packet 输出(protocol_action_packet_fields 不读取该字段),新测试的深度相等断言也覆盖了这条路径。未观察到 drift。
  2. 是否存在中间包的消费者:dispatch_interaction_projection_hooks(registrations) 只接收注册表,producer 以无参方式调用,拿不到 payload;其余所有 packet 读取点都在阶段之后。base 上那一份中间包在树内确实没有读者。
  3. 行为变化披露:这是行为保持型重构,payload 文本、CLI 输出、签名输入都不变,只有构建次数从 2 降到 1;PR body 与内联注释都写清了这一点。
  4. 同一讨论的其他切片(字段退役 PR-05、构造调整 PR-07/08)与另外两处 packet 写入点(quota/should_run.py、quota/should_run_packet.py,本来各自只渲染一次)都不在本切片内,边界干净。

残余风险:两个 helper 的返回值现在具备语义,但只有这个入口消费它;树内另一个直接调用者(periodic-report pending-intent 测试)只断言 payload 效果,不会察觉 flag 被丢弃。另外我的验证是本地的:我跑了拥有该文件的两个套件,加上 hook / pending-intent / agent-context / repository-delivery 相邻套件(97 tests),做了 base 反证,并在精确 head 上读了一次远端检查;我没有复跑作者声称的 802 测试,也没有在真实 daemon 唤醒上观察。

我的整体评价

正向且比例合适。它修的是一个真实但危害有界的缺陷:一次唤醒重复构建同一份、被签名的操作包,并且把一份陈旧中间值留在路径上——今天没有读者,但任何未来插入两个投影之间的读者都会静默拿到「intent 生效之前」的包。修法是让两个 helper 返回它们本来就隐含知道的事实,把渲染集中到唯一的拥有者,没有新模块、新配置、新状态、新 CLI 面;测试用 eager 顺序作为参照而不是照抄实现,且能在 base 上失败,说明它证明的是不变量而非当前输出。

未发现阻断问题(no blocking finding)。唯一建议(非阻断):如果将来投影阶段增加第三个来源,请让它并入同一个累加器,并保留「阶段出口渲染一次」的注释与测试。

English verdict: APPROVE - the base really does render protocol_action_packet twice on the reads+pending-intent path and the intermediate value has no reader in the tree; the change moves that single render to the owning stage exit with no payload, CLI or signature drift (verified by base/head render-count counterfactual, deep payload equality against the eager composition, a direct probe showing the packet does not depend on interaction_contract, and 97 localized tests plus green checks at 562ca62). It is a proportionate refactor, not a behavior change; merging remains a maintainer decision.

…acket-rendering

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

@huangruiteng huangruiteng 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.

Reviewed exact head: 659810367bdf4abb07cb23f07f00974a7288f080 (codex/coalesce-live-packet-rendering, re-review after the branch was merged with main be7789fd9).

动机

discussion #4738 的 PR-04:当 turn-start required reads 与 pending capability intent 同时改动一个 live decision 时,projection 阶段会渲染 protocol_action_packet 两次,而中间那一份没有任何读者。这不是"多花一点 CPU"的问题,更重要的是它让"哪一刻的 packet 是权威的"变得含糊——后来读这个函数的人必须自己去推断两次渲染里哪一次算数。

改动思路

让两个 projection helper 只报告是否发生了改动,把渲染收拢到阶段出口一次完成,仍然放在 unsettled-host recovery 之前(recovery 需要完整 decision)。interaction contract 的构造与 precedence 顺序不变。这样保留了原本"唯一一处构建 packet"的性质,只是把构建时机明确成"所有 projection 都改完之后"。

本 head 相对上次评审的唯一变化是与 main 的合并:冲突正好落在这一段——main 给 recovery 调用加了"settled receipt 归本轮所有,不去找更早的 unsettled Turn"的保护。解法是两者并存:本分支的一次渲染保留,recovery 调用继续挂在 main 的 receipt_bound_replay_phase is not ReceiptBoundReplayPhase.SETTLED 之下;settled 时跳过 recovery,但只要有 projection 改动,packet 仍然渲染。

具体改动

2 个文件、+123/-12(相对当前 main):

  • loopx/control_plane/quota/live_decision.py(+16/-11):_project_turn_start_required_reads 与 _apply_pending_capability_intent_precedence 的返回值由 None 改为 bool(早退返回 False,应用成功后返回 True),不再各自写 payload["protocol_action_packet"];build_live_quota_should_run_decision 用 packet_changed = packet_changed or intent_changed 汇总,在阶段出口 if packet_changed: 渲染一次。
  • tests/control_plane/test_effect_turn_live_quota_decision.py(+107/-1):新增 test_live_projection_stage_renders_one_complete_packet,覆盖 active/paused × reads × absent/pending/invalid intent 共 12 组;并把既有的 prior-unsettled-recovery 用例加上 reads 参数,让 recovery 路径在有/无 reads 两种情况下都被走到。

关键代码讲解

  • live_decision.py:184(_project_turn_start_required_reads):唯一的语义要求是"没投影就别让调用方渲染",所以早退一律 return False,做完投影才 return True。这个 False 是新测试里 len(rendered) == 0 那一侧的来源。
  • live_decision.py:270(_apply_pending_capability_intent_precedence):absent / 非 pending / 缺 summary 或 command 的投影都返回 False——invalid intent 必须不算改动,否则就会退化成"为了一个无效投影多渲染一次"。
  • live_decision.py:593(build_live_quota_should_run_decision):packet_changed 用 or 汇总两个 helper 的结果,渲染点紧贴 recovery 之前。这条注释把不变量写清楚了:两个 projection 都不消费中间那份 packet,而 recovery 需要完整 decision——所以要在这里收口。
  • test_effect_turn_live_quota_decision.py:1007:新测试的做法值得单独说。它 monkeypatch 掉渲染函数记录调用次数与当次 payload,再把 recovery 包一层,断言"进入 recovery 时 packet 等于当前 payload 的重新渲染";随后按旧顺序(每个 helper 各自 eager 渲染)重放一遍,逐字段比较完整输出,包括 summary 顺序。也就是说它证明的是等价,不是"看起来跑得通"。

我复核的关键点(都是自己跑的):

  • 本 head:tests/control_plane/test_effect_turn_live_quota_decision.py 28 passed;连同 TurnResult / prompt upgrade / periodic lookback / quota CLI projection 五个相关模块共 103 passed。
  • base 对照:在独立 worktree(origin/main)里放入这个 head 的测试文件跑同一用例,combined 场景在 active 与 paused 两种状态都失败于 assert 2 == 1(实测两次渲染)。这正是 PR-04 描述的缺陷,也说明这个测试不是"为了绿而写"。
  • loopx canary premerge --from-git-diff:status passed、self_merge_allowed: true、manual_holds: 0,零失败;git diff --check 干净。

遗留问题

无阻塞发现。唯一需要如实说出的覆盖边界:如果将来有人在渲染点之后插入新的 projection 站点,len(rendered) 这个断言不会发现;能兜住它的是 recovery 边界上的 packet 等价断言,而且只有在那个新站点真的改动了参与渲染的字段时才生效。这是这类"计数 + 等价"双断言组合的天然边界,不是本 PR 的缺陷。

对主干的风险

风险面很窄,但值得写清楚:真正危险的不是多渲染一次,而是渲染时机错了——如果渲染被挪到 recovery 之后,或者新 helper 改了 payload 却不报告,goal 就会基于与 interaction contract 不一致的 decision 关闭。本 head 用"渲染贴紧 recovery 之前 + recovery 处断言 packet 等于新渲染"两面夹住这一点。合并解析方面:main 的 settled-receipt 保护被完整保留(settled 时不去翻更早的 unsettled Turn),本分支的一次渲染叠在它之前,两者互不覆盖。没有运行时状态、持久化或权限面改动;回退成本一个 commit。

我的整体评价

结论 APPROVE。这是一次"把重复收敛、把权威时机写实"的重构:16 行生产代码换来一个渲染点,等价性有测试证明(并逐字段比对旧组合顺序),base 复现出的 2 == 1 说明它修的是真问题;合并解析把 main 的 settled-receipt 保护原样保留。

需要说明一点流程事实:本 PR 触碰 loopx/**,也就是仓库自己的规则里"不由作者自合并、留给维护者"的控制面范围;本次是维护者授权下的合并决定,评审证据以本 head 为准。

English verdict: APPROVE - exact head 6598103 (re-review after the branch merged main be7789f; the single conflict was this branch's coalesced render against main's receipt_bound_replay_phase is not ReceiptBoundReplayPhase.SETTLED guard on the recovery call, resolved by keeping both). The projection helpers now report whether they changed the payload instead of each rendering the packet, and the live entrypoint renders once at the stage exit before recovery, so the intermediate packet with no reader is gone. Equivalence is proven rather than asserted: the new parametrized test counts render calls, asserts the packet handed to recovery equals a fresh render, and then replays the previous eager composition order to compare every output field including summary order. I reproduced the base behaviour in a separate worktree with this head's test file: the combined case fails on origin/main with two render calls (active and paused), and passes here with one, while the no-reads/no-intent cases assert zero renders so the change adds no work. Live-quota-decision tests are 28 passed, the six related modules are 103 passed, canary premerge reports passed with self_merge_allowed true and manual_holds 0, and git diff --check is clean. No blocking finding; the one honest coverage limit is that a future projection site added after the render point would be caught only by the recovery-time packet-equality assertion, and only if it changes a rendered field.

@huangruiteng
huangruiteng merged commit 6e5d540 into loopx-project:main Sep 20, 2026
5 checks passed
@songoow
songoow deleted the codex/coalesce-live-packet-rendering branch September 28, 2026 03:00
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