Settle blocked Turns without spend and back off the exact Todo - #5005
Conversation
63af166 to
76f6f31
Compare
4eb8441 to
ca0de4b
Compare
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
ca0de4b to
d2c8199
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Exact head: d2c8199
动机
#5000 的实际故障是:Agent 对已选中且要求完成验证的 Todo 如实写入“被 peer 硬租约阻塞”,回执已持久化,随后配额扣减却被终态验证器拒绝,原 Turn 无法结算。这个 PR 让该精确 Turn 在不假装完成 Todo、也不扣额度的情况下闭合,并给同一 Todo 一个有界重试时间;下一轮可以继续做其他合格任务。
改动思路
权威仍分开:Todo 的完成、租约和验证规则保持在原 Todo 存储;已提交的 Turn Run 只记录本次阻塞与五分钟重试;TypeScript 结算读回判断它是否足以构成“不扣额度”的 closeout。调度器从完整 Run 索引导出等待,再投影进现有 resume_at 选择规则,不另建可写的调度状态。旧 Goal 使用已写在 Todo 上的有界 resume_at;File/SQLite 的 peer 租约不允许被阻塞者改 Todo,因此改由精确 Turn 回执持有等待。缺失或身份不匹配的证据仍拒绝。
具体改动
关键代码讲解
require_blocked_retry_wait 核对同一未完成 advancement Todo、时区与 1–30 分钟期限;对于已推广的本地权威,开放且未设恢复条件的 Todo 可得到五分钟 Turn 回执。readQuotaSettlementFromRequest 只在原写回与回执匹配、存在 typed blocker 和 bounded retry、且没有已提交或半提交的扣额证据时,返回 typed_blocked_writeback_no_spend;旧扣额仍保留。goal_semantic_history_from_runs 从完整 Run 序列提取尚有效的等待,避免展示窗口挤掉调度事实,overlay_active_turn_retries 仅影响同一 Agent/Todo 的选择。cached_goal_run_index_is_current 使用原有 sha256:<值> 格式,在新 Run 落盘后让旧状态缓存失效,未变化时保留缓存命中。host preflight、Vision checkpoint、回放阶段和公开配额文档同步了这一条结算语义。
对主干的风险
最强反例是旧状态缓存:首次实现的直接路径测试虽通过,下一轮读缓存仍会重新选中被阻塞 Todo;修复前 File、SQLite 两种真实 CLI 用例均能复现,现已同时验证“未变化命中”和“新 Run 后失效”。另一个风险是把无效阻塞写回当成免费执行,所以缺失恢复期限、错 Todo、半提交扣额与旧的真实扣额均保留严格失败或原读回。最终 head 的完整 74 项 Python 结算套件和 91 项 TypeScript 结算/恢复/愿景用例通过。23 个改动文件的公共边界扫描零错误;当前精确 head 的 DCO、依赖检查、两项构建及 PostgreSQL 真服务 CI 已通过,Python Tests 工作流仍在运行。生产中租约释放不会即时触发唤醒,仍由现有 host 节奏在到期后重查。
我的整体评价
我没有发现当前 head 的阻断性代码问题。改动横跨 23 个文件,但新增的 Run 字段、TS 读回、完整历史投影、缓存围栏和回归测试共同封闭 #5000 的实际链路;只放宽扣额验证或强改被 peer 持有的 Todo 都不能保持现有权威边界。旧扣额回执兼容保留,新的无扣额语义已写入配额文档。剩余限制是五分钟为最早重试期限,不保证准点唤醒;合并仍需独立维护者评审。
English verdict: APPROVE - d2c8199; exact blocked Turns close without spend, the stale-cache counterexample is fixed, and full local settlement plus focused TS validation passed. Python Tests CI and independent maintainer review remain required.
Goal and resulting behavior
A managed Turn selected for a completion-validated Todo can hit an overlapping peer hard lease before it can implement or validate anything. Previously an exact blocked
outcome_gapwriteback persisted, but the returned quota-spend step was rejected by the terminal validator, leaving the Turn unsettled. This PR closes that exact typed Turn without spending quota and records a bounded retry for the same Goal/Agent/Todo. The original Todo remains open with its validator and lease rules intact.For canonical File/SQLite authority, the blocked Agent need not mutate the peer-gated Todo: the committed Turn owns a five-minute retry. The next quota decision projects that wait into selection, may choose an independent eligible Todo, and cannot immediately reselect the blocked one. The wait expires automatically; a newer work Run on the same Todo supersedes it. An explicit pending Todo
resume_atremains supported for legacy/agent-owned waits. The retry is derived from the full run-index semantic projection, so unrelated runs cannot evict it from a bounded display history. Scheduler cache reads also compare the cached run-index digest with the durable Goal run index; a new receipt forces a fresh status projection instead of replaying a stale selection.Related to #5000.
Entry points and scope
refresh-state, exact settlement readback,quota spend-slot, and subsequentquota should-runnow agree on the no-spend closeout and bounded selection retry.Validation
python -m pytest -q tests/control_plane/test_quota_settlement_cli.py— 74 passed on the final head after the cache-fence change (560.65 seconds). The real CLI regressions include an overlapping peer hard lease on both File and SQLite authorities, exact replay, no debit, unchanged original Todo validator, and next-Turn selection.python -m pytest -q tests/control_plane/test_quota_blocked_retry_projection.py tests/control_plane/test_quota_settlement_cli.py::test_typed_blocked_retry_with_peer_hard_lease tests/control_plane/test_run_context_retention.py— 15 passed. This covers wait expiry, supersession, agent scoping, and a retry retained despite 40 newer display runs.run_index_changedmiss after blocked writeback. The stale-cache scenario failed for both authorities before the fence.main, focused Python File/SQLite, retention and capability-memory tests passed (7 tests), and the three TypeScript settlement/host/vision suites passed (91 tests). A later-subject scope regression passed separately; the blocked Run does not defer a Todo created after it.npm run typecheck:control-plane, targeted Ruff lint, Python compile, andgit diff --checkpassed.loopx checkpublic-boundary scans passed for all 23 changed files with zero errors. The scan reported two pre-existing, unrelated Goal state-projection warnings.Tests use disposable fixtures; no production Goal, provider, exchange, or account was mutated. The retry is a minimum five-minute selection hold, not an immediate event-triggered wake on lease release. The existing host cadence supplies the next wake. CI and exact-head maintainer review remain required before merge.