645 lines
56 KiB
Markdown
645 lines
56 KiB
Markdown
# 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` 文件名猜测。
|