Files
FreeCADWasmDev/docs/WEB_MIGRATION_EFFICIENCY_PLAYBOOK.md
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

56 KiB
Raw Blame History

Web/WASM 移植效率手册

本文总结一种适用于大型原生项目移植到 Web/WASM 的高吞吐执行方法。它不依赖 FreeCAD、Qt 或某个具体 CI 平台;项目只需要提供任务账本、批次清单、上下文 manifest、增量 gate 和周期日志。

结论先行

移植效率通常不是由编译器并行度决定,而是由每个回合在真正实现前后重复做了多少管理工作决定。最有效的边界是:

  • 外层调度器拥有任务领取、批次规划、上下文轮换和账本持久化。
  • 单个代理回合只处理一个 (module, tier, family) 批次。
  • 批次、上下文和失败账本都采用幂等快路径;没有状态变化就不重建、不重哈希、不重放。
  • 实现阶段只做当前批次的增量 gate;全量验证集中到明确的收口阶段。
  • 真实编译、运行时 fixture 和 gate 是不可消除的工作,应该单独计时,不能把它们误判为调度低效。

如何定位慢点

每个周期日志至少记录:startedAt、completedAt、timedOut、命令开始/结束数、失败数、批次 ID 和准备耗时。用下列指标拆分问题:

新版 scheduler 还会在每个 command event 上记录 schedulerPhase、schedulerStartedAt、schedulerDurationSeconds,并在实时 status 中提供 lastStepPhase、lastStepDurationSeconds 和累计 phaseTiming。这使得当前慢步骤可以在回合运行中直接识别,而不必等待整回合结束。

指标 计算 目标
准备耗时 prepare_end - prepare_start 通常小于 1 秒
有效回合率 effective_turns / completed_cycles 大于 90%
命令成功率 (completed - failed) / completed 大于 98%
超时率 timeouts / completed_cycles 小于 5%
批次耗时 周期结束时间减开始时间 由功能复杂度决定
管理开销占比 (prepare + rotate + ledger) / batch_duration 小于 5%

如果准备耗时已经低于 1 秒而批次仍耗时数分钟,继续优化读取没有收益,应该优化批次规模、fixture 复用和 gate 的进程启动次数。

Timing v5:完整执行链与基线

一次实现周期按下面的所有权和时间边界统计,任何耗时只能进入一个桶:

