fix(quota): keep long-history closeout responsive and unify receipt reads - #5050
Conversation
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>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
长期运行的 Goal 在 prior-host-Turn closeout 中反复读取历史 JSONL;已有单次请求索引仍会跨请求重复解码。约 7,010 个历史 Turn 的现场 preflight 曾接近外层超时。需要缩短重复读取,同时保持旧回执冲突、精确 monitor 提交和失败重试的权威语义。本文结论仅针对 #5050 的精确 head 723fc33cadd977b7bfbd7c8f9ec9a4dec6cc2fe3。
改动思路
我先检验了更小的选择:单纯增加超时不能去掉随历史增长的重复解码;持久索引又引入新的写入/迁移权威。当前方案每次读取新鲜字节,只在已换行的旧前缀 SHA-256 完全相符时复用解析结果;缓存可丢弃,磁盘回执仍是唯一事实。另把 monitor 已提交判断复用 TS settlement 规则,删除 Python 第二次 run-log 扫描。第一次审查发现“只读查询丢响应”仍被外层 CLI 误报为回执写失败;最终 head 已补齐公开投影和真正的 CLI 回归,而未放宽真实写丢响应的保守处理。
具体改动
readReceiptLogSnapshot:新鲜全量字节校验、增量解析完整前缀,未结束尾行每次重读;改写、截断、删除/重建、损坏行不能借缓存逃过检查。committedMonitorPollFromSnapshot/preflightPriorHostTurnCloseout:只接受同 Goal、Agent、Turn、Todo 和精确 effect 的已提交 poll;后来的未提交观察不遮住早先的精确提交。_prior_closeout_preflight、quota_failure_payload、handle_quota_command:只读查询丢响应返回quota_closeout_query_unavailable/control_plane_health_repair,保留已有心跳回执或标记not_committed,建议检查 runtime 后同 Turn 重查;真正写丢响应仍是write_failed。
关键代码讲解
receipt_log_snapshot.ts:81 的哈希核对而非 mtime/大小决定复用;settlement_readback.ts:322 是 monitor 提交的共用规则;unsettled_host_turn.py:101 只做 TS 响应与 Todo 事实的桥接;quota_failure_report.py:68 和 quota.py:325 才拥有操作员看见的失败分类与回执视图。最后两层已补上先前遗漏的诊断传递。
对主干的风险
独立复跑最终 head:相关 TS 测试 100/100、Python closeout/CLI 测试 14/14、action-selection/heartbeat-receipt 测试 4/4、TS 类型检查、semantic-vocabulary drift smoke 与 git diff --check 均通过。同一合成输入对比 base/head:只读查询由误报 write_failed 改为查询不可用;真实写丢响应的 write_failed 路径保持不变。新增 8 组合 CLI 测试覆盖有/无已有回执与 compact/envelope。作者报告 canary 19/19;我没有把该报告当作独立复跑。
残余边界:每次仍需读取并哈希完整历史,128 MiB 是保留原始输入量的上限,不是 JS heap 硬上限;原始短暂主机调度原因未稳定重现。RFC 如实保留 D2 容量/retention 和后续默认切换门槛,不应把本 PR 视为其完成。
我的整体评价
未发现最终 head 的阻塞项。这是已有 closeout 路径内可逆、可验证的性能与失败语义修复;没有新增持久权威,也没有把只读故障伪装成成功。代码审查结论为可接受;control-plane/runtime 合并仍由维护者按独立 gate 决定,本 review 不执行合并。未来相邻重构已核对:共用解析和 monitor 规则、移回回执 owner 的 not_committed 视图足以消除本次重复权威;更大持久索引留在独立容量工作包。
English verdict: APPROVE — exact-head review passed; no blocking finding. Maintainer merge gate remains separate.
|
Exact-head delivery decision for Changed surfaces: shared TS receipt-history reads, prior-Turn monitor closeout, Python CLI failure projection, semantic producer registry and RFC checkpoint. Local validation: 51 focused and 124 integration TS tests; 159 combined Python tests; TS typecheck, Ruff, configured/focused Mypy, public-boundary scan, real-history base/head profiling and active-provider read-only recovery. Final clean-head premerge canary executed 19 checks with zero failures and no manual holds. Exact-scope change-quality receipt An intermediate vocabulary check exposed a missing producer declaration, now fixed. A concurrent author edit triggered an intermediate canary side-effect guard; the final complete canary was run on an unchanged clean head. A broad ad-hoc Mypy invocation followed unrelated untyped imports; repository-configured and focused checks passed. CI is intentionally not consulted under this Goal's The independent review also passed 100 TS tests, 14 closeout/CLI tests, 4 action-selection/heartbeat tests, typecheck and vocabulary checks. Coverage includes exact identities, stale/rewritten history, malformed and partial tails, retained receipt reuse and public query/write failure distinctions. The future-facing refactor consolidates actual receipt ownership and removes duplicate Python IO. Fresh reads still scale with history, the retained-source budget is not a heap ceiling, and the transient host scheduling cause remains unproven. The RFC's three migration implementation packages and separate D2 evidence remain open. |
|
Post-merge local acceptance: installed an immutable release from merge commit Against the real long-lived Goal, read-only preflight completed in 0.577 / 0.117 / 0.099 seconds. The full preflight → canonical Todo lookup → typed recovery returned no pending recovery obligation. Recovery plus packaged-bundle verification took 2.60 seconds. These are local observations, not new latency guarantees or SQLite D2 qualification. No Goal promotion, registry/fence update, Todo mutation or fabricated settlement was used for this acceptance. The previous immutable release remains available for code rollback. |
Problem and outcome
Long-running Goals can repeatedly decode their entire receipt history before a heartbeat may proceed. A live closeout preflight exceeded its five-second response budget; a later read-only query and same-Turn retry recovered. Per-request indexing was already present. This change removes redundant parsing across requests and the second Python monitor-log scan, while preserving complete-history validation and the five-second budget.
Changes
not_committed; ambiguous writes retain conservative failure semantics. No automatic mutation retry, server restart, admission bypass or timeout increase.Validation and limits
41ba6f4d9and this implementation: 7,162 scoped Turns; 12 sequential reads with an append between reads. Warm median was approximately 348 → 101 ms; eight concurrent readers completed in approximately 2.93 → 0.60 seconds. Cold read was 427 → 334 ms. These are one-machine measurements, not a new SLO or D2 qualification.The original transient host/process scheduling cause was not reproduced reliably. This establishes reduced repeated work and concurrency headroom, not immunity to environmental timeouts. Full byte reads still scale with history; retained-source bytes are not an exact JS heap bound. This is an in-memory acceleration, not a durable index or new authority. PostgreSQL storage and provider contracts are unchanged. Existing quota/heartbeat callers adopt the repair; no new UI/Lark setting is needed.
Review focus
Challenge stale-cache safety, older conflicting receipts, exact poll identity, cold-history scheduling, memory retention and query-versus-mutation failure semantics. The future-facing pass consolidated the actual receipt owners and removed duplicate Python IO without introducing a new capability or provider.