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

645 lines
56 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 仍真实执行。测试不运行全量功能矩阵,
但任何调度优化加载前必须先通过这组行为不变性门禁。
观察命令:
```sh
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 次周期的耗时和命令数:
```text
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,让下一回合继续。
阶段计时命令:
```sh
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` 参数。
40. 能力 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` 文件名猜测。