阶段 所有者 v5 计时桶
续接、批次规划、context 预检 外层 scheduler preparation/*
首次选择实现入口 Codex agent/initial-decision
读取源码和现有 adapter shell source/read
读取后设计 Codex agent/post-read
生成并应用补丁 Codex agent/editing、agent/post-edit
首错定位和修复 Codex agent/failure-recovery
link closure 和增量链接 shell build/preflight、build
focused gate shell focused-gate
gate 后证据/checkpoint Codex agent/post-gate、agent/synthesis
子进程退出和 JSONL 排空 scheduler agent/finalization、scheduler/stream-drain
无事件触发恢复 scheduler agent/idle-stop

2026-09-05 在 timing-v4 最近 20 轮基线中:总 wall time 9978.459s,平均 498.923s/轮;命令执行 686.188s,外层准备 13.007s,粗粒度代理间隙 8743.321s,未归因 535.943s。代理间隙占 wall time 87.6%,准备仅占 0.13%。16 个唯一批次产生 4 个恢复轮,恢复轮累计 2075.315s,占 wall time 20.8%。因此继续优化 planner 或 -j 不是主方向,主方向必须是减少回合内 重新理解、硬编码 gate、patch 冲突和同一批次恢复轮。

timing-v5 的对应措施:

  • active prompt 从重复规则全文改为已预检工作包,直接注入 record 的 id/name/source 和现有 probe/facade/worker/gate 候选;子回合不再读取 59KB nextTask.md、45KB 完整 task state 或无关前置卡。当前 24 条批次 prompt 约 4.9KB。
  • 同一 mutation epoch 内对规范化命令计数,记录重复 read/gate/build 的次数和耗时; 同一命令达到 3 次会收口回合,而不是继续空转。
  • 两次 apply_patch 校验冲突即收口,下一轮按当前文件重建小 patch;不允许连续数分钟 重放已失效的补丁上下文。
  • preflight、build、focused gate 和 context validation 必须使用独立命令。复合命令仍会 标记为 build+preflight、build+focused-gate 或 focused-gate+context,不再把链接 时间误报为 preflight。
  • JSONL stdout/stderr 排空由每线程 5 秒降为每线程 0.5 秒,固定回合尾开销目标从约 10 秒降至约 1 秒。实测发现主要的 10.907s 实际位于 turn.completed 之后的 Codex tool-host 退出等待,因此 v5 在终态事件落盘后保留 0.5 秒 drain,再只终止该 已完成子进程并将退出归一为成功;不等待 tool-host 的固定延迟。
  • tools/wasm-probe-runtime.mjs 统一 build/bin 归一、pthread worker CJS staging 和 locateFile;worker 源文件变更时刷新 staging,不再由各 gate 复制易失效的 loader。
  • 批次工作包从 CMake probe 源和现有 artifact 推导准确 target/build tree,并注入完整 preflight 命令,避免先调用缺参数的 checker、再猜 configure/configure/bin。
  • 每轮把 input/cached-input/output/reasoning-output token 写入 timing v2;源码读取先定位, 单文件只读命中前后 80 行、每条读取命令输出约 240 行,防止秒级 shell 命令产生数十万 token 的后续推理负担。
  • 提示边界之外再加执行护栏:子回合 PATH 前置 tools/scheduler-bin。rg wrapper 拒绝 仓库根和大型目录参数,git wrapper 拒绝未带 -- <current-files> 的 diff/status;在 大输出进入模型前以退出 64 阻断。构建和其它 git 子命令仍转发到真实工具。

首个完整 v5 样本 cycle 2586 用 184.053s 完成 24 条 Draft 批次,较 v4 最近 20 轮 均值 498.923s 低 63.1%,但仍有明确可压缩项:命令只占 3.254s;post-read 53.902s、一般 decision 36.345s、两次可避免首错的 failure-recovery 25.573s、 synthesis 16.407s、post-checkpoint 13.026s、tool-host finalization 10.907s。 因此下一阶段目标不是继续优化 3 秒 gate,而是用读取上限减少 post-read,用精确 target/build hint 消除两次首错,并用 terminal-event shutdown 消除 finalization;无构建 的同类 24 条批次目标应降到约 90-120 秒。单样本只用于定位,至少 5 轮后才用于确认趋势。

Post-read 根因复盘与结构修复

cycle 2590-2599 的逐事件复盘得到可复现数据:43 条 source/read 命令产生 1,354,448 字节输出,紧随其后的代理处理耗时 543.476s。其中同一个 DraftRuntimeProbe.cpp 被读取 29 次、同一个 module gate 16 次、同一个 Facade 15 次;cycle 2595 的单条搜索输出达到 990,824 字节。这说明慢点不是读取系统调用, 而是每批创建新会话后重新解析相同源码,以及每次工具返回后模型重新处理不断增长的 完整 turn context。

彻底修复分两层:

  1. prepare_module_implementation_packet.py 在外层预检后运行。它只打开当前 manifest 指向的官方源文件,用 Python AST 或 source line 提取每条 API 最多 640 字节的函数片段, 总预算 12KB;同时保存 support file hash、native name 命中、dispatcher 入口和 manifest-driven gate 检查。该步骤是确定性本地处理,不产生模型往返。
  2. scheduler 按模块缓存 thread.started 的 session id。相邻批次 module 相同则调用 codex exec resume,直接复用已经理解的 probe/Facade/Worker/gate;硬上限 8 轮, cache 根据上一轮 input 增量预测下一轮 token,预测达到 800000、模块切换或失败时立即轮换。token 上限优先于轮数, 这样既消除跨批重复读取, 又限制长期上下文增长。

批次工作包会直接嵌入 implementation packet。恢复轮明确禁止重读 support files,只允许 针对新 record excerpts 和实际需要修改的命中行工作。与此同时,纯函数和 geometry-value 批次可从 24 自适应扩大到 32,进一步减少新会话数量;对象契约和重型 OCCT 批次仍保持 原内存上限。

真实运行验证:fresh cycle 2605 只执行 4 条命令和 1 次 source read,input token 为 153075、post-read 为 15.786s;此前批次常见 5-8 次读取、60-100 万 input token 和 50-130 秒 post-read。随后 cycle 2606 使用同一 Draft session 恢复,source read 与 post-read 均为 0,证明跨批重复源码理解已经消除。

恢复轮仍有 preflight/gate/checkpoint 之间的代理决策等待,因此进一步将这些确定性步骤移到 run_module_batch_pipeline.py。implementation packet 给出 target、build tree、artifact 和 generic module gate;Codex 只做源码判断/编辑并立即返回,scheduler 根据 native source 与 artifact mtime 决定是否增量构建,然后自行执行 preflight、sync、focused gate 和 incremental context。失败流水线的首个 step 会注入同一 session 的下一轮;成功步骤不再消耗模型调用。

外层 pipeline 实测分成两类:无源码修改的恢复批次 cycle 2612/2613 中,代理分别只用 23.026s/20.025s、0 command、0 source read,随后 pipeline 分别约 2.8s 完成 preflight+gate+incremental context,完整周期约 24-27 秒。需要源码编辑的 cycle 2620 中, 代理只执行 3 个定点命令和 1 次 edit 后返回,pipeline 用 47.794s 增量构建、6.628s 同步、2.716s gate 和不足 0.1s 的 preflight/context 收口。真实编译成本保留,但构建 前后不再有模型决策等待。

最终结构样本 cycle 2622 更清楚:恢复会话依据 implementation packet 直接完成 1 次编辑, sourceReads=0、post-read=0、代理 turn 26.026s;外层 pipeline 用 50.236s 构建、 7.247s 同步、2.754s gate 和各 0.04s 左右的 preflight/context,完整有效周期 87.153s。与此前同类需要构建的 216-295 秒周期相比缩短约 60%-70%,而真实 60 秒 构建/同步/gate 成本没有被隐藏。此时“代理读取后的处理”已经从主耗时项中消失,剩余主项 是可审计的真实构建成本。

构建复用采用“成功后信任”:首次没有 cache entry 时即使 artifact mtime 较新也必须构建; 只有一次真实 build+gate 成功后,才保存 native probe/CMake/build.ninja 输入 SHA-256 与 runtime/WASM SHA-256。后续只有输入摘要和 artifact 摘要同时匹配、且直接 link archive 没有更新时才跳过,mtime 仅用于诊断和依赖失效。一次恢复中 曾发现首次 mtime seed 跳过后 gate 失败,策略已收紧并通过真实重建恢复,失败记录保留。

Gate 只缓存 TypeScript 编译产物,不缓存 native 结果。cache key 包含直接 TS 文件、Node 版本和 TypeScript package;tsc 完整成功后才写 .complete marker。每批仍启动真实 Worker、 调用真实 WASM dispatcher 并生成新 evidence。Draft 首次样本为 compile 0.539s、Worker ready 0.745s、dispatch 0.699s;下一批相同 TS 输入只跳过 compile,其他检查不变。

批次自适应不再只看最后一个 Codex turn,而是读取最近 3 个相同 (module,tier,family) 的 完整 effectiveCycleSeconds、记录数和失败状态。任一失败立即缩半;平均超过 420 秒减 4; 平均低于 90 秒且吞吐达到 0.25 record/s 增 8;低于 240 秒增 4。最终仍受 pure/geometry 32、普通 24、object 16、Part generic 12 的内存上限约束。

所有效率修改由 tools/test_scheduler_efficiency.py 固化。当前 10 项回归覆盖:Draft 标准 gate、Material 非标准 _object_module_batch_gate、拒绝不兼容 Mesh gate、mtime/hash 内容 变化、直接链接 archive 枚举、快/慢/失败三种批次调整、session token 预测,以及 TS compile cache 命中时 Worker/native dispatch/resources release 仍真实执行。测试不运行全量功能矩阵, 但任何调度优化加载前必须先通过这组行为不变性门禁。

观察命令:

python3 tools/show_freecad_web_scheduler.py
python3 tools/analyze_scheduler_timings.py --cycles 20

状态页同时展示 batchFlow、activity、redundantWork、逐阶段真实事件 count 和 slowestAgentGaps。至少积累 5 个 timing-v5 完整周期后再与上述基线比较,避免把 单个复杂批次的波动误认为整体改进。

正确理解进度

大型项目通常同时存在两套进度,必须分开显示:

  • taskCards 是工作流卡片的终态计数。聚合出口卡(例如最终 Y/gate 卡)在所有记录收口前保持 in_progress,所以批次推进时这个数字可能不变。
  • implementationBatches 和 canonical API manifest 才反映批次移植进度。每个 focused gate 应更新记录 disposition、evidence 和已完成批次集合。

状态工具至少应同时展示 taskCards、implementationBatches、apiExposed、apiRemaining 和当前批次 ID。不要通过增加 task card 分子、提前关闭聚合卡或修改任务状态来制造进度。

必须实现的快路径

0. Feature 构建触发门

在 feature-first 模式中,文件变化本身不能触发 WASM 链接。先生成 module/capability registry,并将当前 batch selected IDs 与 implementationStatus=routed 求交;缺少任一记录时保存 implementation_incomplete 和 missingIds,直接进入下一实现回合。只有整批静态接线完整后执行一次目标构建。

桌面命令应拆入 FreeCADWeb<Module>DesktopCommandProbe,由模块 Worker 按需加载。结果从 {cmd,args} 和真实 BREP/Document 数据生成;禁止硬编码 fixture/result。构建 hash 只包含 build.ninja 中实际进入当前 target 的源文件,不包含同模块其它 probe。

2026-09-06 Part 基准:原 FreeCADWebPartBoxProbe 为约 70.5MB、50 个直接 archive,单次成功构建约 52-58 秒;拆出的 FreeCADWebPartDesktopCommandProbe 为约 17MB、26 个直接 archive,首次含 CMake 重生成 22.58 秒,稳定增量编译/链接 9.85 秒,无变更检查 0.06 秒。静态接线不完整时 pipeline 仅刷新两个 registry,0.136 秒返回且不构建。该数字是本机快照,不作为硬编码阈值;后续模块以相同相对指标评估。

1. 批次幂等

批次 manifest 必须包含 batchId 和选中记录 ID。checkpoint 保存 latestBatch.completed。当当前 batchId 已完成时,直接调用 planner 选择下一批;不得保留旧 manifest 再次运行相同 gate。

2. 上下文指纹

context manifest 的每个输入保存 bytes、mtime_ns 和 SHA-256。常规路径只比较大小和修改时间;只有变化或旧 manifest 缺少元数据时才读取文件并计算哈希。上下文未变化时输出 CONTEXT_PACKET_REUSED。

3. 失败账本快照

回合启动时读取一次失败账本,命令事件只更新内存对象;回合结束集中写入一次。重启恢复只导入尚未登记且包含 cycle.completed 的日志,并保存 hydratedLogs 索引。实现阶段默认冻结历史失败,只有直接阻断当前批次的错误才允许定点重放。

4. 卡片按需读取

不要每轮读取整个任务树。先通过文件名或紧凑索引定位活动卡、planned 卡和直接前置,再按需读取正文。全量卡片解析只允许出现在明确的最终审计命令中。

5. 外层拥有轮换

代理回合结束只写结果、证据和 phase=CONTINUE,然后返回。调度器下一回合统一执行 rotate、sync、prepare-next。代理回合禁止再次调用续接、planner、rotate 或全量 validator。

批次和回合自适应

默认批次大小只是起点。对每个 (module, tier, family) 维护最近 3 次周期的耗时和命令数:

if timeout or duration > 900s or commands > command_budget:
    next_size = max(min_size, floor(current_size / 2))
elif duration < 300s and commands < 24 and no focused gate failure:
    next_size = min(max_size, current_size + 4)
else:
    next_size = current_size

当前实现中,prepare_full_parity_module_batch.py 在未传入 --size 时读取 scheduler 的上一回合状态并应用该规则;显式 --size 4/8/16/24/32 仍可用于恢复卡或受控演练。批次 manifest 会记录 targetSizeReason,方便审计 planner 为什么缩小或放大。

对象契约、序列化、复杂几何和依赖外部资源的批次应使用较小上限;纯函数和只读常量可以使用较大批次。单个批次仍必须共享一个 native adapter、一个 dispatcher、一个 Facade/Worker 分支和一个 evidence JSON。

回合命令预算

在提示词和 scheduler 中同时声明硬预算。当前高吞吐实现回合默认不超过 20 个工具命令,source/read 不超过 8 个;项目复杂度不同可通过配置调整。达到读取预算后必须进入实现;禁止重复读取同一文件、重复运行已通过 gate 或加载完整日志。超出预算时先落盘 checkpoint,让下一回合继续。

阶段计时命令:

python3 tools/analyze_scheduler_timings.py --cycles 20

实时状态中的 currentTurnPhaseTiming 和日志事件中的 schedulerPhase/schedulerDurationSeconds 可用于确认构建、资源 staging、focused gate、源码读取和 checkpoint 各自占比。 调度器外层准备链还会记录 preparationTiming.continue、compact、continueRetry 和 checkpointRepair,用于区分正常续接与异常恢复的成本。

验证分层

实现回合只运行:

  1. 当前批次源码/依赖接线。
  2. 最小 native fixture 和统一 dispatcher schema。
  3. 当前批次的 Facade/Worker focused gate。
  4. validate_incremental_context.py。

完整 API、对象/格式矩阵、Chrome、性能、安全和发布 gate 必须在全部实现任务完成后集中执行,并由用户明确触发。这样可以避免每条 API 重复启动浏览器、重复编译或重复扫描全量清单。

本项目实测基线

本项目最近周期的可观测数据:

  • 准备耗时约 0.14–0.37s,说明上下文/账本快路径已经生效。
  • 短收尾周期会拉低总体中位数;最近主要实现批次(924–926)分别约为 761s、745s、606s,另有 1 个复杂周期达到 1800s 超时。
  • 主要剩余耗时来自真实 FreeCAD/WASM fixture 编译和 focused gate,不是任务卡读取。
  • 最近完成的 BIM 批次曾达到 24/24 exposed,统一 dispatcher、Facade/Worker、取消、reset 和资源释放均通过。

切换 timing-v2 后,1556 回合的阶段实测为 build≈381s、source/read≈63.9s、focused-gate≈1s;9 次命令失败主要来自系统冻结 Emscripten cache 和异常 fixture 重试。后续 timing-v3 使用项目内可写配置并修正 artifact 同步分类,避免把同步时间误计入源码读取。

因此下一阶段的收益排序是:批次自适应 > fixture/构建复用 > 减少 gate 进程启动 > 代理命令预算 > 继续微调上下文读取。准备快路径已实测低于 1 秒,继续压缩它的边际收益很小。不要用关闭验证或伪造结果换取吞吐。

步骤级计时优化

整回合耗时只能说明“慢”,不能说明慢在何处。run_freecad_web_scheduler.py 的 timing-v3 在每条 command event 上记录 schedulerPhase、开始时间和持续秒数,并生成受限的 docs/current/freecad-web-scheduler-timing.json。摘要只保留最近 50 个周期;show_freecad_web_scheduler.py 默认只分析最近 20 个周期,并输出:

  • prepare/checkpoint、context、batch-planning、source/read、build、artifact-manifest、resource-staging、focused-gate、checkpoint/rotation 各阶段总耗时和占比。
  • 最近窗口最慢的命令,带 cycle、phase、duration 和截断后的 command 文本。
  • timedCommands、unmeasuredCommands、unaccountedSeconds 和 measuredCycles,区分 shell 命令时间与代理推理/等待时间。
  • 当前回合的 currentTurnElapsedSeconds 及已测阶段累计值,长回合期间即可判断是否停在构建或代理处理。

摘要文件避免每次状态查看扫描数百个历史 JSONL;状态文件写入也按 250ms 节流,命令开始/结束、失败、阶段和回合收尾仍立即落盘。实时完整事件仍逐行写入 cycle log,不牺牲恢复证据。

优化判定顺序固定为:先看 unaccountedSeconds 是否异常,再看阶段占比,最后看最慢命令。source/read 高时收紧 manifest 读取;build 高时复用 RAM ccache、固定 -j8、使用可写 Emscripten cache 并避免失败重试;artifact-manifest 高时只同步变化的 runtime/WASM/data;focused-gate 高时扩大同 family 批次;prepare/checkpoint 高时修复账本/上下文重复读写。没有计时字段的旧周期只能作为历史吞吐参考,不能用于阶段优化结论。

构建阶段必须先集中源码修改,再执行一次正常构建;只有明确首错才允许一次重试。成功链接后不得因为重复读取、重复 gate 或相同源码 hash 再次重链;构建/重试完成后立即落盘结果并返回。当前 1556 回合构建占约 87.7%,因此构建去重的收益高于继续压缩不足 1 秒的准备阶段。

Artifact 子步骤缓存

web/tools/sync-assets.mjs 处理的 WASM/runtime/data 文件总量很大,单纯把 sync-assets 归为一个阶段会掩盖真正的复制和哈希成本。同步器现在把源文件 size+mtime+inode 签名与 SHA-256 保存在 docs/current/web-assets-sync-cache.json,并将最近一次子步骤耗时写入 docs/current/web-assets-sync-timing.json。只有签名变化、目标缺失或目标尺寸 不一致时才复制和重新哈希。实测首次建立缓存约 42.90s,相同输入的后续同步 约 0.04s(copied=0、hashComputed=0),manifest 仍每次写出且保留完整 artifact 哈希。状态命令会显示 copy、hash、manifest、命中数和缺失数, 因此后续优化应先确认是实际 artifact 变化,而不是重复同步造成的假耗时。

构建批量入口 tools/build_web_module_artifacts.py 同时为每个 target 记录 durationSeconds;判断构建瓶颈时使用单 target 最大值和总和,不把已完成的 no-op target 与真正的链接步骤混在一起。

服务端账户并发拒绝与普通断网必须分开统计。调度器只对明确的 Concurrency limit exceeded for account 做三次/180 秒的提前收口和指数退避; 普通网络断开不改变任务状态,也不作为本项目的性能优化目标。预算超限则在 当前命令结束后收口,保留 checkpoint,避免下一回合重复已完成步骤。

周期摘要会将 command event 之间、且没有活动 shell 命令的间隔归入 agent/reasoning。命令开始到结束的完整区间已经由 command duration 统计, 不会再重复归入事件等待;因此 unaccountedSeconds 只保留尚未观测到的尾部 时间。真正的源码读取、构建、artifact 和 focused gate 仍按各自命令单独统计。 优化时先确认 agent/reasoning 是否占主导,再决定是否缩小批次或减少工具调用。 若该阶段连续超过 FREECAD_AGENT_IDLE_LIMIT(默认 180 秒)且没有活动 shell 命令, scheduler 会保存 checkpoint 并结束该回合,让下一回合继续;这避免单次代理空转 耗尽 1800 秒时间片,同时不打断正在运行的编译或 gate。

默认 scheduler 使用 model_reasoning_effort=low,因为最近窗口实测代理推理 已经与 WASM 构建并列为最大成本。复杂首错需要更深分析时才设置 FREECAD_SCHEDULER_REASONING_EFFORT=medium|high|xhigh;状态中的 reasoningEffort 用于确认当前策略,避免在没有证据时长期使用高推理档位。

对于已有 pass_with_boundaries 证据的收尾批次,scheduler 自动降到 model_reasoning_effort=low,避免重新分析已经完成的 fixture;只有未收口批次 才继续使用默认 medium。

批次已完成且 checkpoint/evidence 一致时,外层 scheduler 使用 scheduler/fast-path:只运行一次 continue_web_task_cycle.py 重新规划下一批, 不启动 Codex。该路径仍以 canonical evidence 和真实 gate 结果为前提;若续接 失败则回退到普通实现回合,不改变任务结论。

最新效率评估

当前窗口的判断口径分为“快速调度周期”和“真实实现回合”两类。最近连续完成的 CAM/Measure 收尾批次已经进入 scheduler/fast-path,单周期约 0.97s;它们不应 再启动完整 Codex 回合。最近仍需代理处理的实现回合中,代理推理约占 85.5%, WASM 构建约占 11.3%,focused gate、准备和 artifact 合计低于 3%。因此下一 阶段的收益排序固定为:

  1. 继续扩大 evidence/checkpoint fast-path 覆盖,避免收尾批次进入代理。
  2. 构建前做 pthread/Qt/OCCT/外部库闭包预检,首错只允许一次定点修复,避免 100s+ 失败重链。
  3. 对仍需实现的批次使用低推理档位和最多 8 条检查命令,必要时才升级到 medium/high。
  4. 只在源 hash 变化时构建和同步;fast-dev artifact 不参与发布性能结论。
  5. 全部功能实现完成后,再统一切换 FREECAD_WEB_FAST_LINK=OFF 做 release link 和全量性能验证。

tools/check_web_link_closure.py 现在作为 build/preflight 阶段计时。它只读取 目标 build.ninja 的 link rule,不扫描源码树;CAM 这类单线程 target 不会因库路径 中的 pthread 字样误报,真正启用了 QtConcurrent/Emscripten pthread 的 target 才要求 -pthread。对 OCCT -lTK* 依赖还会检查选定安装前缀中的归档是否存在, 因此类似 TKMeshVS 的悬空依赖会在链接前直接报告,而不是消耗一次完整 wasm-ld。 预检同时缓存 build.ninja 的静态 link rule,并按 inode+mtime+size 自动失效; runtime/wasm 存在性和 OCCT 归档每次实时检查,避免旧缓存掩盖依赖变化。

若 focused gate 已生成 blocked_native_probe evidence(例如 Node pthread 初始化仍挂起),planner 会按 evidence 中的 apiId 将这些记录移出正常实现队列, 继续其它可执行模块;阻断状态和恢复证据保持不变,后续恢复阶段再定点重试。这样 同一外部/运行时阻断不会占满每个连续回合。

门禁复用还必须校验当前批次记录集合和 WASM/runtime artifact 的 SHA-256。只有 输入与产物均未变化时才复用既有 evidence;产物重建后自动重新探测。复用 blocked_native_probe 采用明确的 deferred 非零退出码,保持阻断语义且避免再次 等待完整运行时超时。

当 planner 返回 no_ready_batch 时,scheduler 现在只做一次轻量 planner 重试, 记录 scheduler/no-ready 后等待状态变化,不再启动没有实际批次的代理回合。这样 不会把空队列误记成实现失败,也避免代理重复读取同一份空 manifest。

fast-path 现在还检查 selected record 的逐条状态。顶层 pass_with_boundaries 中若有 not_implemented/planned,不会继续 fast-path,而是让 planner 依据 pending evidence 换到下一组记录,避免批次 ID 不变的快速循环。

planner 的 runtime evidence 索引写入 docs/current/module-batch-evidence-index.json, 使用文件 size+mtime+inode 做增量失效。索引只收录记录自身为 exposed、restricted、 nonportable、blocked 或 passed 的条目;父 evidence 的 pass_with_boundaries 不会 掩盖其中的 method_evidence_pending 或 source timeout,因此未实现 API 仍能进入 后续功能族批次。 同一 planner 进程同时复用 terminal/pending ID 集合,避免为一次选择重复扫描 runtime JSON;只有显式开启 FREECAD_RETRY_EVIDENCED=1 才会重新调度已有 pending evidence。 runtime evidence 的记录既可能在顶层 records,也可能在 result.records;索引 v4 统一兼容两种结构,并把 passed_boundary 作为真实终态,避免边界批次重复进入代理。 启动代理前新增只读批次预检,验证 active task、module/tier/family、记录下限、 singletonFallback 和 context packet;不满足时先重规划/修复,避免把 manifest 或上下文 失配转化为一次无效 Codex 回合。

状态面板的效率统计也固定在最近 20 个周期(最多可通过 FREECAD_EFFICIENCY_WINDOW=50 扩大),不再按 scheduler lifetime 扫描数百个旧日志; 最近实测查询耗时约 0.3 秒。

build_web_module_artifacts.py 的 Ninja target 发现现在每个 build tree 只解析一次 build.ninja,并且默认按当前模块筛选 target;当前工作区实测发现 48 个配置目标约 0.65 秒,后续只会进入选定模块的构建。新增 --dry-run 可在不构建的情况下显示 当前批次将使用的 target 集合。 对于含多个辅助 probe 的模块,默认进一步选择主 probe(例如 Part 只选 FreeCADWebPartBoxProbe);其它 probe 通过 --target 或 --all 显式构建,避免 无关 target 的缺失依赖把当前批次拖入失败。

针对 Part 的 OCCT 2D/3D wrapper fixture,planner 将 generic-object 批次上限设为 12 条,避免 24 条同时进入 Python/WASM wrapper 时触发 memory access out of bounds; 这是吞吐与稳定性的边界控制,不改变单条 API 的真实 native 结果。

源码快速检索工具兼容 --path 与额外显式路径的混合写法,避免通配路径被 argparse 误判为非法参数;路径仍限制在工作区内,不会放宽检索范围。

同一代理回合中同一 shell 命令连续失败 3 次后,scheduler 会记录 codex_turn_repeated_failure_stopping 并结束本回合,让下一回合从 checkpoint 继续; 这避免了重复的 patch 校验或同一依赖错误长期占用代理时间,原始失败账本仍完整保留。

准备、上下文、源码读取和 gate 已经是亚秒到数秒级,不再作为主要优化目标。

Feature-first 回合的决策耗时治理

最近 20 个 feature-first 周期的分段计时显示,agent/post-read + agent/editing 约占 70% 以上,而 WASM build 约占 5%;shell 源码读取本身只有亚秒级。瓶颈不是磁盘, 而是每个 fresh 回合读完共享结构后再次推导修改位置。对应治理必须同时实施:

  1. implementation packet 除官方 Gui 源码片段外,直接嵌入 native dispatcher、Facade、 Worker 的有界可编辑片段;指纹未变时不再完整重读 support 文件。
  2. feature 回合只在相同 batchId 内恢复会话,最多 3 轮;首轮 medium,恢复轮 low。 跨批次立即轮换,避免节省冷启动却引入旧假设。
  3. feature preflight 按自身 schema 校验 feature-workflow、Module:* ID 和 batch hash, 不再每轮进入旧 API manifest 的 repair 分支。
  4. 桌面命令 route 从 native 注册自动生成,区分 family-batch 与 direct-command; 跨工作台同名命令绑定 canonical owner。React 不保存逐命令默认参数表。
  5. 对共享一个 source family/dispatcher 的 feature command,默认 16 条集中实现后链接一次; FREECAD_FEATURE_BATCH_SIZE 可在 1-32 调整。运行时问题保持最多 4 条,避免扩大故障面。
  6. implementation packet 可用时,feature 回合默认最多 4 条定点读取;修改落盘后不再用 rg/git diff 做尾部确认,直接交给确定性生成器和 pipeline。普通非 feature 回合仍保留 8 条上限。
  7. 相同模块和 source family 连续批次默认两批合并一次最终链接。第一批只在已有 artifact、 manifest 明确还有同族记录且不是 runtime issue 时写持久 pending 状态;第二批链接成功后 同时晋级两批回执。最后一批、首次构建和运行时修复不能延期。 同一模块若多个 desktop family 都不足批次上限,planner 还会将它们合成一个 desktop-command-mixed 尾批,避免每个 1-7 条的小组分别支付一次最终链接。
  8. 对不断增长的单体 adapter,不要只提供首个命中的编辑片段。工作包应同时包含 handler、 registration、family dispatch 和 ABI entry 多个稳定锚点;同一文件单回合集中修改一次。 feature 回合第一次 patch conflict 即轮换,且该线程不写 session cache。
  9. Qt/Gui command 的 manifest 行号通常落在构造函数。外层准备器应从类名定位 activated(), 把实际 Gui::cmdAppObjectArgs、选择和 Document 语义注入工作包,避免代理继续搜索整个 App/Gui 目录。
  10. ripgrep 的退出 1/空输出表示合法零命中,不是构建或目标别名失败。调度 wrapper 对显式文件 搜索归一为成功并忽略目录参数;启动迁移会自动关闭历史同类假失败。本轮首次归一化关闭了 23 条错误账本记录。
  11. 读取预算使用软/硬两级:工作包完整时目标 4 条;复杂首轮在尚未修改文件时最多 6 条。 已修改后超过软预算才立即收口,避免第 5 条必要读取触发 fresh 重建并重复消耗整个回合。
  12. Codex 常以 bash -lc 执行命令,登录脚本可能覆盖父进程 PATH。scheduler 必须通过 BASH_ENV 再次前置 wrapper;wrapper 忽略目录参数、保留显式文件、限制单行 240 列, 防止 minified JSON 一次命中注入整份文件。
  13. Workbench.cpp 的 MenuItem/ToolBarItem.setCommand() 可能只是带子项的 UI 容器,不能 当作 native 命令。生成器通过后续 *item << ... 或 add...(*item) 自动识别;本轮识别 43 个容器,由 React navigation route 承载,避免对应代理回合和 WASM 链接。
  14. implementation packet 已提供官方语义和编辑锚点后,feature 首轮与恢复轮都使用 low reasoning。复杂首错仍由外层 pipeline 返回结构化首错,不靠每个首轮提高全局推理档位。
  15. 支持整族 desktop ID 的 native 分支应使用可机读前缀判定,例如 if (apiId.startsWith("TechDraw:desktop:"))。生成器据此路由整族命令;transport alias 不进入 capability 分母。本轮因此消除了 6 个重复代理回合,并将 routed 从 428 提升到 547。
  16. 延期链接除了 pending 文件,还必须写 implementation_pending_build 回执,否则 continuation 会保留原 batch 并在下一轮提前构建。第二批成功后将 pending 与当前回执一并晋级 ready。
  17. 失败构建输出保留头部和尾部。CMake 配置文本通常很长,只保留前 4 KiB 会丢失真正的 file:line: error;恢复代理不得在没有编译器诊断时猜头文件或 API。
  18. probe 发现使用规范化、大小写无关的模块前缀,避免 BIM/BimRuntimeProbe.cpp 之类 命名差异产生空工作包。若文件没有 DesktopCommand 命名锚点,至少保留 family handler、 unified ABI 或有界文件尾,不能以 0 字节 editable packet 启动代理。
  19. Python workbench 的 FreeCADGui.addCommand(name, GroupClass()) 若对应类定义 GetCommands(), 与 C++ menu/toolbar container 语义相同。用 AST 关联注册和类定义,生成 React navigation route; 全 UI-container 批次直接写回执且 buildRequired=false。BIM 本轮因此一次关闭 12 条错误 native 任务,从 19 个无修改恢复回合转入下一模块。
  20. fast_source_search 应兼容代理常见的 -n/--paths/--glob 写法和直接执行;scheduler-bin 提供 python 到 python3 的兼容入口。输出必须在子进程返回后按全局 --max-count 截断, 因为 ripgrep 原生限制是逐文件计数。兼容迁移关闭了 36 条历史工具调用假失败。
  21. 读取预算不能靠杀死尚未完成的代理 turn 实施。即使命令输出已写入 scheduler JSONL,强制 结束后恢复仍可能出现 Custom tool call output is missing。正确做法是在当前命令完成后写入 本轮专用 budget marker,让受控 rg/fast_source_search 返回“使用已有结果并编辑”,由同一 turn 自然完成。只有收到真实 turn.completed 才保存 thread ID;补丁冲突、流损坏和跨批次一律不续用。
  22. 同批续用轮不能再次注入完整工作包。首轮保留官方源码和 editable excerpts,续用轮只发送 batch/module/family/record IDs 与“基于上一轮输出立即编辑”的短指令。本轮实测提示由 28,072 B 降至 1,336 B(约减少 95.2%);收益必须继续用 sessionMode=resume、promptBytes、 agent/initial-decision 和 agent/post-read 的实际值确认。
  23. 状态页判断存活不能只看 /proc/<pid> 是否存在,PID 可能已经失效或被复用。必须校验 /proc/<pid>/cmdline 含预期 scheduler/watchdog 脚本;进程不匹配且状态仍是 running 时明确显示 observedState: stale,避免把停止的调度器误当成低效率运行。
  24. apiId.startsWith("<Module>:desktop:") 只能表示覆盖范围,不能证明每个 command 的源码语义 已实现。整族 route 必须带 FC_WEB_AUDITED_DESKTOP_FAMILY("<Module>") 审计标记;未审计的通用 fixture 不进入 routed 统计。这样可防止一个三角网格 fixture 冒充 Difference、Export、FillHole、 Decimating 等不同命令,并避免虚假进度触发无效链接。
  25. implementation packet 要同时解析 <slug>-python.worker.ts 与实际常见的 <slug>.worker.ts。 Worker 已有 family dispatch 时只注入指纹和检查结果,不让代理为了确认文件名再执行 rg --files、 ls 和整文件 sed。首轮有完整 packet 时硬读取边界设为第 5 次;达到边界后协作式熔断, 不能再允许第 6/7 次重复确认产生累计 token 成本。
  26. feature 批次不能 all-or-nothing。用 FC_WEB_IMPLEMENTED_DESKTOP_COMMAND("name") 显式标记 已执行官方源码语义的单个命令;pipeline 在 ready > 0 && missing > 0 时构建实际 runtime target、 写 implementation_partial 回执并只重排 missing IDs。部分批次不延期链接,防止未构建代码先被 规划器跳过。desktop build target 排序为 DesktopCommandProbe > RuntimeProbe > 其它 family probe, 避免修改 RuntimeProbe 却错误链接另一个 GeometryFamilyProbe。
  27. marker 不能单独作为实现证据。生成器必须找到同名的命令专属 else if 分支,并确认该分支 明确返回 exposed;只在通用 fallback、交互摘要或 nonportable/not_implemented 分支中的 marker 一律忽略。planner 的 ready IDs 还必须取“历史实现回执”和“当前 capability 仍 routed”的交集, 这样错误标记被撤销后,旧回执不会永久跳过对应功能。
  28. 批量大小必须按“有效回执”而不是单轮耗时调整。默认 feature 批量保持 16;同一 manifest 超过 180 秒、最新 pipeline 仍为 implementation_incomplete、ready=0 且批量大于 4 时, continuation 不再保留旧批次,planner 将同族记录缩成 4 条。产生部分/完整回执后下一批恢复默认 16,避免永久承担小批次开销。本轮实测把 19 个恢复周期仍为 ready=0/16 的 Mesh 批次重规划为 ready=0/4,官方源码 excerpt 从约 12 KiB 降到 1.8 KiB,首轮 prompt 从约 36.8 KiB 降到 20.9 KiB。状态页必须显示 batchSizing 的 default、selected、reason 和 stallSeconds。
  29. 静态验收失败必须反馈具体原因,不能只返回 missing IDs。capability generator 输出 diagnostics.rejectedDesktopMarkers,pipeline 将当前批次命中的 marker、ID 和拒绝原因写入 staticReadiness.rejectedMarkers,fresh/resume prompt 直接携带它。首分支 if 与后续 else if 都要被识别;本轮修复后 4 条缩批从 ready=0/4 变为 ready=4/4,前三条完成 43.1 秒链接、 0.54 秒同步,第四条进入合并链接等待。状态页的 receipt records 只统计历史回执与当前 routed capability 的交集,同时保留 historical 数字供审计,避免错误旧回执冒充当前进度。
  30. 单体 runtime probe 超过默认 96 KiB 且仍在承载 desktop feature 批次时,implementation packet 输出 desktopAdapterRecommended=true 和 oversizedNativeSources。后续新增命令实现应迁到 <Module>DesktopCommands.cpp/.h,runtime 仅保留共享调用,目标仍链接同一个模块 WASM。 这减少每批重编巨型翻译单元的成本;最终链接成本仍存在,继续由部分回执和两批合并链接控制。
  31. probe 文件名不能直接当模块名。对 FemGenericFamilyProbe、MeshPartRuntimeProbe、 PartDesignRuntimeProbe 等名称,应从 canonical workbench 模块集合做规范化最长前缀匹配。 命令分支验收同时兼容 FC_WEB_DESKTOP_COMMAND("name") 和 name == QStringLiteral("name"),以及普通字符串/QStringLiteral 两种 status 写法;同一命令有 对象类型映射和结果状态等多个分支时必须检查全部命中。本轮因此将 Fem 4 条从 ready=0/4 修正为 ready=4/4,并避免继续消耗代理回合修已有功能。
  32. 拆出 <Module>DesktopCommands.cpp 后,工作包与 route generator 必须真正以该文件为主: feature packet 省略其它大型 C++ probe 的 editable excerpts,但保留指纹;feature registry 在 *DesktopCommands.cpp 中同时扫描旧双参数注册宏和 FC_WEB_IMPLEMENTED_DESKTOP_COMMAND。 命令专属分支兼容 command/name 变量,并遍历同名全部分支。Fem 实测 editable packet 从 9,176 B 降至 3,080 B(约 66%),增量 build 从 48.2 秒降至 31.3 秒(约 35%);4 条 post-filter 从 ready=0/4 变为 4/4,并消除了对 142 KiB GenericFamilyProbe 的重复编辑读取。
  33. 同族批次默认 16,但 desktop-command-mixed 涉及多个异构源码 family 时默认最多 8 条。 异构批次的官方语义和参数结构更分散,CAM 11 个 source family 填满 16 条时首轮初始决策升至 38.7 秒;限制为 8 可降低 prompt 和分派复杂度。连续两个 mixed 小批仍由同 target 的 deferred build 合并,只支付一次最终链接,因此不以增加链接次数换取代理时间。
  34. family Facade/Worker 发现不能依赖单一文件名和类型名。Facade 应匹配 <slug>*python*family*facade.ts,覆盖 cam-python-family-batch-facade.ts;Worker 检查兼容 PythonFamilyRequest 与 PythonFamilyBatchRequest。确认两者已统一调用 fc_web_dispatch_json 后仅保留指纹,不再注入 editable excerpt,避免每个新模块重复搜索已完成运输链。
  35. CAM 实测验证异构缩批:原 16 条 mixed 回合耗时 312 秒仍为 ready=0;重规划为 4 条同族后, Facade/Worker 发现修复使 editable packet 从 6,144 B 降到 4,096 B,最终 build 31.50 秒、 asset sync 2.93 秒并一次写入 4 条 ready 回执。编译首错 Mod/Part/App/Feature.h 不存在时, 应通过源码 include 先例定点解析为 PartFeature.h,4.4 秒内修复后只重试一次,不重新启动代理分析。
  36. CAM 后续 mixed 实测:16 条时 prompt 约 25.2 KiB、初始决策 38.7 秒;8 条上限生效后 prompt 约 14.4 KiB(下降约 43%)、初始决策 7.5 秒(下降约 81%)。同时 family facade/worker 指纹识别使 editable packet 从 6,144 B 降至 4,096 B,只保留 native adapter。该数据说明 heterogeneous batch 应以模型上下文复杂度为主要约束,链接次数由 deferred build 单独合并。
  37. 无外层构建目标的 feature probe 也必须及时刷新静态能力投影。外层续接入口只比较 *Probe.cpp/*DesktopCommands.cpp 的 mtime_ns 与 feature-capability-registry.json;源码 发生变化时才运行一次轻量 generate_feature_capability_registry.py,随后将当前批次中已经 implementationStatus=routed 的记录立即从 manifest 收缩。这样 ReverseEngineering 等没有 可用 WASM target 的模块不会因为缺少 pipeline receipt 而反复读取同一源码;未 routed 的记录 仍保留在下一批,结果不改变、证据不伪造。该刷新路径只读 stat + 受控 probe 扫描,不触发构建、 gate 或全量源码检索。
  38. 源码读取预算采用“软边界 + 无修改硬收口”:达到软预算只写 marker 并提示代理编辑;若 sourceReads >= hardLimit 且本回合 fileChanges == 0,scheduler 在下一事件终止该回合并从 checkpoint 新开低推理回合。这样保留已读结果又避免代理在无 editable 锚点时继续猜路径;一旦 已有文件修改,允许自然收口,不丢失实现补丁。
  39. implementation packet 的 native 发现必须覆盖实际 C++ 命名变体。除标准 *Probe.cpp 外, 受控 probe 目录还可匹配模块前缀的 *ProbeFixed.cpp 与 *InventoryApi.cpp;这类扩展只增加 packet 锚点和 build hint,不改变 target 所有权。若 packet 没有 editable native excerpt,代理 不得扫描仓库猜路径;优先修正 packet 发现规则,再继续实现。
  40. feature desktop packet 在存在 unified dispatcher 时只保留 dispatcher owner 的 C++ editable excerpts;同模块其它 runtime/inventory/persistence probe 仍保留 supportFiles 指纹但不再 注入 file-tail。这样代理上下文只包含实际可编辑入口,避免 post-read 因稳定文件重复解释而 膨胀;Surface 实测 editable excerpts 从约 11.9 KiB 降至 6.1 KiB,未改变 target、路由或构建 所有权。
  41. build hint 不能以“产物已存在”作为唯一可用条件。对于首次构建的目标,应从受控 build.ninja 中确认 target rule,并推导 tree/bin/runtime/wasm 路径;只有当 target 未配置时 才返回空 build。否则新模块(本轮 MeshPart)会因尚无 .js/.wasm 被误判为 outerPipelineAvailable=false,代理重复实现而无法进入首次增量链接。该修复不改变 target 或 链接闭包,只消除首次构建前的准备空转。
  42. deferred build 必须是可收口的事务,不能只依赖“下一批同 target”碰巧触发。planner 返回 no_ready_batch 时,scheduler 先读取 module-build-pending.json,按 target 做 link-closure preflight 和一次增量 build,全部成功后统一 sync 并写回原 batch receipt;失败 target 保留在 队列并设置有限退避。这样避免路由已经 100% 而最后一个 deferred batch 永久悬空,也避免 idle loop 每几秒重复同一个编译首错。该步骤只做目标构建和实现回执,不运行 runtime gate。
  43. 路由、构建、验证是三个独立状态。能力生成器识别 native marker 后只能把 route 标为 routed;目标 .js/.wasm 成功增量链接后才能写 implementation_ready;运行时或 Full Parity 仍保持 deferred,直到明确验证阶段。最终命令尤其要检查 C++ marker 是否为可编译的 no-op macro,并保证目标与其 pthread 依赖闭包使用一致的 -pthread/-sUSE_PTHREADS 参数。
  44. 能力 registry 的 native 所有权发现必须与 packet 保持同一受控文件集合。除 *Probe.cpp/*DesktopCommands.cpp 外,还要扫描模块前缀匹配的 *InventoryApi.cpp;否则 marker 虽已存在、packet 也能定位,静态路由仍会错误显示 ready=0。本轮 Robot 修复后,4 条 命令由 ready=0/4 正确投影为 routed,随后 planner 自动切换到 Surface;该修复不改变运行时 结果,也不触发全量源码扫描。

判断收益时要比较同一批次的首轮与恢复轮:准备阶段应无 batchPreflightRepair,恢复轮 应显示 sessionMode=resume、reasoningEffort=low,并观察 agent/post-read 与 recoverySeconds 是否下降。只有这些实际计时下降才算优化生效,不能用路由数量变化代替。

首次组合实测(Sketcher 约束命令族):16 条功能的代理阶段为 90.0 秒,0 次源码读取、 2 个文件修改;增量 build/sync pipeline 为 37.4 秒,总计约 127.4 秒,即约 8.0 秒/功能。 优化前窗口平均 181.9 秒/回合且每批 8 条,约 22.7 秒/功能;因此当前端到端吞吐约提高 2.8 倍。该数字是历史样本,不作为硬编码阈值,后续仍以 timing JSON 的同族窗口滚动评估。

Mesh 部分提交实测(v9):优化前最近窗口平均 184.1 秒/回合;启用第 5 次读取协作熔断、 真实 turn.completed 会话续用、1,336 B continuation prompt、Worker 指纹复用和 partial receipt 后, 窗口平均降到 103.6 秒(约下降 43.7%),最近命令成功率从 92.19% 升到 100%。实际 FreeCADWebMeshRuntimeProbe 增量链接 16.5 秒、资源同步 0.52 秒;两轮部分回执先后晋级 2 条和 4 条真实命令,planner 自动从下一批移除,未实现命令继续排队。该结果说明主要收益来自减少 代理重复推导,同时部分回执避免为了凑齐整批而伪注册功能。

当前落地与后续改进

已经落地:单次 scheduler 续接、完成批次跳过、bytes+mtime_ns 上下文指纹、回合级失败账本快照、未登记周期日志索引、按需任务卡读取、RAM ccache、-j8 构建、增量验证和命令预算提示。

建议后续项目增加:

  1. 将每个批次的 duration、commands、timeout、gateResult 写入小型性能账本,并让 planner 按上文公式自动调整下一批大小。
  2. 对同一 native probe 的多个 focused gate 使用单一 Node 进程或 worker pool,减少重复 loader 初始化。
  3. 将编译、资源 staging、WASM gate 分别计时;只有编译或 gate 超预算时才拆批,不因准备阶段微小波动改变批次。
  4. 把失败账本分成“当前批次阻断”和“历史待后置”两个集合,状态展示中禁止混用。

跨项目落地清单

  • 定义唯一任务账本和唯一 checkpoint。
  • 为批次、上下文和失败账本实现幂等指纹。
  • 将 planner/rotate/sync 从代理回合移到外层 scheduler。
  • 给每个周期写入 JSONL 事件和可读状态摘要。
  • 为每个命令写入阶段、开始时间和持续秒数,并按阶段累计占比。
  • 设置回合命令预算、批次超时和一次性重试规则。
  • 使用受控源码搜索入口,禁止根目录全量扫描。
  • 实现增量 gate 与最终全量 gate 的明确分界。
  • 只在全部实现完成且收到明确指令后运行全量验证。

模块业务优先阶段(2026-09-07)

逐 API 推进会重复支付源码理解、Worker、链接和上下文成本,也会把内部符号数量误当成用户功能进度。后续统一以可直接使用的功能模块为实施单位,复用模块已有对象、Document、几何、属性、格式、Facade、Worker 和动态 WASM。模块内部直接调用 FreeCAD C++/Python 能力;API 清单只作源码导航,不再安排实现。

调度入口 prepare_implementation_business_batch.py 负责阶段切换:先补齐所有功能 route/build receipts,再按模块处理 37 组实际 import/export 用户工作流,最后合并唯一 React 主产品页面。不得调用 Python API 批次规划器,不得创建 API 专用 Worker/WASM/page。

实施阶段只运行依赖闭包预检、目标编译/链接、artifact sync 和 implementation receipt。历史 evidence 只用于定位,不重新扫描;canonical exposed/passed/native_handler 和已有 receipt 直接进入复用集合。focused gate、逐 API smoke、Chrome、Full Parity、性能与发布验证全部冻结,直到用户明确重新开启统一验证。

编译首错快车道

2026-09-07 的 20 周期窗口显示,优化前耗时主要是 agent/idle-stop=35.8%、agent/post-read=27.5%、agent/initial-decision=24.1%,合计 87.4%;真实增量编译通常仅 5-32 秒。根因是恢复提示遗漏编译首错、复用超过 25 万预测 token 的旧线程,以及 180 秒无事件等待。

当前恢复链固定为:pipeline 提取 file:line: error → 自动附带故障行附近源码和精确 CMake target 路径 → 同一 batch 的短会话复用(会话损坏时自动轮换)→ 30 秒专用无事件熔断 → 外层增量重建。失败 pipeline 在源码和批次未变化时不重复启动;一旦支持文件 mtime 变化才重新编译。MeshPart 实测恢复提示由约 31 KiB 降至 7.7 KiB,初始决策由最高 170 秒降至 3.8 秒,编辑 4.2 秒且无需源码读取;Inspection 同族错误恢复代理阶段约 14.5 秒。

所有 native target 在构建前统一执行链接闭包预检;pthread 闭包必须显式声明 -pthread 和 PTHREAD_POOL_SIZE。若 probe CMakeLists.txt 比 build tree 的 build.ninja 新,先执行一次 rebuild_cache,再读取闭包,防止旧构建图重复报告已修复错误。业务 batch ID、tier 和 record prefix 由 planner/preflight 共享合同,避免每个新格式模块先进入一次 checkpoint repair。

正常模块业务回合使用 90 秒无事件阈值;已有完整 file_change 后默认 20 秒(可用 FREECAD_POST_EDIT_FINALIZE_SECONDS 调整,最低 15 秒)无事件即自动结束代理并进入外层编译,不等待文字总结。代理尝试运行 tsc/test/gate/validate/Chrome 时调度器直接阻止;已有修改仍进入外层编译,避免历史验证错误把上下文带离当前模块。业务读取采用软预算 3、硬上限 8:修改前可读到上限,修改后超过软预算即禁止继续搜索。

BASH_ENV 必须把每个 bash -lc 归一到显式项目根,防止外层 workspace 使存在的源码被误报缺失。业务 packet 只注入承载 fc_web_dispatch_json 的 native owner、模块 IO facade、模块 Worker 和 FormatDispatcher;Python family facade、历史 gate 与非 owner probe 只保留或完全省略。Part 格式 owner 从错误的 PartIoProbe 修正为 Worker 实际加载的 PartBoxProbe,避免构建与运行时所有权分裂。

对代理异常退出或被熔断后的已落盘修改,调度器写 unbuilt-module-batch-edit.json。下一回合若同 batch 的 native/frontend 源码新于目标产物,先构建再决定是否启动代理;成功直接写 receipt,失败进入编译首错快车道。实测 Part 遗留修改先构建发现一处 C++ 签名错误,恢复代理仅用约 6.8 秒且零读取修复,随后真实链接 56.4 秒完成;Mesh 链接约 10.1 秒,Spreadsheet 约 13.8 秒。业务进度同期由 90.14% 提高到 95.85%。

provider 的 reconnect/error 只进入错误统计,不得刷新代理有效活动时钟,否则每条重连消息都会把 90 秒熔断重新计时。模块业务回合若已经完成源码读取但无修改而熔断,在没有活跃命令、patch conflict 或会话损坏时保存 thread;下一轮发送约 0.5-1 KiB 的直接编辑提示并复用已读上下文。Points 实测首轮读取后续接 prompt 为 530 B,恢复轮不再读取源码,完成修改后外层构建 16.2 秒成功。

第二轮调度优化(2026-09-07):编译首错回合只在当前 batch 的支持文件相对上次失败发生 mtime 变化时重跑 pipeline;否则复用已有诊断,直接进入修复会话。编译恢复和 feature 回合均限制为同一 batch 的短会话,避免跨批次上下文污染。实现回合对 npx --prefix <dir> tsc、npm --prefix <dir> run build 以及复合 git diff/status 形式执行前拦截,防止验证命令绕过策略并把既有错误写入失败账本。所有策略都有单元测试覆盖,调度器下次自然重启后生效,不改变当前正在运行的回合。

实现阶段的 tsc/test/gate/validate/Chrome 和代理自行 build 均由调度器硬阻止;若命令前已有文件修改,则把回合转为 post-edit finalize 并继续外层目标构建。每个 bash -lc 通过 FREECAD_SCHEDULER_ROOT 固定项目根,避免相对路径从外层 workspace 执行。格式 packet 的 target 必须与 Worker 实际加载的 unified dispatcher owner 一致,不能按 *IoProbe 文件名猜测。