Repository navigation
fix(quota): close unavailable monitor attempts without poll or spend - #6178
loopx-agent wants to merge 3 commits into
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=xhigh
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
自动监控调用者在来源不可访问时,需要如实记录尝试并继续独立任务。 旧版要求为不可观察的尝试提供监控结果,原 Turn 无法关闭;新版以证据关闭原尝试,再由当前恢复 Turn 选择独立工作。 File/SQLite 实际 CLI 已验证零 poll、零扣额、观察字段保留和恢复命令消费。 本 PR 只修共享结算与恢复路径,不解决来源访问,不证明账户观察或外部消费者采用。 正式发布、本机原 effect 恢复,以及 PostgreSQL 和 App/Lark 打包入口仍待各自资格化。 这里的 Turn 指一次已准入、具有固定身份的执行片段。旧实现让恢复不断要求一个无法取得的结果,阻断后面的独立工作;修复应关闭失败的尝试,同时如实保留未知来源状态。
改动思路
同一原身份、证据门槛和持久回执由既有 quota owner 统一决定,renderer 只投影动作。 交付完整 CLI/File/SQLite 无观察结算与恢复接续片段;来源访问和正式部署另验。 从已准入的 quota guard 经 refresh-state 到精确持久回执,再重新进入当前 recovery Turn。尝试不可观察是调用者提供证据的事实,不能从“没有 poll”自动推断;它也不代表来源恢复。只修改 preflight 会留下 poll_due 和 hash-only 恢复包,只跳过 Turn 或套用 advancement 五分钟重试则丢失义务或制造等待。当前方案复用同一 owner,Python 仅渲染命令,没有平行控制状态。
具体改动
评审 head 082a8e1dc5b12a7d19870d7c0ea2292cd39bb187。独立规范依据为 docs/reference/protocols/quota-blocked-causal-closeout-v0.md,spec_revision f96be230c34b61d98411eaf949bf042065daa2dd;按改动前的要求审查,不能用本 PR 新写的说明作为自己的验收依据。以下名称是对该文档段落的描述性 criterion_id:Identity-evidence 对应原身份、准入、typed blocked 与持久证据,CLI 错绑/缺证负例通过;No-debit 对应零扣额、无交付信用和幂等,File/SQLite 精确重放与回归通过;Retained-task 对应任务未完成、验收器及等待语义保留,新测试冷读 OPEN 与原观察字段;Legacy-retry 对应旧 retry 保留,同输入 advancement 的基线与 head 观察指纹完全一致。
关键代码讲解
blocked_wait.ts:101 prepareBlockedWait对唯一、开放、active 的 Agent Monitor 冻结quota_monitor_unavailable_v0,不制造 due_at。原 advancement 分支保持原有五分钟或因果等待。settlement_phase.ts:105 receiptBoundMonitorPhase只消费结算 owner 已核验的 unavailable 回执,因此没有 poll 仍可关闭此次尝试;不会把任务完成或业务观察写入 Monitor。settlement_readback.ts:996 readQuotaSettlementFromRequest继续要求原 effect、typed blocked 和持久回执,已有扣额不会被抹去;真实 poll 优先,冲突的 unavailable 写回在追加前拒绝。既有 material poll 的正常补充路径保留。unsettled_host_turn_contract.py:8 recovery_cli_actions与 TS 原恢复 recommendation 一起给出观察成功/无法观察两条分支,均固定旧身份,最后重入当前 Turn。新 CLI 测试直接消费返回命令,随后选择独立新任务,而不是由测试自行发明恢复动作。
settlement_plan.ts 同源说明 blocked closeout 后不要 spend;两个测试源覆盖新语义及旧错误文案,双语协议解释新证明、旧 reader 和降级限制。共九个文件,六个产品源、两个验收源和一个协议文档,没有私有 payload 或新增 capability。
对主干的风险
最强风险是把失败尝试记成真实观察、改变检查时钟或让旧义务吞掉独立工作。File/SQLite 新 CLI 八例验证 clock/hash/generation 保留、零 poll/debit、缺证/错绑拒绝、能力不足拒绝及恢复、已 poll 后冲突写回拒绝、精确重放和同一 recovery Turn 接续。六组相同输入的不可变基线/head 原生 CLI 对照中,旧版两 provider 的 unavailable 被 advancement-only 拒绝,head 成功;普通 advancement 的结果相同。合成环境使用真实 CLI、TS 子进程、provider 与 lease,没有 mock 出待证结算结果,也没有验证外部来源访问。
旧等待/poll 相关 47 个语义用例由首次 46 通过和修正后的四项 projection 重跑覆盖,另外 13 个 recovery/monitor CLI 兼容用例通过;207 项 TS、typecheck、Ruff 和 public boundary 检查通过。早期缺少合成 lease proof、已 poll 冲突追加以及诊断文本断言过窄的失败均保留,最终八例重新执行覆盖这些条件,没有通过删除语义断言或放宽预算取得成功。wait_for_ci=false,未获取或等待远端 CI。
语义与 CI 对齐
新证明扩展既有 typed quota vocabulary,旧 blocked_retry 槽和共享 predicate 为活动调用者及历史回执保留;新证明不进入 advancement retry overlay。semantic advisory 不能证明等价,实际 semantic drift 与 CLI 输出预算的基线/head 检查均通过。本改动没有默认关闭能力的声明或开关;缺已有 network 资格仍拒绝,恢复相同资格后才可准入。共享规则使用 Monitor/Turn 等中性术语,must-attempt 及精确结算仍由机器执行,命令提示不授予额外权限。
我的整体评价
未发现阻塞问题,结论 APPROVE,交付判断为 justified_increment。long_horizon 和 user_experience 在已验 CLI 路径上 improved:旧 effect 不再无限捕获后续独立工作,代理可从恢复包找到真实可执行动作,不需编造结果或重复录入已知身份。机制 proportionate,architecture retain;上述边界是同一问题的完整片段,发行和外部来源不应与结算 authority 混合。既有判定/回放 owner 被复用,没有第二套 quota 或 Python 判断规则。
正式发布、本机原 effect 恢复,以及 PostgreSQL 和 App/Lark 打包入口仍待各自资格化。 本 PR 没有自行合并/升级 authority,也不证明 source healthy、账户事实或金融效用。新持久证明需要兼容运行时核对后才可降级;来源持续不可用时仍按原 cadence 尝试,本 PR 不改变调度。App/Lark 打包入口没有单独运行,应保持未验。此 author-owned COMMENTED 是评审结论,不是 GitHub 正式批准或合并授权。
English verdict: APPROVE - 082a8e1; exact unavailable Monitor attempts close without poll/debit and the real recovery packet restores independent continuation; File/SQLite CLI, 207 TS, type/static/semantic/output checks passed. Release and live adoption remain unqualified.
loopx-agent
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | xhigh
Exact head: 082a8e1dc5b12a7d19870d7c0ea2292cd39bb187. REQUEST_CHANGES for a reproduced competing-effect/replay defect. The earlier same-head approval does not cover the reverse ordering below.
动机
自动监控调用者在尝试读取来源却拿不到观察时,会进入这条恢复路径。 旧版要求提供无法取得的 poll 结果,原 Turn(一次固定身份的执行尝试)因此一直未结算;新版允许带真实阻塞证据关闭这次尝试,再让当前恢复 Turn 选择独立工作。
File/SQLite 的实际 CLI 已验证无观察写回能够关闭原尝试,且当时不新增 poll、不扣额、保留观察字段;但同一旧身份随后仍能追加 poll,改写时钟和 hash,并使原写回重放失败。 本 PR 不恢复来源访问,不证明账户状态、发行或本机采用,也不新增 capability、设置、权限或前端操作。
这不是仅凭计数判断交付:正常 unavailable 结算已经工作,缺口发生在后续重试与读回。一次成功回执不能允许同一历史尝试再记录互斥的新结果。独立工作接续是实际验证过的改善;来源本身是否健康仍未知。
改动思路
扩展现有 quota owner 和冻结回执是相称的最小机制;缺口在于新 terminal outcome 只接入 settlement reader,没有接入同身份 poll 写入准入。 本 PR 的适当边界是 CLI/File/SQLite 的共享 quota 恢复和精确回放;来源访问、PostgreSQL、App/Lark 打包采用及发行另外资格化。
不改会保留恢复死循环;编造 hash、直接跳过义务或套 advancement 重试定时器都会改变真实业务事实。现方案复用 TS quota owner,Python 仅投影原身份命令,没有第二个决策源。应把新终态在同一 owner 内贯通至写入准入,不能只改读回的 phase。
[P1] unavailable 结算后同身份仍能追加 poll,随后破坏精确重放
独立 File、SQLite 实际 CLI 均复现:先准入并取得自己的 lease,消费真实 recovery packet 的 refresh-state unavailable 命令,得到 settled / typed_blocked_writeback_no_spend;随后保留同一个 Goal/Agent/Todo/Turn 与仍有效的 lease,执行 quota monitor-poll --result-hash late-observation --execute。结果 rc=0, appended=true,追加一条 poll,改写 last_checked_at、next_due_at、result_hash。再原样重放先前 refresh,得到 rc=1, committed_writeback_payload_conflict。没有实际扣额:spend 仍返回 no-op。
新 settlement_readback.ts:1177 只拦“已有 poll → unavailable”,没有拦“unavailable → poll”;未改的 monitor_poll_commit.ts:634 admission 仍按当前 due Monitor 放行。这是同一 Monitor 的原结算身份,不是已结算 advancement 的独立 auxiliary Monitor。
最小修复:在既有 typed monitor-poll admission/commit 边界、provider CAS 之前识别同一精确身份已提交的 unavailable closeout,拒绝迟到的新 poll;保留已提交 poll 的幂等重放及已结算 advancement 的独立 auxiliary Monitor。 补 File/SQLite 两种提交顺序、迟到 non-material/material poll 的拒绝和原写回重放;断言原观察字段、单条 unavailable、零新 poll/debit,保留合法 poll 重放和 advancement auxiliary 路径。
具体改动
规范依据为改动前 docs/reference/protocols/quota-blocked-causal-closeout-v0.md,固定 spec_revision f96be230c34b61d98411eaf949bf042065daa2dd,原规范。文档没有编号,以下 criterion_id 直接描述其已接受条款:Exact identity and durable evidence(准入、原身份、typed blocked 与回执)已验;Frozen historical readback and exact refresh retry(冻结历史事实、原样重放)因上述负例未满足;No debit and unfinished Todo(无扣额、不完成 Todo)已验;Legacy retry and independent continuation(旧等待与独立接续)在相关 source 回归中通过。新加入的说明不能作为自己的独立验收依据。
完整差异九文件 +344/-12:六个 quota/host 产品源、两处测试源、一个双语协议。新真实 CLI 测试有199行,覆盖原身份恢复包消费、缺证/错绑/能力不足拒绝、恢复能力、观察字段保留、重放、独立接续和已 poll 的冲突写回。旧 projection 只更新更宽 qualifier 的错误提示断言;拒绝语义保持。
关键代码讲解
blocked_wait.ts:100 prepareBlockedWait:开放、active 的唯一 Agent Monitor 在 canonical 原结算路径冻结quota_monitor_unavailable_v0;保留 clock/hash/generation/cadence/expiry,不生成 advancement due_at。原 retry/causal 分支保留。settlement_phase.ts:106 receiptBoundMonitorPhase:消费 owner 核验的 unavailable flag 即可返回 settled,即使没有 poll。它只是 reducer,不能代替 provider 写前的终态检查。settlement_readback.ts:1004 readQuotaSettlementFromRequest:原 guard/effect、typed blocked、持久回执仍必需;1140 将 unavailable 接入 phase,1177 新冲突规则却在后来 poll 出现时拒绝原 refresh。下一次策略变更应复用这个 typed terminal owner,避免另建状态来源。unsettled_host_turn_contract.py:8 recovery_cli_actions与 TSrecoveryObligation:同源提供 verified-observation poll 和 unavailable blocked writeback,固定旧身份,随后重入当前 recovery Turn。Python 不决定结算或来源事实。settlement_plan.ts相应说明无扣额分支;文档明确旧 runtime 降级前的回执核对限制。
CLI/managed Turn 是当前改变的入口;没有前端设置、Lark 命令或访问授权变化。App/Chat 消费同源命令提示,但 packaged App/Lark、PostgreSQL、发行及本机实际采用均未由本次证据资格化。
对主干的风险
最强风险是原身份出现两种互斥的持久结果,监控事实被迟到重试更新,而原样 replay 反而报冲突。本次双 provider 使用真实 CLI、TS 子进程、canonical store、lease、journal,没有 mock 出结算结果;来源和 observation 值是合成的,不证明外部访问。
独立 source 验证:uv run --extra test python -m pytest tests/control_plane/test_monitor_unavailable_closeout_cli.py tests/control_plane/test_causal_blocked_closeout_cli.py tests/control_plane/test_quota_blocked_retry_projection.py tests/control_plane/test_monitor_poll_transaction_recovery.py tests/control_plane/test_superseded_turn_settlement_cli.py tests/control_plane/test_auxiliary_monitor_poll_availability.py tests/control_plane/test_closed_monitor_replay.py -q,74 passed。两个相同输入的 base/head provider 对照,实际 base 都拒绝 unavailable(advancement-only、无追加),head 都成功 settled、零 poll/debit、字段保留。另加的八项探针中四项 unavailable/capability-restored 迟到 poll 失败,四项原有观察/拒绝场景通过;进一步两条完整 File/SQLite trace 都确认字段改写与 replay 冲突。故原测试全绿不覆盖反向顺序,不能作为批准理由。
Control-plane typecheck、changed Python/test Ruff 与 diff vocabulary advisory 通过。最初把新增完整测试直接放到基线,六例在新提示文字断言处失败、两例通过;该结果仅证明提示差异,未当成 runtime 缺陷证据。随后同输入的直接 CLI probe 才证明上述旧拒绝/新准入差异。无产品文件改动、删语义断言或放宽预算。
语义与 CI 对齐
这是既有 typed quota vocabulary 的持久 variant 扩展;advisory 没检测到单常量定义不代表没有语义。blocked_retry 槽与旧 predicate 因真实活动调用者、历史回执而保留。协议不宣称 opt-in/default-off;既有缺 capability 拒绝和恢复受实际测试覆盖。通用文案保持 Monitor/Turn 等中性概念;must-attempt/准入/证据义务由机器执行,提示不授予额外权限。
当前 main ruleset 要求一名合格审批、Code Owner、最后推送资格及 thread resolution;CODEOWNERS 保留 maintainer 与 agent 路由,但 author-owned COMMENTED 不是平台正式审批。本次 wait_for_ci=false,未获取、轮询或等待 CI。完整 semantic/output-budget/canary、normal-wheel installed caller 和 packaged host 未独立执行;REQUEST_CHANGES 的依据是当前真实运行反例,不能把作者检查或未执行资格当作通过。没有要求无关全量 RFC、付费模型调用或长 soak。
我的整体评价
REQUEST_CHANGES。机制与问题相称,现有 owner 足够,但 accepted exact frozen-replay 条款在这个 head 确实失败,当前交付判断为 not_yet_proven。未来相关重构检查选中同一 terminal predicate 与 poll admission 的小范围复用;不需要新 provider、并行 Python rule、独立调度或广泛语言迁移。补齐两种提交顺序的互斥和 replay 后再评审新 head。
已读回另一份同 head APPROVE;其中验证了 poll 后 unavailable 拒绝,本结论提供尚未覆盖的相反顺序证据,不继承或删除其讨论。精确源码反例既没有证明扣额错误,也没有验证 material successor effects,应保持这两个结论的边界。控制面合并仍由维护者处理。
Motivation
Monitoring callers may attempt a source read and fail to obtain an observation. Previously the original fixed-identity execution attempt could remain unsettled because recovery demanded a poll result it could not truthfully provide. The new path closes that attempted outcome using blocker/evidence and lets the current recovery Turn select independent work. Real File/SQLite CLI confirms the initial closeout creates no poll/debit and retains observation fields. However, a later poll on that same closed identity succeeds, mutates clocks/hash and makes the original refresh replay fail. Source access, release, live adoption and new permissions are outside this PR.
Change strategy
Extending the existing typed quota owner and frozen receipt is proportionate; a fake hash, unconditional skip or advancement timer would change truth or lose obligations. Python remains a command projection. The defect is that the new terminal outcome reaches settlement readback but not the competing poll writer's admission. The appropriate boundary is shared CLI/File/SQLite exact settlement/recovery, with PostgreSQL and packaged/live host adoption separately qualified.
Implementation
The complete nine-file diff extends blocked_wait, phase, plan, readback, TS recovery obligation and Python recovery commands; it adds a199-line real CLI test, adjusts one existing diagnostic assertion and documents the new variant bilingually. The pinned pre-change specification and four criterion IDs above supply the independent acceptance frame. prepareBlockedWait freezes an open canonical Monitor without a retry clock; receiptBoundMonitorPhase now settles its owner-qualified proof; readQuotaSettlementFromRequest rejects poll-first unavailable refresh; recovery_cli_actions preserves original binding and current recovery reentry. There is no new UI/config/authority. The existing legacy proof reader and actual auxiliary Monitor caller remain necessary.
Main risk
P1: after committed unavailable closeout, same-identity late non-material poll is accepted with a valid lease on both providers: rc0/appendedtrue, one poll, updated last_checked_at/next_due_at/result_hash. The unchanged original refresh then returns rc1/committed_writeback_payload_conflict; spend remains a no-op with zero debit. This is the original bound Monitor, not an independent auxiliary observation. Fence its late poll before provider CAS using the same typed terminal owner, retain genuine replay/auxiliary behavior, and test both competing commit orders including material/non-material cases.
Independent source checks passed74 cases; identical base/head runtime probes establish the intended new admission. Four extra reverse-order cases fail despite the positive suite; two captured provider traces confirm mutation and broken replay. Typecheck, focused Ruff and advisory passed. Initial baseline guidance-only failures were not mistaken for runtime attribution; the direct paired CLI probe supplies it. Full semantic/budget/canary, installed wheel, PostgreSQL and packaged/live host qualification remain unverified. No remote CI was consulted. These synthetic real-provider tests do not prove live source access or material successor effects.
Overall assessment
REQUEST_CHANGES: the narrow architecture fits, but its new terminal outcome is not protected against a later competing effect and violates accepted immutable exact replay. The bounded future-facing repair belongs in the existing shared terminal/admission owner. The prior same-head approval covers only poll -> unavailable refusal, so it cannot settle this reverse-order counterexample. Review records and GitHub aggregate approval/merge authority remain distinct; do not rewrite historical receipts to hide the conflict.
English verdict: REQUEST_CHANGES - 082a8e1; unavailable -> same-identity late poll succeeds on File/SQLite, mutates observation fields and breaks original refresh replay. Source74/type/lint passed; reverse-order effect fencing is required before approval.
An admitted continuous Monitor can attempt its observation and find the source unavailable. The direct blocked no-spend writeback qualifier previously accepted only advancement Todos, leaving an unavailable attempt on an active Monitor unsettled and exposing only the poll-hash recovery command.
The existing typed quota owner now closes that exact attempt with blocker/evidence-backed
outcome_gap, preserves Monitor clocks/hash/generation and creates no poll or debit. The recovery packet exposes both the verified-observation poll and unavailable-observation writeback commands using the original identity; after closeout, the same current recovery Turn can select independent work. A committed poll rejects a conflicting unavailable claim. Ordinary advancement retries and poll replay retain their behavior.An existing accepted lifecycle closeout already provides another recovery route: record a genuine persistent blocker under the current execution lease, release that lease, and apply the narrow
blockedlifecycle transition through the owning Todo authority. Independent work can then resume without installing this PR. This change adds attempt-level closeout while preserving Monitor status; it does not replace the existing lifecycle route or make a product release a prerequisite for independent work.Validation uses real CLI and canonical File/SQLite providers: unavailable closeout, actual projected-command consumption, exact replay, independent recovery continuation, missing evidence/wrong identity, capability rejection/restoration, and committed-poll rejection. Targeted existing blocked/poll/recovery regressions, 207 TypeScript checks, control-plane typecheck, Ruff, semantic drift and CLI output-budget checks passed. One legacy error-text assertion was updated for the broader qualifier; its rejection semantics are unchanged.
This is the bounded R1/S2/S10 quota recovery slice documented in the existing blocked-closeout protocol. It adds no account adapter, new authority or capability switch. PostgreSQL, packaged App/Lark qualification, release and live consumer adoption remain separate. Rollback must reconcile the new proof receipts with a compatible runtime first.