两审独立坐实根因链、收敛「修计划后执行」。纳入发现校正三处: ① fix② 前提错(实测 _template rng 本就合规 ctx?next():0、无 nowMs,违规是模型 自造)→ 从「改模板」重定向到 check 报错点破规则+提示层克隆纪律+check-early(更上游); ② 阻塞判断纠正:实测真过门率 9/23≈39%(初版把「跑到门 83%」当过门),死圈只是多种 失败模式之一,三修到不了 80%,80% 缺口(E/G/H 玩法正确性)属另一工作面; ③ fix① 边界钉死:edit 走 read→apply→cheap_run.write_file 继承 L1/L3+whitelist、 不触发 check、加软停 blocked-tools;纠偏仅 schema 缺字段/不重置 stuck 计数/cheap-only 别改坏共享 tier2 熔断;edit 自身错配纳入纠偏。fix③ 落 evidence 脱敏(源工程可交易)。 验收改 ok=True 收敛(非「跑到门」)+ 预置违规 fixture。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.6 KiB
title, date, status, topic, 上级, 关联
| title | date | status | topic | 上级 | 关联 | ||
|---|---|---|---|---|---|---|---|
| fix: 便宜档生成死圈修复 — 增量修改路径 + 规则纪律 + brief 落盘 | 2026-07-08 | 已过 Codex+Opus 双评审(2026-07-08,两审独立收敛「修计划后执行」,发现已纳入本版)· 待执行 · 归便宜档生成质量线 | 便宜档生成死圈修复 | docs/plans/2026-06-30-便宜档M3-生产cutover-plan.md |
|
便宜档生成死圈修复
为什么做这件事
2026-07-08 对一次真实失败运行做了第一性根因分析(全程与证据见 cheap-model-game-generation.md §11),结论经 Codex + Opus 双评审逐环坐实:一次便宜档生成(brief=躲避掉落街机)最终 ok=False,死于一条工具面死圈——agent 写出的 game-logic.js 带一个红线违规(rng()/nowMs() 回退裸用 Math.random()/Date.now()),check 判 FAIL,agent 正确地想去修,但便宜档六工具面只有整体覆盖的 write_file、没有增量 edit,于是"改三行"变成"重写 22KB",MiniMax 吐这么长的 tool-call 参数时反复漏掉 path 字段(先还幻觉调了个不存在的 edit),连撞四次触发 stuck 熔断强杀回合,九门收口从未运行,ok=False。
双评审同时纠了本计划初版两处错,已在本版改掉:其一,违规不来自模板——当前 _template/src/game-logic.js:66-68 的 rng() 回退本就是 ctx ? ctx.random.next() : 0(合规),模板根本没有 nowMs() 函数;违规是模型无视合规模板、按训练直觉自造防御性裸回退,还用错误注释("check 不拦函数内部")合理化。所以真正上游根因是规则理解缺口 + 不照模板克隆的纪律缺口,比"缺增量 edit"更靠上游——agent 一开始就不写违规,整条死圈不会触发。其二,死圈只是多种失败模式之一,不是全部(见下阻塞判断)。
是否阻塞当前计划(纠正初版)
不阻塞已交付的内测闭环 A 段(登录/额度/dify/facade,与生成质量正交,已合并 dev/2.0.0 并逐环真机验证)。
生成质量对内测是真实短板,但不是闭环管路的硬阻塞。 初版说"死圈是少数失败模式、九发对照只有本局没跑到九门"——这条证据被双评审驳回:"跑到收口门"≠"过门"。实测 23 局便宜档生成,跑到九门 19 局(83%),但 verdict.pass=True 只有 9 局(≈39%),其余多为与死圈无关的玩法坏死(_wg1-gen/{80020,80021,80027}/verdict-feedback.json 挂 E_live/G_input/H_progress,输入不驱动状态)。所以真实过门率 ~39%,远低于 MVP ≥80% 门。据此纠正:
- 死圈(本计划治的)是一种终止模式(熔断在门前),约占失败的一小部分;fix① 消除它,但修完到不了 80%——80% 缺口的大头是玩法正确性(E/G/H 门),属便宜档质量线另一条工作面,不在本计划。
- 放行内测靠的是"内测容忍 + 数据飞轮迭代生成质量 + 已真机产出过可发布局",不靠"生成质量已达标";本计划不夸大三修把成功率抬向 80%,只声明它消除死圈这一结构性拖累。
- 建议:内测放量前完成 fix①(免得种子用户高频撞死圈),但内测启动不以本计划完成为门。
修复(按杠杆排序,含双评审钉死的执行级边界)
fix① 死圈治理:增量修改工具 + 窄口径畸形参数纠偏(最高杠杆,治死圈这一种终止模式)
fix①-A 增量 edit 工具(cheap-worker/cheap_toolkit.py + cheap_run.py)。给便宜档加一个 edit_file(path, old, new)(或 apply-patch)工具,让"改 rng 三行"表达为短参数替换,不再触发 22KB 整体重写的 MiniMax 漏参高发区。执行级边界(双评审 I3 钉死):
- 实现走 read 现文件 → 内存里精确替换 old→new → 经
cheap_run.write_file回写同一条路,天然继承write_file的 L1 plumbing 写保护、L3 边界、write_whitelist(M4"只改玩法"边界不被打穿)。不新开任何绕过校验/写保护的旁路。 - edit 不触发 check(核实:
write_file本就不触发 check,唯一强制点是finish门复跑 check+build——cheap_toolkit.py:96-116;"edit 要对齐 check 触发"是不存在的对齐点)。 - 加入软停 blocked-tools 集(
middleware.py:178的DEFAULT_SOFT_STOP_BLOCKED_TOOLS),否则越软停线后 edit 成为新的绕过面。 - edit 有 write_file 没有的自身失败面:old 串不精确匹配→edit 失败,MiniMax 也可能吐坏 diff、成新 stuck 源。这条纳入 fix①-B 的纠偏覆盖(匹配失败给结构化提示"未找到待替换串,请附完整旧串或改用 write_file")。
fix①-B 窄口径畸形参数纠偏(tier2/gen-worker/worker/middleware.py 的 CircuitBreakerMiddleware,cheap 与 tier2 共享同一类——改动必须不误伤 tier2)。执行级边界(双评审 I1/I2 钉死):
- 仅匹配 schema/参数校验类失败——错误头含
Input validation failed/is a required property(漏必填字段)才注入一次结构化纠偏("你漏了path参数,请带完整参数重发")。绝不对所有同签名失败放行:反复 build 同一编译错、反复 check 同一红线、L1 拒写,这些是该熔断的真死圈,纠偏不得放行。 - 纠偏不重置
_fail_repeat计数:只在_fail_repeat ∈ [1, stuck_threshold)时注入提示,达stuck_threshold照常_trip(stuck)。即"纠偏至多 threshold-1 次,仍同签名失败就熔断"——上界由现有 stuck 阈值(默认 4)天然封死,零新增无界重试面(否则 1→纠偏→1→纠偏…无界烧到 ¥ 硬地板 ¥15 才停,把最省的死圈变最贵)。 - cheap-only 开关或子类:便宜档启用纠偏、tier2 默认关(tier2 走 Anthropic 原生、基本不犯漏 path,纠偏对它是死代码但共享类风险共担);别裸改共享类默认行为。改完 tier2 的 stuck 用例必须回归(
tier2/gen-worker/tests/test_circuit_stuck_signature.py)。
fix② 上游纪律:check 报错点破规则 + 提示层克隆纪律 + check-early(比造工具更上游更省)
初版"改模板 rng 默认合规"对 rng 是空操作(模板已合规)、对 nowMs 是无中生有——删掉。真正杠杆在让 agent 一开始就不写违规、写了立刻自己发现:
- check 红线报错点破规则(
game-runtime/tools/amodel-gen/tools.mjs):现红线只输出正则 source、无行号,且stripCode()把块注释整体替换成单空格会致行号漂移(tools.mjs:136)。改为逐行 strip 后逐行匹配保留行号,报红线 game-logic.js 第 N 行: 禁用 Math.random(),文案加一句"函数内回退也算裸调、正则按去注释后纯文本命中"——直接消解 agent"封装进函数就不拦"的错误心智模型(这句比行号更治本)。 - 提示层克隆纪律 + check-early(
cheap-worker/cheap_roles.py):加一条"rng/nowMs 照_template逐字克隆,禁自造Math.random()/Date.now()防御回退";再加一条 check-early 纪律"每写完一个game-logic.js就立即check,红线修到 PASS 再扩玩法"——文件小时发现违规→小修→写参数短,天然避开 MiniMax 长 write 漏参高发区,与 fix① 叠加、且更省(fix① 只救终局那一下,救不回前面的长空转成本)。 - 模板补合规 nowMs 示范(
game-runtime/games/_template/src/game-logic.js,minor):当前模板缺时间源范例(用 dt 累加/timerScheduler),若确需 nowMs 就补一个走ctx.time.nowMs()、裸回退给确定性值的示范,补上范例缺口——不是"改默认写法"。
fix③ brief 落盘补可追溯(落 evidence、脱敏限长)
生成核心输入 brief 原文在 worker/Service 任何日志与产物里都没落盘,失败复盘无法审计"是否完整拿到 brief"。补落盘,但双评审 M1 钉死落点:落 game_dir/evidence/brief.json,不落 game_dir 根——因资产面决策源工程可进素材市场交易,brief 是用户私有输入,落工程根会随发行外泄;evidence 是取证目录、不随发行源打包。字段 brief / bytes / sha256 / source=job.brief / truncated,文本按前端 PROMPT_MAX=1000 限长、必要时只留 hash + 截断预览,避免无界膨胀。
验证(双评审校正:收敛判据落在 ok=True,不用"跑到门")
按便宜档生成线口径,n=5 收敛环做修复回归、不做 n≥30 统计批;拆两层:
- fix① 回归(可复现构造):预置一个带
Math.random()违规的game-logic.jsfixture 让 agent 只修红线,验"经 edit/纠偏修掉红线 → 跑到九门 → ok=True 过门"而非熔断;收敛判据 = 5/5 不因缺 path stuck、且过门(ok=True,不是"跑到门")。tier2 stuck 用例回归绿。 - fix② 回归:新生成产物默认不带
Math.random/Date.now裸调、check 首过率上升、check 报错带行号且不误报(函数内/注释/字符串不误报、多行块注释后行号不漂,配单测)。 - fix③ 回归:每次运行
game_dir/evidence/brief.json落有原文,失败复盘可读到输入。 - 全跑真环境(cheap Service :8300 真 agent loop),不用桩。统计达标率(是否够 MVP ≥80%)另开分层样本,不在本计划——本计划只证死圈这一终止模式被消除。
归属与排期
归便宜档生产 cutover 线(本计划上级 2026-06-30-便宜档M3-生产cutover-plan.md)。已过双评审,进入执行。fix① 优先(治死圈)、fix② 随后(降触发面,更上游)、fix③ 并入。不在内测启动关键路径上,但建议内测放量前完成 fix①。80% 达标缺口(玩法正确性 E/G/H)属质量线另一工作面,本计划不覆盖。