← 工作原理 · 原文件 notes/reports/dev/2026-10-03_G45_carryover_limits.md(Markdown 源文件已渲染;链接到其他文档的会跳转,指向源码的只显示路径)
G45.6 / G45.7:跨环续做、执行上限与权重复用
1. 起因与范围变化
用户 2026-10-03 担心复杂算法(要训练的 OT / CFM / VAE)在 12 h 一环里跑不完:候选每次执行受视图 limits(30 min)限制,一个入选候选被重复执行约 9 次;环截止时在飞的 Engineer 工作没有交接。任务中途两次改口径:
- 用户:不要固定执行时限,改为“健康即继续”的监督 + 宽松兜底(360 min),终选预留按实测,健康邮件改每小时(之后用户再改为每 3 小时,timer 由主会话改并部署),T1 链不写
time_limit_min。 - Codex 审阅(P0 / P1,覆盖 18 号草案中冲突的部分):截止语义、封存事务、终选资源隔离(缺基线拒绝替换、基线先落盘、预留估算口径)、seed / 训练状态 / 缓存有效范围写进契约;权重缓存键;新会话续做、真实父节点、额度累计、
segments[]证据;健康监督先只观测;链级已暴露评测 seed。后置:--seeds批量接口、每节点提前三 seed、续同一 opencode 会话、前五名固定重训(改为入围候选冷启动重现检查,只作诊断)。
中途写过的 --seeds 多输出路径、晋级读已有 seed 分数、前五名重训已全部撤回(代码里不留)。
2. 候选执行上限与健康监督(G45.7)
| 项 | 实现 |
|---|---|
配置 limits | controller.normalize_limits:{memory_gb, time_limit_min} 或 {<榜>: {...}} → cfg.limits = {board, memory_gb, time_limit_min}(进锁);默认 dataview.EXEC_LIMITS_DEFAULT = 28 GB / 360 min(views.LIMITS 同步改 360)。build_run_views 把它写进本 run 每个视图(含测试题视图)manifest 的 limits(dataview.set_limits);execute.program_limits 与 evalspec 仍从 manifest 读 |
健康监督 health.py | Supervisor:每 collect_s 60 s 读进程组(累计 CPU、I/O、按 pid 归属的 GPU SM% / 显存(nvidia-smi pmon + compute-apps)、GPU 排队、进程状态 / wchan、cgroup 内存与余量、输出、可写目录 mtime、solution/progress.json、探针可用性),每 interval_s 300 s 汇总写 <exec_dir>/health.jsonl + health_latest.json。活动只认 CPU / 自己的 GPU 计算 / I/O / D 态 + I/O / GPU 排队,心跳与进度不算;没有内存逼近判据 |
| 模式 | exec_health.enforce: false(默认,只观测):30 min 无活动只记 would_hang,程序受 360 min 与阶段终点约束。enforce: true:30 min 无活动 → hung(候选失败);360 min 只在监督器失效(failed_after_s 600 s 读不到进程组)时生效(backstop → timeout) |
新状态 hung | archive STATUSES / FAILED / EXEC_FAILED、final.CANDIDATE_FAILURES、tune.CANDIDATE_FAIL、viewcheck.INCONCLUSIVE_STATUSES、看板词表、报告失败列表 |
| 截止语义 | 所有终止 SIGTERM → 宽限 term_grace_s 60 s → SIGKILL(kill_tree(grace_s));run_group(deadline_fn=...):控制器的 live_deadline 让 cutoff 前移时在飞的 Engineer 会话(cutoff − tail)和执行(阶段终点)一起提前 |
| Engineer 停顿判定 | ActivityProbe 加 health.GpuActivity(每 60 s 一次 pmon,会话进程树自己的 GPU 计算算活动) |
| 进度文件 | 执行时 solution/progress.json 是 solution/ 里唯一 rw 绑定的文件(环境变量 VEC_PROGRESS_FILE),提交时排除 |
| 评测规格 | evalspec execution 加 health(仅当配置有 exec_health;旧锁不变) |
| 健康邮件 | timer 由主会话改为每 3 h(OnCalendar=*-*-* 00/3:00:00,本任务不再改它);reports.health_report 每个运行中 run 列出在飞候选执行(health.inflight_summaries / summary_line),熔断与模型异常的统计窗口 = 邮件周期近 3 h(HEALTH_WINDOW_S) |
3. 训练一次、多 seed 复用(G45.7)
ARTIFACTS.json条目加per_view: true(必须在子目录);EXECUTION.json加seed_affects_training(bool)与train_seed(int)。- 提交:
per_view文件不进 git、不按提交存;声明了但提交时不在的文件自动转per_view(记commit_skipped.txt)。 - 执行(
execute.prepare_weights):缓存键weights_key= sha256(solution 树、训练 seed、view_content、env_key(venv、pip freeze、modeling 快照)、额外参数如--ablate)。命中 → 只读硬链回放(replay);未命中 → 持该键的 flock(锁在运行时目录,不在 run 目录)、权重子目录 rw 绑定(目录内指向产物库的硬链先换成私有副本)、成功后验收并不可变登记(runenv.store_per_view,索引artifacts/per_view/<tree>/<key>.json,首写者为准,进账本)。已登记的权重缺失 / 哈希不符 →interrupted。artifacts="fresh"(视图盲检查伪装与复核、冷启动重现检查)训练但不回放、不登记。 - 四种执行的核实:
--ablate对照走evaluate(extra_args)→ 键含参数 → 训练自己的一组(对照不能用完整机制的权重,按 Codex 改);晋级 seed 1/2、终选护栏 seed(含基线)走evaluate→ 同键回放;--continue-from:continuation.import_runs原先只复制产物库顶层文件,补上递归复制per_view索引(硬链 + 账本)。单元测试WeightsTest逐项覆盖。 - 冷启动重现检查(
repro_check,默认开):终选在 holdout / record 诊断之后、仍有时间时,对入围的训练型候选 seed 0fresh再跑,记repro_json、REPRO.json、repro_check事件,只作诊断。
4. 终选资源保护与 seed(Codex P0-3、item 8)
final.py:配置了护栏但没有基线提交 → 记final_guard_refused,不写final/,不做任何候选审查 / 执行(原为“跳过护栏、按排名放候选”);有护栏时基线先落盘,再做候选的视图盲检查和审查。- 终选预留按实测(
Controller.final_reserve_estimate/update_final_reserve,主循环与收尾循环每 60 s):未做的视图检查、未审查、缺的晋级 seed、护栏全部任务(最坏候选 + 基线)、final 冷启动(× 1.5)与基线 final 运行、每次执行 60 s 评分排队,串行累加;执行时间用多视图合计 + 评分时间(evaluate新增wall_s,进seed_scores);reserve = max(配置, 估计 + 10 min),上限半个挂钟;Budget.extend_reserve只前移 cutoff,存 meta,resume 后保持。 - 链级已暴露 seed:
assign_fresh_seeds在 launch(inputs 锁定、锚点预建之前)沿--continue-from链收集各环护栏fresh_seeds与chain_exposed_seeds,重叠就换成最小未用整数(3–7 → 8–12),集合写进config.chain_exposed_seeds(进锁)。
5. 跨环续做(G45.6,carryover.py)
- 带走:会话在交接点(cutoff − node_tail)被切断且没留下可执行程序(
gen_failed/gen_empty)→carried_over(engineer);切断但可运行 → 照常立即执行,快照只作证据(handoff_sealed事件);权威执行被搜索阶段截止切断(interrupted+budget_cut)→carried_over(execution,封存权重目录里的最后检查点)。 - 封存事务
carryover.seal:进程组已回收、打分 client 已移除后,复制solution/(跳过 .h5ad / 符号链接 / 超过 2 GB)、PLAN、原始轨迹、检查点,写HANDOFF.json(交接摘要、改动文件、METHOD / run.py 状态、已用查分、会话统计、最后 30 条消息节选、segments[])与SEAL.json(逐文件 sha256),全部只读,登记账本;节点Archive.interrupt(status="carried_over")(新增状态,属 SET_ASIDE)。 - 导入:
continuation.import_runs对carried_over行copy_verified(先按 SEAL 逐文件校验,失败不复制、记carryover_rejected),通过的写 metacarryover_verified;continued_from.json加carryover段。 - 续做:
build_resume_queue(已校验、未被续做、最旧优先、每环 ≤max_resume_per_ring4)→ 主循环先next_resume再抽签;resume是新 op(本环节点,计max_nodes),父节点 = 被带走节点的真实父节点;父节点审查 fail →resume_refused。resume_origin列(JSON):run、id、imported_id、kind、segments、seal_sha256、commit(Codex 要的“origin 记上一环 run id 与节点 id”放在这一列,而不是origin_run:后者在全代码里表示“导入的、不是本环的工作”,会把续做节点排除在max_nodes、节点估时、报告的“本环新节点”之外)。 - engineer 类:不调 Researcher;新会话;工作区 = 快照的
solution/,已写的 METHOD 保留;提示 = 本环完整提示 +carryover.resume_block(续做标记、交接摘要、最后消息、只读快照路径、剩余额度);快照只读挂载;额度 = 每节点额度 − 各段已用之和。execution 类:同一提交直接execute_node。 - 证据:续做节点的
trajectory_paths以各段快照的原始轨迹开头;package把祖先链里续做节点的快照(轨迹、HANDOFF、SEAL、PLAN)并入lineage_trajectories,MANIFEST.json加lineage_segments。 - 配置
carryover: {enabled: false, max_resume_per_ring: 4, max_file_mb: 2048};T1 / T2 链配置打开。 - fake 后端:
fake_faults.slow_engineer/slow_program/train_weights(按节点 id)供端到端用。
6. Codex 实现审阅(notes/reports/reviews/2026-10-03_codex_g45_impl.md)的修复
| 条 | 改动位置 | 测试(agent/search/tests/test_g45_carryover_limits.py) |
|---|---|---|
| P0-1 截止收尾 | controller.work_phase / bind_phase(节点线程、晋级线程绑定启动时的阶段;live_deadline、carry_on、evaluate、llm_call 都按它),_spawn、_promoter;execute.LiveDeadline / live_stop(前移的截止传到训练锁、admission、GPU 槽、评分请求);loop() 收尾:cutoff 后等在飞节点回收、封存、结算(宽限 + SETTLE_EXTRA_S,不晚于 stop_at − 60 s) | ReviewP0Deadline.test_bound_phase_survives_the_final、test_live_deadline_reaches_waits、test_final_waits_for_search_work_to_settle |
| P0-2 多环去重 | archive.insert_imported 重映射 resume_origin.imported_id;controller.work_key(稳定身份 = 封存它的 run#node)、build_resume_queue(已续做的工作不再续;名额只数 origin_run IS NULL 的 resume)、next_resume(resume_origin.run/id = 直接上一段,记 work) | ReviewP0MultiRing.test_three_rings;fake 三环 §7 |
| P0-3 权重验收 | execute._run_sandboxed:store_per_view 有任何 problem → crash、不打分;runenv.load_per_view:索引不完整 / 读不出 → ArtifactError(→ interrupted),不退回训练 | ReviewP0Weights.test_acceptance_failure_is_not_scored、test_incomplete_index_is_interrupted |
| P0-4 祖先污染 | continuation.ancestor_integrity(递归:TAINTED、ops.verify_run、目录 / 锁存在);接入 describe_source(launch)、controller.package(失败拒绝打包)、scripts/prepare_submission.py(祖先问题列为不能提交) | ReviewP0Ancestors.test_recursive_taint |
| P1-9 指纹拆分 | evalspec.search_hash(去掉护栏 fresh seed 与只为它们建的锚点、视图 limits、time_limit_min、threads、health),spec() 输出 search_hash;scoring_def.search_hash_of(旧锁从锁里的规格 + 磁盘视图重算);continuation 按 search_hash 判可比 | ReviewP1.test_search_hash_ignores_guard_seeds_and_time_limit、test_view_limits_do_not_change_the_search_view_hash;fake 三环 B、C 都 comparable=True |
| P1-6 额度 | carryover.used_from_log(复用评分服务口径)、controller.queries_used;queries_used_total 有未知段 → None → 续做额度 0 | ReviewP1.test_unknown_quota_grants_nothing |
| P1-10 宽限 | runenv.kill_tree:整组在宽限期内退出才算完,到期 KILL,返回残留 pid;run_group 记 reap_left;执行有残留 → 不登记权重、不封存(reap_incomplete),会话有残留(CallResult.reap_left)→ 不封存 | ReviewP1.test_grace_covers_the_whole_group |
| 追加:线程上限 | limits.threads(默认 8,进锁、写进视图 manifest、进评测规格 execution.programs.*.threads,不进 search_hash);runenv.thread_env 经 base_env 给执行与 Engineer 沙箱设 OMP_NUM_THREADS / OPENBLAS_NUM_THREADS / MKL_NUM_THREADS / NUMEXPR_MAX_THREADS / NUMEXPR_NUM_THREADS / VECLIB_MAXIMUM_THREADS / BLIS_NUM_THREADS;权重缓存键的 env_key 含线程数;search_spec.md 预算条加半句 | LimitsTest.test_thread_budget |
后置(已知限制,未做):5(segments 逐段完整取证:Engineer 封存只收 Engineer 轨迹,execution 封存无轨迹;package 只打包当前 imported_id 的快照,不逐段解析 segments[].run/node)、7(封存 / 导入不是可恢复的原子事务:SEAL 写完到节点结算之间崩溃会被 recover 记成普通 interrupted)、8(seed / guard 阶段的截止没有扣除候选 final 冷启动预留;估算未计护栏 holdout 执行)、11(缓存键未纳入注入的 GPU 环境 / sitecustomize)。kg_task 接线按更正未合入(research 服务无 kg_task 时 kg 保持 disabled)。
6b. Codex 二审(notes/reports/reviews/2026-10-03_codex_g45_impl_r2.md)的修复
| 条 | 改动位置 | 测试(test_g45_carryover_limits.py) |
|---|---|---|
| P0-1 等待资源时截止 | execute.run_program / _run_sandboxed:程序未开始、admission 放弃、GPU 槽放弃、admission 后无时间 → interrupted 带 budget_cut=True;训练锁等待超时(prepare_weights 记 budget_cut)带 True,产物损坏不带 | ReviewR2P0WaitCut.test_admission_and_gpu_waits、test_train_lock_vs_damage、test_wait_cut_node_is_carried_over(真实 execute_node → run_program,admission 与 GPU 两条分支都落到 carried_over 并封存校验通过) |
| P1-1 T1 锚点命名 | evalspec.anchor_seed(按字段解析 sN,兼容 B:s3:d0)、search_hash | ReviewP1.test_search_hash_guard_seeds_only、test_search_hash_real_proxy10_labels(真实 proxy10.describe())、test_old_round2_lock_guard_seed_swap(真实 r2-D-s0/s1 锁,只读;VM 上没有该 run 会跳过,Spark 上运行) |
| P1-2 在途评分截止 | agent/scoring/service.py:GroupDeadline、request_stop(内部请求的 deadline_key)、控制 op set_deadline(只降不升)、FifoGate 等待每 2 s 重读、_run_worker 循环 communicate 每 2 s 重读并在新截止杀 worker;execute.LiveDeadline.key 与 score_prediction / check_prediction 带 deadline_key;控制器 push_deadline(开跑时与每次前移 cutoff 时);stop_promoter 上限(cutoff + 宽限 + SETTLE_EXTRA_S,超时 promotion_abandoned、结算 failed,promote 末尾不再覆盖) | ReviewR2P1Deadlines.test_group_deadline_wakes_a_queued_request、test_set_deadline_op_lowers_only、test_live_deadline_names_its_group、test_stop_promoter_is_bounded |
| P1-3 时间 / 线程进比较 | search_hash 不再排除视图 limits、time_limit_min、threads(只排除护栏新 seed 及其锚点和 health);search_hash_of 只用锁里的规格;T1 链配置 continue_rescore_top_k: 40 | test_search_hash_guard_seeds_only、test_old_round2_lock_guard_seed_swap(改时限后不可比) |
| P1-4 在途查分结算 | 服务 remove_client 把 client 留在 retired,新 op settle_client;控制器先移除 client 再 settled_count(90 s),结不清 queries_settled=False → queries_used 为 None | ReviewR2P1Quota.test_pending_request_across_the_seal、test_unsettled_count_is_unknown |
| P1-5 旧 resume_origin | archive.insert_imported 补 work = run#id;controller.resume_work 去重兼容旧记录 | ReviewR2P1OldResume.test_migration |
| P2-1 忙循环 | runenv.kill_tree:leader 退出后按 0.5 s 间隔 sleep / poll | ReviewP1.test_grace_covers_the_whole_group(行为不变) |
取舍(P1-3):时间上限与线程数候选看得见,可能改变输出,所以留在比较里。代价:今晚 T1 第一环从 r2-D-s0 / s1(30 min、无线程变量)接续时 search_hash 不同,导入节点只重评排名前 40 个(加各自种子),其余成为只作历史的节点;重评要额外执行约 40 次候选(每次在 proxy10 与 X3 上各一次)。
后置(已知限制):二审 P1-6(v6 及更早祖先只查 TAINTED、不做内容校验;今晚两个来源是 v8,不触发);一审 P1-5 / P1-7 / P1-8 / P1-11(见 §6)。
6c. Codex 三审(notes/reports/reviews/2026-10-03_codex_g45_impl_r3.md)的修复
| 条 | 改动位置 | 测试(test_g45_carryover_limits.py) |
|---|---|---|
| 1 settle_client 封口 | service.Client.closed;remove_client 在服务锁内弹出、置 closed、转 retired;score 的准入(pending += 1)同一锁内先查 closed,关闭后拒绝(refused “client closed”);controller.queries_used 只认 queries_settled is True,服务不答复也算未知,去掉日志回退 | ReviewR3Quota.test_no_request_admitted_after_close、ReviewR2P1Quota.test_unsettled_count_is_unknown(加了“无结算证明不回退日志”) |
| 2 计数归一化 | controller.final_count:_engineer 写进 info["queries_used"](即节点列)前归一化,未结清为 None;Engineer 与 execution 两种 HANDOFF 都沿用节点列 / 该值 | test_unsettled_count_is_unknown(final_count) |
| 3 set_deadline 确认 | push_deadline 记待确认值,响应确认(返回的 stop_at ≤ cutoff)才清除;retry_deadline 每 DEADLINE_RETRY_S 30 s 重发(挂在 update_final_reserve,主循环 / 收尾 / 重评都会调用);confirm_deadline 在进入终选前最多重试 60 s,失败记 deadline_unconfirmed;客户端 execute.service_live:LiveDeadline 过去 30 s 仍无应答就返回 interrupted | ReviewR3Deadline.test_retry_until_confirmed、test_retry_is_bounded_and_final_confirmation、test_client_stops_waiting_at_the_moved_bound |
| 4 晋级弃置 / 视图锁 | promo_commit(锁 promo_commit_lock + 代次 promo_gen):promote 的两处结果写入都经它,弃置时代次加一,迟到写入丢弃;abandoned_threads 保留旧线程;view_check(lock_timeout=...),终选用 FinalSelector.vc_lock_timeout()(≤ 300 s 且不过终选截止),超时 BudgetExhausted → 跳过该候选 | ReviewR2P1Deadlines.test_stop_promoter_is_bounded(加迟到提交被丢弃)、ReviewR3Promotion.test_view_lock_timeout |
| 5 导入重评 | rescore_node:interrupted + budget_cut → 保持 imported_unscored、留在 rescore_queue、事件 rescore_cut;continuation.describe_source 读来源 run 的 rescore_queue,import_runs 把仍是 imported_unscored 的这些节点重新入队(不论可比与否);rescore_imported 在后台线程跑,主线程期间更新终选预留 | ReviewR3Rescore.test_cut_rescore_stays_queued、test_open_obligations_are_imported |
| 6 seed / guard 截止 | FinalSelector.cold_final_s / place_reserve_s:补跑 seed、旧式护栏、非劣护栏的截止扣 max(1.5 × 基线 final, 候选冷启动 final 估计) + 120 s;冷启动估计 = 训练过的(weights train / fresh / 无 per_view)最长单视图 seed-0 实测 × 1.5,没有则 final_cold_fraction(新配置键,默认 0.25)× limits.time_limit_min;没有 limits 的旧锁不加默认(行为不变) | ReviewR3FinalReserve.test_place_reserve |
| 7 共用收尾截止 | settle_deadline() 首次调用时固定 cutoff + 宽限 + 180 s(≤ stop_at − 60 s),节点收尾与 stop_promoter 共用 | ReviewR3FinalReserve.test_one_settle_deadline |
6d. Codex 四审(notes/reports/reviews/2026-10-03_codex_g45_impl_r4.md)的修复
| 条 | 改动位置 | 测试(test_g45_carryover_limits.py) |
|---|---|---|
| 1 额度封口 | service.settle_client 只对 closed=True 的 client 给 settled(应答多 closed 字段);controller.close_client 检查 remove_client 应答、最多 3 次重试(“no client” 视为已移除),关闭无法证明时不结算、计数为未知 | ReviewR4Quota.test_open_client_is_never_settled、test_close_client_checks_the_answer |
| 2 冷启动估算共用(最要紧) | final.cold_final_estimate(模块级):无 per-view 权重 = 最长单视图实测 × 1.5;有 per-view 且有训练实测 = 最长训练实测 × 1.5;有 per-view 只有回放 = 0.25 × time_limit_min;旧锁无 limits = 0。FinalSelector.cold_final_s 和控制器 final_reserve_estimate 都用它,后者再加同一个 PLACE_CHECK_S(120 s) | ReviewR4FinalReserve.test_guard_runs_with_replay_only_candidates(Codex 复现:5 个已晋级 replay-only 候选、每视图 30 s 执行 + 5 s 评分;用控制器算出的预留作为终选全部时间,护栏 10 个 fresh-seed 作业全部运行)、test_no_per_view_weights_uses_the_measurement、test_shared_estimate |
| 3 截止确认持续失败 | confirm_deadline 失败后 reap_scoring_workers:反复列出并 SIGKILL + stop 本 run 的评分 worker scope(vec-score-<run>-*),直到 10 s 内无新出现(最多 120 s),事件 scoring_workers_reaped,然后才进入终选 | ReviewR4Deadline.test_unconfirmed_deadline_reaps_workers |
| 4 晋级代次(P2) | _promoter 启动时绑定代次,循环条件与 promote(nid, gen) 都用它;promote 的启动登记(running)也经 promo_commit;异常分支改走 promo_commit | ReviewR4PromotionGeneration.test_stale_thread_registers_nothing |
关于默认 0.25 × 360 = 90 分钟是否过大:按上面的划分,默认值只用于“声明了 per-view 权重、但本 run 里从没有一次训练执行被测到”的候选。正常情况下这类程序的第一次权威执行就是训练(weights: train),会有实测;只回放过的典型来源是导入节点,而今晚的导入因线程数 / 环境改变,权重缓存键不同,重评时会重新训练、得到实测。没有 per-view 权重的程序(包括 replay-only 字样之外的所有普通程序)一律用实测 × 1.5,不会落到 90 分钟。所以 90 分钟只在少见情况下生效,且现在已计入总预留,不会再出现“预留不足、护栏全部跳过”。
剩余的已知限制(第 3 条):reap 只杀已经起了 worker 的请求;服务内仍在排队、尚未起 worker 的 search 请求按它们发送时的截止运行(服务不确认截止通常意味着服务已不可用,这时也没有排队请求)。
7. 回归
四审修复后(最新):Spark bash scripts/test_on_spark.sh -j 2 70 个模块全部通过(failed modules: 0);test_g45_carryover_limits 70 项(VM 上 1 项跳过:r2-D 旧锁只在 Spark 上);VM 另跑 test_objective_ops、test_proxy10、test_continuation_promotion、test_ni_guard、test_x3_guard 全过。
三审修复后:Spark bash scripts/test_on_spark.sh -j 2 70 个模块全部通过(failed modules: 0);test_g45_carryover_limits 63 项全过(Spark 上无跳过,r2-D 旧锁测试运行)。VM 上 test_objective_ops、test_proxy10、test_x3_guard、test_continuation_promotion、test_ni_guard 也单独跑过(test_continuation_promotion.test_rescore_node 曾因 rescore_imported 新的后台线程用到 hb 失败,已改为可选)。
二审修复后(Spark bash scripts/test_on_spark.sh -j 2,同步的是当前工作树):68 个模块全部通过,failed modules: 0。 其中 agent.search.tests.test_g45_carryover_limits 54 项全过、无跳过(test_old_round2_lock_guard_seed_swap 在 Spark 上读到了真实的 r2-D 锁并通过,test_search_hash_real_proxy10_labels 用真实 proxy10.describe());agent.search.tests.* 全部、agent.notify.notify_test、agent.notify.quota_test、agent.outer.outer_test / quota_watch_test / watcher_test、agent.research.test_kg_proxy(1 项跳过)/ test_service、agent.relay.relay_test、agent.scoring.test_service 均 OK。(Codex 二审在只读沙箱里看到的 36 项报错是无法建临时目录,不是代码问题。)
以下为一审修复时的记录:
- VM:
test_g45_carryover_limits(43 个)、test_execution_contract、test_run_correctness、test_continuation_promotion、test_objective_ops、test_proxy10、test_score_parts、test_roles_controller、test_g39_search_fixes、test_x3_guard、agent.notify.notify_test全过。 - 既有测试的两处调整(新语义所致):
test_run_correctness.test_budget_cut_kill_is_interrupted显式设exec_health.term_grace_s: 8(默认宽限改 60 s,被测程序忽略 SIGTERM);test_x3_guard的假run_program接受artifacts,并断言视图盲检查用fresh。 - Spark 全量
bash scripts/test_on_spark.sh -j 2(最后一次,含agent.notify.notify_test与本任务全部改动、健康邮件窗口 3 h):65 个模块 62 OK、3 个失败,都不在本任务改动范围内,是并行会话正在改的文件: 本任务涉及的模块(agent.search.tests.*全部、agent.notify.notify_test、agent.dashboard.*、agent.report.report_test、agent.scoring.test_service等)全部 OK;前一次全量里唯一与本任务有关的失败(test_x3_guard的假run_program不接受artifacts)已修。agent.outer.outer_test:新建的未跟踪模块agent/outer/,测试读.claude/skills/outer-loop/SKILL.md,该目录被.gitignore(.claude/)排除,test_on_spark.sh不同步它(环境问题);agent.research.test_kg_proxy:kg_proxy 正按 Codex 审阅改成固定模板,测试与模块版本不一致(build_queries不存在、run()签名不同);agent.relay.relay_test:test_anthropic_json_injects_key_and_logs_usage用量字段不符,agent/relay本任务未改(可能与并行的用量 / 定价改动有关)。
8. fake 三环端到端(Spark,VEC_RUNS_ROOT=~/vec/scratch/g45_runs,flock heavy.lock)
| 环 | run | 结果 |
|---|---|---|
A search_t1_g45_carry_fake.yaml | 20261003-171426-search-t1-g45-carry-fake | 节点 3(慢 Engineer,删掉 run.py 写 helper.py)在交接点被切 → carried_over(engineer;HANDOFF:run_py false、method_state placeholder、改动文件 (deleted) run.py, METHOD.md, helper.py、queries_used 0);节点 5(run.py sleep 900 s)在 cutoff 被切 → carried_over(execution);收尾日志 “cut-off reached with 1 node(s) in flight [5]: waiting up to 240 s …”,节点 5 结算后才进终选;package / verify ok |
B …_ring2.yaml --continue-from A | 20261003-172936-search-t1-g45-carry-fake-r2 | 护栏 seed 3–7 → 8–12;comparable=True(完整 hash 不同、search_hash 相同 e0b90d45…);两个快照校验通过并复制;resume 队列 [3, 5] 先于抽签:节点 6(engineer,按 slow_engineer.ops: [resume] 仍慢)再次被切 → carried_over;节点 7(execution,同一提交重跑)又被 cutoff 切 → carried_over;新程序训练 per_view 权重(artifacts/per_view 8 个树目录);package / verify ok |
C …_ring3.yaml --continue-from B(max_resume_per_ring: 2) | 20261003-174729-search-t1-g45-carry-fake-r3 | 护栏 seed → 13–17,chain_exposed_seeds 3–17;comparable=True;队列只有 B 的再带走节点 [6, 7](工作 …r2#6、…r2#7),A 的节点 3、5(工作 ringA#3/#5,经 B 再次导入)没有被再次续做;B 的两个历史 resume 节点不占 C 的 2 个名额;节点 18 续做 B#6:提交里有上一段的 helper.py,轨迹路径以 nodes/6/carryover/trajectory/engineer.jsonl 开头,打分 48.48、审查 pass;节点 19(execution)再次被切 → carried_over;package / verify ok(终选给基线,lineage_segments 为空:胜者无祖先链) |
未在端到端里直接看到的:晋级 / 护栏执行的 weights: replay(fake 程序的复跑目录被清理,seed_entry 原先不记模式——已补 seed_scores.<seed>.weights_modes,下次 run 可见;回放路径由 WeightsTest 在真实 run_program 上覆盖);冷启动重现检查(三环的终选都只剩基线,没有入围训练型候选)。
9. 文件清单
新增:agent/search/health.py、agent/search/carryover.py、agent/search/tests/test_g45_carryover_limits.py、agent/configs/dev/search_t1_g45_carry_fake{,_ring2,_ring3}.yaml、本报告。
修改:agent/search/{controller,execute,runenv,archive,budget,continuation,dataview,views,evalspec,final,viewcheck,roles,tune,fake_llm,scoring_def}.py、agent/search/CONTRACT.md、agent/search/tests/{test_run_correctness,test_x3_guard}.py、agent/prompts/{search_spec,ideas_T1__val}.md、agent/configs/experiments/chain/{t1_D_12h,t2_embryo_interp_12h,t2_heart_extrap_12h}.yaml、agent/notify/{reports,cli,__init__}.py、agent/notify/README.md、agent/notify/systemd/vec-notify-health.{timer,service}、agent/dashboard/glossary.py、agent/report/collect.py、scripts/prepare_submission.py、notes/architecture/10_harness_era_architecture.md(§4.6 跨环续做、§4.8 候选执行上限 / 训练一次多 seed 复用 / 终选预留 / 链级 seed / 搜索可比性指纹、§9 表与 §9.1–9.3)、notes/architecture/18_carryover_and_seed_policy.md(定稿)、notes/plan/tasks/G39-G43_plan_2026-10-03.md(§7.2 G45 表 45.6、45.7)。
10. 遗留
- 健康监督只观测(
enforce: false):上线后看would_hang的误判率再决定是否打开;打开会改变评测规格(不改 search_hash)。 limits默认改 360 min 与 threads 8 改变完整评测规格;接续旧 run 时由 search_hash 保持可比(旧锁从磁盘视图重算),但旧 run 目录必须还在。- Codex 实现审阅 5 / 7 / 8 / 11 后置(见 §6)。
- 续做时父节点审查 fail 直接拒绝(
resume_refused),没有做“先自动复审继承物”。 --seeds批量接口、每节点提前三 seed、续同一 opencode 会话按审阅不做。- G39-G43 计划 §7.2 外另有一处(E03 停止原因那行)也用了编号 “45.6” 指 stop_reason,与本次新增的 45.6(跨环续做)编号冲突,未改动那一行,需要人定编号。
- Engineer 会话内的试跑由会话自己的停顿探针管(已加 GPU 计算为活动);
vec-score本身不运行程序,没有单独的执行监督。 - fake 三环与旧 ring 的 run 目录留在 Spark
~/vec/scratch/g45_runs/(4 个 run,含第一次因event(kind=…)参数冲突在 ring 1 收尾报错的20261003-165615-…,该 bug 已修),可删。