模型调度设计审查
总体结论:调度骨架健康,恢复闭环是主要短板。原子领取、幂等键、收据先落盘、冻结绑定与证据链四大支柱经静态审查与对抗验证均成立;7 条确认缺陷中 4 条集中在故障恢复路径(旧收据卡死节点、超时不停止底层会话、停机跳过取消、CLI 误判污染他人运行态),另有 lane 配额权威分裂与容器释放核验缺口。主检出存在 2 个测试可运行性断点。建议在下一次生产窗口前完成 P0 修复项。
结论与修复优先级
四维审查(状态机竞态 / 并发配额 / 恢复对账 / 分层契约)的整体评价一致:核心调度合同可信,不需要重构;问题集中在异常路径的收尾与两套配额权威的收敛。按下表优先级修复即可。
| 优先级 | 项 | 理由 |
|---|---|---|
| P0 · 下次生产窗口前 | D2 CLI 误判重复启动 · D1 旧收据卡死节点 · D6 停机跳过取消 · T1/T2 测试断点 | D2 是一次未派发的重复请求即可停整批的放大器;D1/D6 让单次执行前崩溃或正常停机留下无法自动恢复的节点;测试断点使标准验证命令在主检出不可用。 |
| P1 · 近期 | D3 lane 配额统一 · D4 容器释放核验 · D5 超时取消 | D3 是已配置 A=2 与旧 SQL guard 的现实冲突;D4/D5 造成账面额度与物理资源脱钩、模型额度继续消耗。 |
| P2 · 排期 | D7 MCP 身份对账 · 文档漂移清理 · U 系列逐条核验 | 影响面窄或已有人工流程兜底,但应在扩量前收敛。 |
调度架构全景:四层派发栈
任务从用户目标到模型执行经过四层,各层职责与真相源分离;协调器持本地 SQLite、节点主动连接,符合「不搞第二套调度器」的项目原则(storage/orchestration.py:1、AGENTS.md:106)。
协调器任务状态机(orchestration.py:120,191,236,268,292,305):
派发语义为 at-most-once:worker 收据先落盘、start 确认后才执行、未知执行不重放(orchestration_worker.py:371,403,774,794)。已知缺陷 D1/D5/D6 均发生在这条链的异常出口。
做对了什么(审查确认的设计优点)
| 支柱 | 实现事实 | 证据 |
|---|---|---|
| 原子领取 | claim 的选择、并发/内存预留、写 leased 在同一 BEGIN IMMEDIATE 事务;双 worker 同领有保护;start 前再查暂停/遥测/内存/取消。 | orchestration.py:72,191,236,247 |
| 幂等合同 | 同 idempotency_key + 同 spec 返回原任务,改 spec 报 409;阶段键 owner:campaign:instance:stage;提交先持久化意图再网络提交,ACK 未知重发同 spec。 | orchestration.py:141-216; e2_pipeline.py:412,496,602 |
| at-most-once 执行 | 收据(claimed/start_requested/complete_pending)先落盘;只有收到持久 start 确认才运行 handler 或创建 Lody 会话;重启 recover 只补报不重放。 | orchestration_worker.py:371,403,774,794,804 |
| 三层持久化分离 | 业务账本(准入权威)/ Pipeline 阶段意图 / 协调器执行租约各自独立;E2 判分与重跑不经协调器。 | e2_pipeline.py:32,175; orchestration_e2.py:15 |
| 冻结绑定 | 来源 plan 冻结完整配置 + 任务字节 SHA + campaign/stage 字节 SHA,每次派发复验;policy hash 三处一致才可加载;run-bounded 结束收据拒绝同窗口重放。 | e2_pipeline.py:116,320,330,348,796,852 |
| 抽检与撤销隔离 | 语义抽检独立 SQLite 队列,节点离线/抽检超时不阻塞自动发布;撤销只写 sidecar 保留更正链,不改原始 release。 | e2_spotcheck.py:27,296; native_release.py:278 |
| 测试文化 | 7 个调度测试文件 149 个定义:假 Lody/CLI、假时钟、临时库,锁定内存预留、旧租约 fencing、完成 ACK 重试、断连重连等不变量。orchestration 子集实测 78/78 通过。 | storage/tests/test_orchestration*.py 等 |
确认缺陷(7 条,均经对抗验证于 bc191cd)
每条由独立怀疑者代理重读源码尝试推翻后确认;「高危」指可由现有控制流触发且影响整节点/整批,非理论风险。
高危 · 恢复D1 · 未启动租约回收后,旧收据使节点永久卡死
worker 在执行前崩溃一次,重启后即退出码 2 且无自动恢复路径。
V1+V7 合并
D1 · 未启动租约回收后,旧收据使节点永久卡死
worker 在执行前崩溃一次,重启后即退出码 2 且无自动恢复路径。
节点已写 claimed/start_requested 收据、尚未获得 start 确认时崩溃;中央 expire 把任务回 queued 并清 machine_id/lease_token。重启后 recover 携旧令牌补报 complete 被 409 拒绝却仍 halted;acknowledge 要求远端 machine_id 等于本节点(queued 或已换主时 403);中央 release 只接受 needs_reconciliation。收据文件名仅按 task_id 命名,同节点重领原任务也被旧收据拒绝。
一次执行前崩溃 → 该节点 worker 此后每次启动都因旧收据退出 2,原任务与后续任务都无法经现有入口继续,须人工改收据。
修复方向:收据按租约(lease_token/attempt)区分执行尝试;提供「核实旧租约未启动且已失效 → 封存收据」的可审计确认入口,保留旧令牌隔离。
orchestration.py:175,232,322,309; orchestration_worker.py:306,403,425,770,879; orchestration_reconcile.py:23-32; 现有测试仅验证中央换主(test_orchestration.py:106),未串联本地恢复。
高危 · 竞态D2 · E2 CLI 把合法的重复启动拒绝误判为 unknown_completion
一次未派发的重复请求即可改写他人运行态并暂停整个 campaign。
主调度已复核代码
D2 · E2 CLI 把合法的重复启动拒绝误判为 unknown_completion
一次未派发的重复请求即可改写他人运行态并暂停整个 campaign。
start 子命令捕获所有异常后,只按「任务当前是否 running」判断,便写 needs_infra_review、halt campaign、记 unknown_completion——不核对本次调用是否取得启动权。执行者 B 的重复/迟到请求在入口被正确拒绝,却把执行者 A 正在运行的任务改写;A 的 runner 继续跑,账本不再表示其状态,整批停派。
A 已启动求解并阻塞等待 runner;B 并发调用同一实例 start → B 被拒 → CLI 异常处理把 A 的 running 改 needs_infra_review + campaign halted。
修复方向:未知完成处理限定在本次调用成功提交启动权之后;状态更新加本次尝试身份条件;补经 CLI 入口的重复启动回归测试。
e2_queue.py:304-305,370-375,491-499;数据库 trigger 允许 running→needs_infra_review 并联动停派(:74-94);现有 CLI 测试只模拟本调用已取得启动权后的失败。
高危 · 配额D3 · lane 配额没有统一的原子执行合同
A=2 与旧 SQL guard 冲突;不装 guard 时 B=1 可被两个控制器突破。
主调度已复核代码
D3 · lane 配额没有统一的原子执行合同
A=2 与旧 SQL guard 冲突;不装 guard 时 B=1 可被两个控制器突破。
Pipeline 允许 lane_solver_limits A=1或2/B=1(e2_pipeline.py:77,ready 模板已配 A=2);但 install_guards 的 e2_dispatch_guard trigger 固定「同 lane 不得已有 solver」——装了 guard 的账本上第二个 A 在 UPDATE running 时被 ABORT。不装 guard 时,queue.start 事务只原子检查全局<2,不查 lane:两个不同 state_db 的控制器可各启动一个 B,突破 B=1。
带旧 guard 的账本 + A=2 配置 → 第二个 A 通过 Pipeline 准入后被 SQL 拒绝,作业失败并停派;无 guard + 双控制器 → B 路双开。实际部署账本是否装 guard 未核验(须逐账本查 trigger)。
修复方向:把冻结 lane 额度纳入账本原子启动检查;显式迁移或拒绝不兼容的旧 guard。
e2_pipeline.py:77,542; e2_queue.py:71-83(trigger 原文),370-375,475; config/e2-ready-pipeline.example.json:14; test_e2_pipeline.py:821 与 test_e2_queue.py:149 分别断言相反规则,无联合验证。
高危 · 资源D4 · 阶段内部超时可在容器仍运行时释放执行槽
账面额度已归还,物理资源仍被占用。
静态确认未复现
D4 · 阶段内部超时可在容器仍运行时释放执行槽
账面额度已归还,物理资源仍被占用。
准备/后处理阶段用 subprocess.run(timeout) 终止直接子进程,但不在阶段所有者处核验后代进程或容器停止。阶段 TimeoutExpired 退出被 worker 视为普通 failed,协调器立即释放槽位与内存预留。
preparation_command 已启动本题容器,setup 阻塞超过 preparation_timeout → 外层 Python 杀 helper、finally 无法执行 → Docker 容器继续由 daemon 运行;节点领取新任务时旧容器仍占 CPU/内存。后处理创建独立资源时同窗口。
修复方向:阶段所有者核验后代/容器停止;无法确认清理时上报 needs_reconciliation 而非普通 failed。
e2_pipeline_stage.py(timeout 调用); e2_b08_prepare.py; orchestration_worker.py:371; orchestration.py:268(failed 即释放)。
高危 · 恢复D5 · Agent 作业超时不停止已知的底层会话
job_timeout 只限制 worker 等待时间,不限制执行。
HANDOFF #156 实录对应
D5 · Agent 作业超时不停止已知的底层会话
job_timeout 只限制 worker 等待时间,不限制执行。
Agent 达到 job_timeout_seconds 或状态查询抛错后,worker 直接提交 needs_reconciliation 并移除活动任务,不调用已有的取消+idle 核验路径(该路径存在于 cancel 流程:机器 online、execution/phase idle、无 activeTurn、queuedTurnCount=0)。中央保留预留(正确),但会话继续执行消耗资源与模型额度。
已取得 session/user turn 的 Agent 持续 busy 到截止 → worker 上报未知并退出监督 → 底层会话继续跑;此后调协调器 cancel 只返回已有未知状态。HANDOFF 记录过实际超时后 session 仍 busy、须人工取消并确认 fresh idle 的事件——该流程未固化进超时代码。
修复方向:到期时先尝试精确取消并核验停止;无法确认再保留未知态交人工对账。
orchestration_worker.py:598,610,715,757,834; HANDOFF.MD:156; 取消核验合同 lody_client.py:417。
高危 · 恢复D6 · 常驻 worker 停机可跳过取消并以 0 退出码留下未知执行
正常 stop 不能证明底层执行已停止。
静态确认
D6 · 常驻 worker 停机可跳过取消并以 0 退出码留下未知执行
正常 stop 不能证明底层执行已停止。
常驻 run 在清理前确定退出码,随后立即设置 _heartbeat_stop;同一个 Event 又控制 Agent 监测与取消确认。它让正在等待状态的 Agent 直接走超时出口,或让取消确认在首次 busy 观察后提前截断(stop_timeout 10s < cancel_timeout 45s)。
Agent 刚查询到 busy、等待下次轮询时收到 stop → 主循环先选定返回 0 → finally 设 _heartbeat_stop → 任务线程被唤醒后直接报 agent_job_timeout,未发送 Lody 取消;已选定的返回值不再改变。另一路径:取消已发送但首次查询仍 busy,等待事件提前截断后续 idle 确认。
修复方向:停止确认完成后才关闭监测;退出码按最终清理状态确定。
orchestration_worker.py:598,615,852,880,956; lody_client.py:417。
中危 · 恢复D7 · MCP create 未知后不回收迟到身份、不 resume 原_operation
已受理的会话无人停止,只能人工对账。
V8 · 由高危降级
D7 · MCP create 未知后不回收迟到身份、不 resume 原_operation
已受理的会话无人停止,只能人工对账。
MCP create 请求未返回身份时,取消或超时退出唯一消费回包的路径;迟到身份不持久化,也不用收据里的稳定 operationId 走 resume 找回。拒绝重放避免了主动重复 create(正确),但无法找回并停止已受理的会话。
Lody 已受理 create、回包在途时协调器要求取消 → worker 记录 create_pending_without_session_identity 并退出 → 底层会话随后开始执行,回包被丢弃;重启只上报未知态。
修复方向:按原 operationId 做身份对账与停止流程(服务端 resume 合同已存在)。
orchestration_worker.py:636,654,668,680,687,700; lody_client.py:142,176; AGENTS.md:114(resume=true 要求尚未实现)。
未验证发现(13 条)
以下发现未做对抗验证,按原始严重度列出;U6/U12 与已确认的 D5/D3 同源。U9 为其中唯一高危。
| # | 级别 | 发现 |
|---|---|---|
| U9 | 高 | manage start-pipeline 校验配置后固定启动无时间上限的 run,无法传入时间上限、不要求来源上限,绕开正式生产的有界入口合同(orchestration_manage.py:119 vs AGENTS.md:34)。 |
| U4 | 中 | external_preparation 路径只消费已有 ready、从 solve 起创建受管作业:外部镜像/环境准备不登记执行槽或内存预留。HANDOFF 自身已把「统一 Windows 执行槽 3」标注为待接线工程缺口。 |
| U5 | 中 | GPU 只有能力标签:派发门不检查 GPU 设备/显存/占用遥测,不能兑现 AGENTS.md:104「派发前核对 GPU 占用」。 |
| U7 | 中 | run_bounded 只在 tick 前后检查 deadline,tick 内部 claim/submit 不检查剩余时间:单 tick 跨越截止点仍能启动新来源/阶段。 |
| U10 | 中 | 来源计划冻结了配置,但来源上限与 lane 配额仍读重启时的 self.config:同一控制器重启可改变已冻结窗口的全局配额。 |
| U11 | 中 | campaign.per_task 预算只写入收据,不参与命令构造、不与零调用预检比对——影子预算。 |
| U13 | 中 | stage_sources 只验证调用方列出的文件,不要求覆盖实际执行的阶段实现:非空清单不能证明本次执行用了冻结实现。 |
| U1 | 中 | reviewed 的 reviewing 校验在写事务之外,最终 UPDATE 不校验状态/所有权/版本:并发收尾后提交者可覆盖已提交终态。 |
| U2 | 中 | _get_lody 无初始化互斥:并发 Agent 可在 MCP 握手前取得共享客户端发请求,任务被误记未知并全局 halt。 |
| U3 | 中 | 待对账标志只在 claim 前检查:领取回包后的登记与执行启动门不再检查,隔离与派发之间有竞态窗口。 |
| U8 | 中 | 管理器先启动子进程再写 PID 收据,双写间隙无恢复机制:未登记的守护进程绕过判重。 |
测试可运行性:两个主检出断点
实测(系统 Python 3.12.10,主检出,基线 036a04b 工作树):orchestration 子集(5 文件)78/78 通过(22s);完整 storage 套件 246 跑 36 错——35 个导入失败 + 1 个真实失败。
| 断点 | 根因 | 处置建议 |
|---|---|---|
| T1 · 35 个测试模块导入失败 | native_release.py:9-12 与 verify_native_alignment.py:11-16 把 swefactory_self/results、training/swe_world 插入 sys.path;主检出 swefactory_self 子模块未初始化 → split_isolation/to_sft_qwen 缺失。e2_pipeline/e2_queue/e2_admission 等调度测试全部传递性依赖该数据层模块。 | 跑全套前先 git submodule update --init 并核对 gitlink;或把调度层测试与数据层模块解耦。AGENTS.md 的标准验证命令在当前主检出不可全跑。 |
| T2 · test_e2_b08_prepare 真实失败 | e2-b08-stage.json:10 等 4 个 committed 配置把冻结引用钉在已删除的 worktree E:\Deeplearning\wt\executor-e2-b08\(e2-auto-20-...-stage.json:12 钉 Lody 管理 worktree)→ e2_queue.verify 对缺失文件抛 FileNotFoundError。 | 冻结引用应指向仓库规范路径或 campaign 归档;对已封存批次重新出冻结收据,不改写原 run 身份。 |
文档与配置漂移
| 项 | 冲突 | 证据 |
|---|---|---|
| 主调度机器 | 2026-11-02 框架页「现在怎么调用」推荐 Windows Think;AGENTS.md 与角色草案优先 Mac——同项目两处直接相反。 | docs/2026-11-02-Lody多机多Agent执行框架.html:11,19 vs AGENTS.md:84-85, lody-role-design.json:23,31 |
| 生产主流程 | 框架页仍展示旧语义审查链(Mac Review 发布前)并指向旧模板,未限定为旧批次;现行规则已是四阶段自动准入 + 独立抽检。 | 框架页:72,73,91 vs AGENTS.md:122, e2_pipeline.py:32 |
| solver 额度描述 | HANDOFF #99 仍说 _solver_available 按 lane 检查 busy/reserved 为 0;代码已支持 lane_solver_limits 并按 instance 合并。但同条提到的 SQL trigger 限制仍在(即 D3)。 | HANDOFF.MD:99 vs e2_pipeline.py:77,542 |
| 执行槽目标 | HANDOFF 要求 Windows 统一 3 槽(镜像/外部准备同门);nodes 配置仍 max_jobs=2,external_preparation 不进槽(U4)。 | HANDOFF.MD:13,17 vs config/orchestration-nodes.json:7 |
| 时间边界 | 新方案 90 分钟从选题起算;run_bounded 的 deadline 从函数调用时建立——入口本身不保证端到端窗口,须调用方扣除准备时间。 | HANDOFF.MD:13,87 vs e2_pipeline.py:796,803 |
| resume 合同 | AGENTS.md 要求 create/chat 稳定 operationId + resume=true 续接;worker 仅 MCP create 有固定 ID,CLI 用标题 + 本地不重放,无 resume 实现。 | AGENTS.md:114 vs orchestration_worker.py:403,642,659 |
| P3 held 出路 | 文档把「需语义复核」交给 Mac Review,但 held→blocked 后 claim-review 不接收、reopen-review 只接收 quarantined:无现成复核续接入口。 | docs/2026-11-03 卡点页:127 vs e2_queue.py:167,437,531 |
测试覆盖缺口(七文件盘点结论)
| 缺口 | 说明 |
|---|---|
| 真实崩溃恢复窗口 | 无真实 kill worker 后验证仍活着的命令/Agent 不重复执行;start_requested、start ACK 后收据未落盘、CLI 已受理而身份未落盘等窗口未测。 |
| 双 Controller 重叠 | 旧控制器已过 _can_dispatch、HTTP 提交仍在途时租约到期且新控制器接管的窗口未测。 |
| 真实资源清理 | 取消测试只跑单个 sleep 进程;无孙进程/Docker/WSL 资源真实退出的验收(对应 D4)。 |
| 并发 Agent 会话 | 并发 worker 测试全为 command;未覆盖两个 Agent 会话同时创建/完成、共享客户端身份隔离(对应 U2)。 |
| 模型配额调度 | 无 token 预扣/结算、RPM/TPM、402/429、跨后端回退的调度测试——Magpie 链在调度面无回归锁定。 |
审查边界与方法
方法:6 名读者并行绘制设计地图 → 4 名维度审查者独立复核 → critical/high 发现各由 1 名对抗验证者重读源码尝试推翻(8 条全部确认,1 条降级)。全程只读:未修改文件、未运行安装/smoke/评测脚本、未触碰运行态协调器、账本或 Lody 目录;仅在主检出运行一次标准测试命令取证。
基线:读者阶段为 036a04b;审查期间 PR #10(角色设计)与 PR #11(synth-throughput,storage +3778 行)合入,验证阶段在 bc191cd 上完成——7 条确认缺陷均对照 bc191cd。新增的 e2_prepare_driver.py 及共享槽改动有独立验收收据(HANDOFF 2026-11-03「准备流程优化落地」),本轮未深审,建议随后续批次单独复核。
未覆盖:13 条 U 系列发现未做对抗验证;运行态部署配置(实际节点并发、账本 trigger 安装状态、实时 role 绑定)未查询;eval/work 运行数据未读取(按 AGENTS.md 检索边界)。