Codex/eval quota requeue - #31
Open
lisihao wants to merge 675 commits into
Open
Conversation
…egration Integrate browser profile control plane and Webwright bridge
Scrub browser agent identity placeholders
Fix Solar autopilot builder-ready drain
监护人'赶紧解决': needs_human_review 事件 660条/小时 (37504条累积), 把 events.jsonl 撑爆并把 solard 每轮拖到 27.5 秒。 根因: redispatch 每轮 (coordinator.sh --scan-all + solard reconciler 两处) 重扫所有 failed 节点, 对已超重派上限的同一批 63 个节点反复 emit dag_node_needs_human_review — 单个节点 N2 被重报 1843 次。 emit 逻辑没检查'此节点是否已升级过', 无去重 (与之前通知刷屏、 capability_demand_dropped 刷屏同源: 哨兵对同一对象反复报)。 修复: 只在首次升级 (needs_human_review 尚未置位) 时 append/emit/notify; 已升级的归入 skipped[already_escalated], 不再重复。 验证: - 隔离单测: 第1轮升级 emit 1 次, 第2轮同节点 skipped, 不重复 - 手动跑新码 --scan-all --apply: total_escalated=0 新增事件 0 (旧码 ~60+) - 重启 solard 加载新码 → 60s 内 needs_human_review 新增 0 条 - solard loop_ms 27524ms → 47ms (快 585 倍, 恢复 O(Δ) 毫秒级) 这是'观测不到自己重复'病的最后一处: 通知静音(b470ee3) 只掐了弹窗, 本次掐掉事件 emit 本身, 噪音源头根治。
…ty 刷屏去重 监护人推进 human-review 清理时, 放行 10 节点后 solard loop_ms 飙到 98 秒。 查出两个并发拖累: 1. pane_hygiene reconciler 每轮崩: PosixPath.is_executable() 不存在 (PosixPath 无此方法), 抛 'PosixPath object has no attribute is_executable' 124 次。改用 os.access(bash, os.X_OK)。 2. capability_demand_dropped 刷屏: validate_required 每轮对同一批缺能力 节点重 emit (94条/15min), 撑大 events.jsonl。与 needs_human_review/ notify 刷屏同源。加持久化指纹去重 (_should_emit_demand_dropped, 同 context+dropped 在 30min TTL 内只报一次)。 附带: events/all.jsonl 已从 269MB 轮转归档至 ~5MB (前期刷屏遗留, 撑慢 solard 扫描)。 验证: capability 去重单测 (同指纹第2次 False); pane_hygiene 修复后 重启 solard loop_ms 52000ms→303ms (快 170 倍), 心跳 3s 新鲜, 新 reconciler_error 归零。
监护人推进 human-review 时发现 builder pane 全被幽灵占用 (busy=False 但 assignment/graph_node 记录占着), ready 节点 interrupt_prompt_blocked 派不出, solard 每轮空 fork 28 次子进程拖到 94 秒。 根因 (B 手动验证确认): _active_multi_task_status_for 只读 multi-task status.json 的 status 字段 (dispatched/running), 不验进程真活。僵尸 status (worker 早死但状态没回写) 每轮把节点钉回 dispatched → reconcile 收割逻辑 被 continue 跳过 → pane 永久占用 → 死锁。与 solard 假死/cmd_status 假阴性 同源: 信状态文件不验进程。 修复: 复用现有 _pid_alive, multi-task status 只有 pid 真活才算 active; 僵尸 (pid 死/缺失) 视为非 active, 放行节点收割。 B 阶段手动验证 (已落盘): - 清 5 个幽灵 pane lease/assignment → 7 pane 释放 - 收割 2 个 11 天前 completed 的 dispatched 幽灵 (N5/N6→passed) - 归档 1 个僵尸 multi-task status → builder pane 8/9 空闲可派 (之前几乎全占) 验证: _pid_alive 死pid/None→False, 活pid→True。 注 (未尽): 仍有 dispatch_id 不匹配导致 closeout=None 的幽灵 (B0_prep 类), updated_at 每轮被 reconcile 刷新使 grace 判定失效 — 更深的纠缠, 留待 单独评估 (改 dispatch_id 匹配/grace 逻辑风险高, 不在本次最小修复内)。
监护人 '妈个逼就没解决, 头痛医头' → 4-agent 穷尽测绘挖出 27 个成因, 收敛为单一病理: 占用不验活性 + 无TTL兜底 + 收割链断裂。 完整方案见 ~/.solar/reports/2026-06-16-幽灵pane死锁-统一根治方案.md。 本提交是 P0 (最小止血), P1 统一 OrphanReaper 框架待批。 G4 (修正我昨天的无效修复 4ac2611): multi-task status.json 根本没 pid 字段 (pid 只写 agent.pid), 所以我加的 _pid_alive(status.get('pid')) 恒收 None → 恒判死 → 对真活 worker 也误杀。 修复: multi_task_runner.py 在 agent_pid 生成后写 worker_pid 进 status.json。 验证: 渲染 runner_script bash -n PASS, worker_pid 片段正确嵌入。 G3 (解锁收割入口, critical): solard.py:230 收割 (dispatch-ready 含 _reconcile_existing_dispatches) 仅在 ready 非空时 fork。全节点卡 dispatched 时 ready=[] → 永不收割 → 幽灵永久。 修复: 有占用态节点 (dispatched/assigned/...) 即使 ready=[] 也 fork 收割。 G6 (止空转死循环, 配合 G3 必须): dispatch_ready 末尾无条件 save_graph → os.replace 刷 mtime → DirtyScanner 重新标脏 → scan→fork→0派→save→重新脏 ('28 fork 1 enqueue' 的 27 次空转源)。 修复: graph 内容指纹比对, 只在真变化时 save。注意: 必须 import hashlib (差点又是假修复 — 漏 import 时 except 吞异常返空串, 看似生效实则从不save)。 效果 (实测): - solard loop_ms 15-94秒 → 89ms (G6 止空转, 飞快) - G3 让 solard 对全卡死 sprint 也收割 (实测收割改了 graph) - 手动清当前 8 个 dispatched 幽灵 → pane 释放 未尽 (留 P1): 3 个 closeout=None 幽灵 (dispatch_id 不匹配 G9/G10) 仍需 统一 OrphanReaper 超时兜底根治 — P0 手动清, 非自动。
…e_reclaim
P0 止血后的架构根治。把 27 个成因 (4-agent 测绘) 收敛为一个统一占用生命周期
框架, 而非 27 个补丁。完整方案见 ~/.solar/reports/2026-06-16-幽灵pane死锁-统一根治方案.md。
组件1 LivenessProbe (lib/occupancy_liveness.py) — 治病理1 (9个活性校验成因):
统一活性判定 is_holder_alive/liveness_verdict, 三级信号容错:
1. holder pid (os.kill 验进程) → 治 G4/G13/G16 (各类占用不验进程)
2. heartbeat 新鲜度 → 治 G24/G25
3. occupied_since + grace TTL → 治 G1 (占用态无 TTL) + grace 失效
活性优先于文字 state; 无证据保守判孤儿 (幽灵危害 >> 误回收, 后者会重派)。
单测 14/14 (死pid/心跳陈旧/grace超时/state=running但pid死→孤儿/治grace失效)。
组件3 OrphanReaper (lib/orphan_reaper.py) — 治病理2+3 (18个收割/调度成因):
独立 reconciler, 不依赖 ready 门 (治 G3) 不依赖 sprint 标脏 (治 G19):
每轮扫所有占用态节点 → LivenessProbe 判活 → 孤儿统一回收
(operatord完成+handoff→passed; 否则退pending释放pane)。
用 dispatch_id 末尾时间戳作 occupied_since (非每轮刷新的 updated_at) — 治 grace 失效。
注册进 solard RECONCILERS (interval 120s); SOLAR_ORPHAN_REAPER_ENABLED=0 可秒退。
注入式单测 8/8 (dispatched幽灵/刚派发不误回收/completed→passed/终态跳过/治grace失效)。
组件2 OccupancyTTL 的 force_reclaim (graph_scheduler.set_node_status):
G2 修复: 单调 rank 守卫封死占用态降级, 导致诊断出幽灵后 set_node_status
(node,'pending') 是静默 no-op。force_reclaim 旁路仅放行占用态→pending/queued,
不破坏其他语义。只有 OrphanReaper 等回收路径显式传 True。
实测: OrphanReaper active 自动回收了 1 个 occupied 3天的孤儿
(B2_replay_suite_offline → requeue); 当前确认幽灵数 8→0。
与现有修复关系: 今晚 P0 (0397ccb) + multi-task活性 (4ac2611) 等零散补丁
全部并入此框架 — 从'各修一类幽灵'升级为'一个 LivenessProbe+OrphanReaper 管所有占用'。
未尽: loop_ms 性能观察中 (dispatch-ready fork 本身慢, 非本框架引入);
operator/pane/actor lease 三类占用的 LivenessProbe 接入待 P1.5 (本次先做 graph 节点)。
P1 性能闭环最后一块。监护人 '头痛医头' 根治的收尾。 问题: P1 后 solard loop_ms 仍 35 秒。两个根因: 1. G6 指纹盲区 (核心): dispatch-ready 每轮跑 capability enrichment 刷新 nodes[*].capability_inference.generated_at 时间戳 → graph 指纹变 → 我的 G6 内容比对误判'有变化' → save → 刷 mtime → DirtyScanner 标脏 → 重复 fork (agent-plan sprint 被 fork 13次/5min 全空转)。 修复: _graph_fingerprint 排除 volatile 字段 (generated_at/updated_at/ enriched_at/cached_at 等), 只对功能内容算指纹。功能无变化不 save。 实测: 改 generated_at→指纹不变(不save); 改 status→指纹变(save); 端到端 dispatch 后 mtime 未变 → 空转死循环掐断。 2. G3 收割移交 OrphanReaper: P0 的 G3 让'有占用态也 fork dispatch-ready' 收割幽灵, 但 P1 OrphanReaper 已接管收割 (进程内~1s, 不 fork)。两者 重复且 fork 慢。回退 G3 到只为 ready 非空 fork (纯派发), 收割归 OrphanReaper。职责分离。 效果 (实测): solard loop_ms 35秒 → 52-242ms (毫秒级), dirty 大部分=0, 空转死循环根治。当前确认幽灵=0 (OrphanReaper 自动维持)。 至此幽灵pane死锁: P0 止血(0397ccb) + P1 统一框架(bb7272c) + 本次性能 闭环 = 27成因从架构消除, solard 健康, 幽灵自动回收, 空转掐断。
…rk没用上 监护人问'codex-spark 算子为啥没用上' → 挖出更上游的吞吐归零根因。 现象链: - 整个 builder operatord pool=0 在跑, 92 个 planning_complete 等着没 worker - graph_dispatch 近2h executed 166次 但 enqueued 仅 3 个 (派发率 1.8%) - 失败全是 no_matching_worker 根因 (graph_scheduler.py:2587): 软约束 (task #18) 只对 required_capabilities 生效, required_skills 仍是硬门 — _skills_match 失败直接 continue 淘汰 worker, 发生在 soft 模式判断之前。纯 required_skills 节点 (无 required_capabilities) 差一个 skill 就 no_matching_worker。 实例: N3 要 [debugging,concurrency-testing,regression-testing], worker 匹配 2/3, 软阈值=(3+1)//2=2 本应放行, 但 _skills_match 的软阈值分支只在 required_capabilities 非空时才走 (2364行), 纯skills节点直接 return False。 连锁: enqueue 98% 失败 → 活进不了 operator inbox → wake 环无活可踢 → operatord 不启动 (pool=0) → 即使 codex-spark 优先级最高(96)也无活可消费。 修复: soft 模式下 skills 不匹配也不淘汰 worker (与 capabilities 软约束一致), 降级匹配 (排序仍偏好 skill_match_count 高者)。hard 模式保持硬淘汰。 验证 (实测): - 修复前 no_matching_worker, 修复后 N3/agent-plan enqueued=1 - 手动 dispatch-ready: N3 真派到 operator-pool:builder.0, inbox 1→2 - wake 环踢起 mini-codex-gpt53-spark-builder-1 operatord 消费 → codex-spark 终于被派到活 (回答监护人的问题) 这是派发链最上游的瓶颈, 比幽灵更直接卡死吞吐。
监护人 '可以执行' → 修放行/回收节点假重试的真 bug。 问题: OrphanReaper 和手动放行把 failed→pending 时只改 status, 没清旧 eval.json/handoff artifact。下一轮 reconcile 读到旧 eval 结论 → 又判 failed, 节点假重试永远过不了 (实测: 放行 53 个, 34 个被旧评审打回 failed, rc=0 无新 result — 没真执行就被旧结论判死)。 修复: OrphanReaper requeue 动作复用 graph_redispatch._clear_eval_artifacts 清旧 node_results artifact + 归档物理 eval 文件 (留痕可追溯), 让重试是 干净的。与 graph_redispatch 重派逻辑一致。 附带: 手动清当前 34 个被旧 eval 打回的 failed 的 artifact + 干净重放行。 效果: ready 节点 46 个待派, 这次重试不会被旧结论秒判 failed。 至此派发链全链路: skills软约束(6a7b395) + 幽灵根治(P0/P1) + OrphanReaper清理(本次) = 派发匹配通 + 占用自动回收 + 重试干净。
监护人观察30min零吞吐, passed反跌9。根因之一: OrphanReaper 反复回收同一 节点 (N1 被 requeue 两次)。派发器派节点成 dispatched 时不盖 occupied_since, OrphanReaper 退化用每轮刷新的 updated_at 判 grace → 判定不稳 → requeue↔dispatch 横跳, 节点从不真完成。 修复 (P1 OccupancyTTL 漏接的一环): set_node_status 进占用态 (assigned/dispatched/in_progress/running/reviewing) 时盖 occupied_since 独立戳 (仅首次进入, 重复 set 同态不刷新); 出占用态时清戳。OrphanReaper 用它判 grace 超时, 不用 updated_at。 验证: occupied_since 单测 5/5 (盖戳/不刷新/清戳/占用态进出); 回归 OrphanReaper 8/8 + LivenessProbe 14/14。 注: 这修了横跳, 但30min零真result的核心(operatord消费端没真干活)是 独立问题, 下一步聚焦消费端。
监护人'1和2都干'→ 派审判官牛马深挖 operatord 消费链 + 写 handoff。 牛马定位真瓶颈(非消费端), Solar 验真+修复。 根因: _builder_operator_pool_available_count (lib/graph_node_dispatcher.py:5809) 用 12s 超时跑 pm_dispatch builder-pool-status 探针, 但探针串行 probe ~14 operator 实测 41-49s → 每个 drain 周期超时 → 回退缓存默认 available=0 → 调度器永远看到'0 个可用 builder' → 41 ready 活派不进 operator 池 → 吞吐归零。 真实可用其实有 1-2 个。 验真 (Solar 亲跑): pm_dispatch 探针实测 40.98s/49.10s total_available=1; _builder_operator_pool_available_count() 旧码返0(超时12s) 修复后返2(32.4s)。 止血: 超时 12→60s; 缓存 TTL 20→90s 减少探针频率。 权衡: 探针首次32s阻塞主循环(冷启动loop_ms16s), 缓存命中后回落66ms。 效果: 调度器看到可用builder 0→2, ready活能派进池。 治本留 P1.5: 读 health-watchdog 快照(capacity.operators_usable已有)/并行化探针。
监护人全盘点发现: thunderomlx 本地模型死了, 失败 124 次还在接活, 把全队
成功率从~60%砸到32%。codex-spark 失败 21 次同理。
根因: classify_failure_state 只认 rate_limit/auth 两类失败, TimeoutError/
服务死/exit65/connection refused 全返 '' → apply_failure_flow_control 不熔断
→ 坏算子无限接活无限失败。
根治: operator_flow_control.record_operator_outcome — 按连续失败计数熔断,
覆盖所有失败类型 (不依赖 classify 的有限错误识别):
- 连续失败 >= SOLAR_OPERATOR_CONSEC_FAIL_THRESHOLD(默认3) → 强制 cooldown 1h
- success 重置计数; 计数持久化 operator-status/{op}.json consecutive_failures
operatord 失败/成功路径都调它 (tools/operatord.py:1320)。
验证: 单测 失败1/2不熔断, 失败3熔断(circuit_broken=True), 成功重置计数=0。
附带处置 (本次盘点):
- thunderomlx 从 builder_pool 禁用 (desired 1→0, daemon killed)
- 冷却 4 个 0%成功率坏算子 (thunderomlx×2/codex-spark×2, 累计失败139)
- 46 个 dispatched 僵尸节点已被 OrphanReaper 自动收割 (现 0)
全盘点结论 (4周): 558 sprint, 完成 377(67%), codex-medium 两个 builder
近24h 扛 82 任务(95%+成功率)。系统真在产出, 坏算子是拖累, 现已熔断。
监护人指令: thunderomlx 两个算子标注为废弃。 本地模型死了, 124 次超时 0 成功, 已确认无价值。 physical-operators.json + agent-actors.json: mini-thunderomlx-qwen36-builder: enabled=false deprecated=true mini-thunderomlx-qwen36-knowledge: enabled=false deprecated=true deprecated_reason=local_model_dead_124_timeouts_0_success
监护人配额才用13%/Sonnet没用, 但harness把8个claude算子冷却到午夜闲置。 监护人: '你给我的数据就是放屁'。 根因: recent_operator_quota_block 扫claude历史任务output.log, 发现 'You've hit your limit · resets 1:40am'(4天前的软限流提示)就冷却算子。 parse_rate_limit_reset_at 把'1:40am'解析成'今天1:40am'(永远在未来) → reset_at>now → 永久cooldown。且旧逻辑: 有reset_at就不检查日志多旧 (max_age只在reset_at=None时查) → 4天前的旧日志永久生效。 修复: reset_at存在时也检查日志mtime, 超max_age(默认48h)直接忽略。 旧限流日志不再永久冷却claude算子。 验证: 修复后 sonnet recent_operator_quota_block 返None(旧日志被拦); 8个claude算子解冻。 注: claude CLI偶发软限流提示(订阅几小时恢复), 但harness当硬限流。 治本应缩短max_age至软限流恢复窗口(~2h)或信任真实配额而非日志文本。 本次先修'旧日志永久生效'这个明确bug。 教训: 我多次把agent报告/中间数据当真相讲给监护人(误报'14算子限流到午夜'), 未核实。监护人配额13%是铁证, 数据探测(no-admin-key→误判耗尽)才是错的。
claude CLI 偶发软限流(订阅几小时恢复), 旧默认48h太长让一次软提示冻算子半天。 缩到2h对齐软限流实际恢复窗口。配合上一commit(旧日志检查)彻底解冻claude算子。
fix(harness): stop stale Claude cooldown propagation
lisihao
force-pushed
the
codex/eval-quota-requeue
branch
from
June 18, 2026 00:49
1dce8e4 to
dcaeabc
Compare
codex 接力分析定位准确: prune_expired_operator_config_blocks 每轮从 physical-operators.json 的 quota_guard_state 把旧 claude cooldown 复活, 所以 Solar 之前清 runtime status 10+ 轮都被写回 (鸡生蛋死锁: 算子被冷却 →不派活→无心跳→旧逻辑'心跳清block'检查失败→永久kept)。 修复 (按 codex P0-1, Solar 验真): prune 对 claude-code 订阅算子, registry cooldown 若 recent_operator_quota_block 返 None (无近2h真实限流日志证据) → 清理 registry block (不依赖心跳), 持久写回 registry。有真限流证据才保留。 验真 (Solar 亲跑, 非盲信报告): - 修复前 prune kept 12 个 claude cooldown (expires 明天午夜) - 修复后 prune 清理, registry claude quota_guard_state 全部 ok(10个) - registry 文件真被改 (持久, 不再被复活) codex 分析审核结论: 核心论断全部验真为真 — 控制面分裂✓ / prune 复活旧block是真凶✓ / quota_refresh已修对一半✓ codex 比 Solar 更系统 (Solar 挖 10 轮没找到 prune 这层)。 其余 P0-3(watchdog drift)/P1(block ledger独立化) 见 codex 报告, 留后续。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ok