# 35-validation-rigor-and-body-readiness-proposal. 复盘结论 + 改进方案 > **日期**: 2026-08-22(复盘)/ 2026-08-22(提案 A 当天拍板并实施) > **性质**: 复盘 + 改进方案提案。承接 > [31](./31-work-graph-fail-fallback-coverage.md) / > [32](./32-hire-body-fill-reliability.md) / > [33](./33-needs-gate-role-filter-bug.md) / > [34](./34-validator-scored-without-real-deliverable.md) 四份文档的真实回归验证 > 与调研——那几份记的是"发现了什么",这份记的是"复盘下来该怎么办"。 > **触发**:用户问"任务结果这么差,是 agent 能力问题、架构问题、还是 graph > workflow 缺陷",本文档是那次复盘的书面结论 + 据此提出的改进方案。 > **状态**:**四条提案全部已实施**。提案 A(commit `bab675c`);提案 B/C/D > 一起做完(commit `fadb6fa`,全量单测 1012/1012 通过)。细节见各提案小节下方 > 的"实现记录"。 --- ## 一、复盘结论:四类原因,权重不均 用一条真实回归任务(`T-mt2r6fz0-125b`,"做一个 apple.com 风格的 lemonboss 官网") 撞见的问题,按发生顺序分类: ### 1.1 图/模板设计缺陷(已修复)—— 31、33 号文档 - `org-planner-sdd` 没有兜底边,自判失败即卡死到人工重试(31 号)。 - `test→validator` 边声明的 `produces:["log"]` 是一个 test 类节点永远写不出来的 kind,导致 `missing_required_input` 假阳性、永久熔断(33 号)。 这两条是**真实的图/模板设计缺陷**,不是 agent 判断失误,也不是"能力"问题—— 图本身的结构不完整。**权重:中**(真实存在,但已经修好,且这类问题一旦发现, 定位和修法都很直接)。 ### 1.2 招聘装配可靠性 bug(已知,这次是新样本)—— 32 号文档 `unit-tester` 本体的 `agent.md` 全程是空白的 `_base` 骨架(带 `LEMONBOSS_BASE_SKELETON` 哨兵标记),隔壁 `coder` 却正常填充过。32 号文档已经 分析过这类异步装配的可靠性问题并实施了方案 B(`6a18379`),但这次撞见的样本 发生在修复提交**之前** 1 小时,且没有专门验证过新机制是否已经完全堵住这个口子。 **权重:这次具体检查偏弱的最直接原因**——`unit-tester` 在没有任何角色指导的 情况下,只能靠自己对"该怎么测试网页"的通用理解去写检查脚本,写出来的东西天然 比有明确方法论指导的 `org-coder`/`coder-validator` 弱。 ### 1.3 一个新发现的、更深的架构缺口:完整性闸门只在招聘时查,执行时不查 复盘时确认过一件事(`server/services/graph-engine.js` 全文搜索): `missingBodyDefinition()`(检测本体是否还带 `_base` 哨兵/空白)只在 **招聘/建公司/dream 沉淀**这几条路径上被调用(`graph-service.js` / `graph-seed-service.js` / `company-service.js` / `dream-service.js`), **图执行的核心路径(`advanceOne`/`defaultAgentExecutor`)一次都没有调用它**。 也就是说:即使 32 号文档的修复完全生效、招聘那一刻永远不会留下半成品——只要一个 本体因为**任何其它原因**(手工改坏、迁移遗留、装配虽然完成了但内容本身依然是 空骨架而系统判断不出来……)在 `system_agents` 目录里长期处于未填充状态, **图执行侧完全没有第二道防线**,会原样把这份空白提示词交给真实 Claude CLI 去跑, 静默产出一份"看起来是回复、其实没有任何角色指导"的结果——这正是这次 `unit-tester` 发生的事。**这是本次复盘里唯一一条原来没人查过、独立于 32 号文档 既有分析的新架构缺口**。 ### 1.4 不是 agent 能力问题——两次订正过这一点 - 最初以为"没有浏览器工具,物理上做不到视觉检查",后来发现同一批工具权限下 隔壁一条任务真的跑出了 Playwright 测试(见 34 号文档 §5.3 的订正)——**能力 和权限都够**,缺的是指导。 - `unit-tester` 在零指导的情况下,还是主动写出了一份能真实解析 CSS `clamp()` 算出字号比例、能识别锚点跳转反模式的静态断言脚本——这是**相当不错的即兴发挥**, 不是能力不足的表现。 **结论:这次任务结果差,主要是架构 + 一个具体的可靠性 bug,不是模型能力问题。** 按影响力排序:①执行侧没有完整性闸门(新发现,架构缺口)> ②招聘装配可靠性 bug (已知,这次撞上了修复前的窗口)> ③图模板设计缺陷(已修)> ④agent 能力(不是 问题,甚至有正面例子)。 --- ## 二、改进方案(分两档,供讨论) ### 第一档:低成本、高置信度,建议直接做 #### 提案 A:把 `missingBodyDefinition()` 接进图执行的派发前置检查 **做什么**:`advanceOne`(或它调用 `defaultAgentExecutor` 之前的那一步)在真正 派发一次 agent 调用前,对这个节点的 `systemAgentId` 跑一次 `agentLocator.missingBodyDefinition(systemAgentId)`;非空就不派发真实调用, 直接按新 fail_reason(复用已有语义相近的命名,比如 `body_not_ready`,与招聘时 挡重复招聘的判词同名同义)让节点走 fail 流程——**跟 31 号文档修的兜底边机制是 同一条已有的选边逻辑**,不需要发明新的路由方式,只需要在这一个检查点新增一次 判断和一个新的 fail_reason 取值。 **为什么现在做**: - 判据函数已经存在、已经测过、语义已经很清楚(`agent-locator.test.js` 覆盖过), 不是从零设计。 - 改动面很小:一个检查点、一个新 fail_reason、复用现成的选边/兜底/熔断机制。 跟 31 号文档同一类"结构性、可验证、影响面可控"的修法。 - 直接堵住了 §1.3 说的"唯一没有第二道防线"的洞——不管以后是招聘装配失败、 手工改坏、还是任何其它原因导致本体不完整,图执行到这一步都会被拦下来, 而不是静默拿一份空提示词去跑真实 LLM。 - 有真实的失败样本(`unit-tester`)可以直接改写成回归测试,验证成本低。 **代价 / 需要想清楚的点**: - fail_reason 走兜底边意味着这个节点会被打回上游重跑——但重跑到同一个 systemAgentId,body 还是没填,会不会陷入无意义的重试循环?需要确认 `body_not_ready` 这类 fail_reason 是否应该被排除在正常回退计数(backward_count) 之外,还是就该老实算一次退(让 §四.D.2.1 的熔断兜底),倾向于后者——不发明 第二套计数规则。 - 需要决定:检查失败时是"这个节点 blocked 等人工重新触发装配",还是"引擎自动 触发一次装配再重试"(后者更省心但复杂度更高,涉及跟 32 号文档的 `agent-body-filler.js` 打通)。**建议第一版先做前者(简单、可控),装配自愈 留给后续。** **实现记录(2026-08-22,commit `bab675c`)**:按"第一版先做前者"落地—— `runClaimedNode` 派发前查 `agentLocator.missingBodyDefinition(systemAgentId)`, 非空就直接判 `fail`(`ENGINE_FAIL_REASONS.BODY_NOT_READY = 'body_not_ready'`), 走正常的选边/兜底/熔断(不豁免 backward_count,理由同上——重试到同一个空骨架 本体不会自愈,就该老实退熔断等人工,不发明第二套计数规则);不扣预算、不起 边界守卫(没有调用真的发生)。 **中途踩的一个坑,记录一下**:第一版没加"只在真实执行器下生效"这条判据, 直接把检查焊死在派发前——结果全仓库几百个用假 `systemAgentId`(`w-prd`/`impl` 这类夹具名,不对应 `system/agents/` 下任何真实目录)+ 注入假 `executor` 的单测 一次性全部判成 `body_not_ready`,`npx vitest run` 从 1004 掉到 75 个用例失败、 16 个文件。根因是 `missingBodyDefinition` 查的是**真实文件系统**,而这些用例 的本体压根不存在于真实仓库里——按真实文件系统的判据去判一个不存在的虚构本体, "缺失"永远成立,等于把闸门焊死在开着的状态。**改法**:借用已有的 `boundaryGuardApplicable` 判据(`agentExecutor === defaultAgentExecutor` 时才生效,测试注入假 executor 时天然跳过),不是发明一条新判据——两处判的是 同一件事:"这次会不会真的起一个 Claude CLI 子进程",答案会不会真起决定了 两道闸门(边界守卫 / 本体完整性)该不该查。收窄之后单测回到 1004/1004, 新增 `graph-engine-body-not-ready.test.js`(4 例,覆盖闸门生效/预算不扣/ 注入假 executor 时不生效/subgraph 节点不受约束)+ 更新 `graph-engine-fail-reason.test.js` 的取值域断言。 #### 提案 B:控制台展示"这份验收有没有证据支撑"(纯 UI,不改行为) **做什么**:在已有的 `TaskFilesPanel`/节点详情面板上,如果 validator 的 `attempt-*.scores.json`(或对应产出)能关联上一份"真实检查证据"(比如同目录下 有没有一份 `tests/*.mjs`/`*.check.js` 之类的脚本、或 `attempt-*.json` 里有没有 `test_run.executed:true` 这类字段),标一个不起眼的小标签——不是要engine 判断 "验证够不够严谨"(那是提案 D 的范畴,成本高很多),只是**把现在完全不可见的 差异露出来**,让人在看"已完成"三个字时,能多看一眼"这条完成背后有没有真实证据"。 **为什么可以现在做**:纯前端 + 读现成数据,不改变任何执行行为,风险几乎为零, 且直接回应 34 号文档 §5.3 那条"三个任务验证严谨度天差地别、界面上完全看不出来" 的具体抱怨。 **实现记录(2026-08-22,commit `fadb6fa`)**:没有另做一套"有没有测试脚本"的 启发式判断——直接读提案 D 已经算好的结论(`goal-contract.js` 判决里的 `dimensions[].evidence_required`/`.ok`),比自己猜一遍"这份产出看起来严不严谨" 更准。`GET /api/graph-runs/:id` 新增 `benchmarkVerdicts`(只读 `listVerdicts`/`readVerdict` 落盘的判决,不重新计算,读失败就跳过那一条,不拖垮 整个响应);`NodeDetailPanel.vue` 概览 tab 新增"验收证据"小节,逐维度显示分数 + 是否有证据支撑,缺证据的标 ⚠️。只在这个节点真的有 benchmark 判决时才出现(同「输入 输出」tab 的"不猜、不留空标题"口径)。已用 Chrome 实测过:点开 `org-coder-validator` 节点,六个维度分数与 ⚠️ 标记正确渲染。 --- ### 第二档:更大的设计决策,建议先讨论优先级再动手 #### 提案 C:给"视觉/UX 类"任务提供一套标准化、可复用的验收工具 **问题**:`p-bkhylu` 那条任务能做到 Playwright 级验证,纯粹是因为那次的 agent 会话自己想到了、自己写了 `verify.mjs`。换一次任务、换一次会话,不保证还会有人 这么做——`unit-tester` 这次就没想到(或者说,没被指导要这么想)。 **候选做法**:在仓库里放一份可复用的验收脚本模板/skill(比如 `scripts/visual-acceptance-check.mjs`,封装"读 index.html/css/js → 用 Playwright 量视口/对比度/字号"这类通用检查逻辑),并在 `coder`/`unit-tester`/ `coder-validator` 这类角色的 `agent.md`(或它们的 `tools.md`)里显式点名:"遇到 `goal-benchmark.json` 里要求视口/对比度/首屏这类视觉维度时,用这份工具,不要 自己发明"。 **代价**:这不是"填一个空文件"那么简单——要设计这份工具的接口(读哪些文件、 输出什么形态、怎么接进 `attempt-*.json`)、要决定哪些角色的 `agent.md` 需要 更新、要不要影响现有已经在跑的任务。**这是一个需要先讨论范围的设计任务, 不建议直接动手**。 **实现记录(2026-08-22,commit `fadb6fa`)**:接口定成了四个独立子命令 (`overflow`/`style`/`console`/`contrast`),各自吐一行 JSON 到 stdout,不做成 "一次跑全部维度"的大而全脚本——`goal-benchmark.json` 的维度因任务而异,硬编码 成一份不可复用。只更新了 `benchmark-validator/agent.md`(真正逐维度打分、写 `evidence` 的那个角色),没有动 `coder`/`unit-tester`(当初以为的目标,后来发现 写分数和证据的其实是另一个角色,见 §五.2 的角色梳理)。没有影响任何现有已经在跑 的任务——纯新增文件 + 一段引用说明,agent.md 里其余内容一字未改。配一份不进 `vitest` 快速循环的独立冒烟脚本(`visual-acceptance-check.smoke.mjs`,起真实 Chromium 断言四个子命令,几秒量级)。 #### 提案 D:给"要求真实检查"的验收维度加引擎级证据闸门 **问题**:`goal-benchmark.json` 的 `evidence_hint` 目前只是**写给 agent 看的 文本提示**,agent 信不信、照不照做,引擎完全不管——验证严不严谨完全靠自觉。 **候选做法**:给 `evidence_hint` 里出现"打开浏览器/截图/开发者工具"这类关键词 的维度,要求打分时必须附带一个可核实的 `evidence_ref`(比如指向一份真实脚本的 `stdout` 日志文件,或类似 `coder-validator` 已经在用的 `test_run.executed:true` + `output_excerpt` 那套惯例)——引擎收到一个没有 `evidence_ref` 的高分视觉维度时, 可以选择"降级为 inconclusive"或至少"打一个显式的 ⚠️ 标记",而不是原样采信。 **代价**:这个提案影响 `goal-benchmark.json` 的 schema、`goal-contract.js` 的 判定逻辑、以及所有会产出这类分数的 validator 角色的 `agent.md`——影响面明显 比提案 A/B 大,且需要先定"哪些关键词触发这道闸门""没有证据时降级还是拦截"这类 细节,**建议留到提案 A/B 落地、有更多真实样本之后再决定要不要做,不是这次的 优先级**。 **实现记录(2026-08-22,commit `fadb6fa`)**:关键词用了固定正则 (`浏览器|viewport|视口|开发者工具|devtools|console|network|截图|screenshot`, 不分大小写);没有证据时选的是**拦截**(该维度直接判不过),不是"降级为 inconclusive 但仍算过"——因为整个 §一复盘的教训就是"光有分数不该被当真", 选降级等于还是采信了那个分数,只是换了个说法。 **中途发现的一处设计对齐**:`goal-benchmark.json` 的 schema 完全不用改——`evidence` 不是新字段,`benchmark-validator/agent.md` 早就在教 agent 把 `dimensions[].evidence` 写进判词文件(`attempt-.json`),只是这份数据从来没被 `goal-contract.js run` 读过(它只读 `--target` 指向的 `attempt-.scores.json`,只有分数)。所以没有 发明新格式,而是让 `runBenchmark` 多看一眼同目录的姊妹判词文件—— `readSiblingEvidence()`:`attempt-.scores.json` → 同目录 `attempt-.json` → 取 `dimensions[].evidence`。`--target` 文件里如果也显式给了 `evidence.`, 优先用那份。6 条新单测覆盖:命中关键词但无证据判不过 / 给了证据后正常通过 / 普通维度不受约束 / 空字符串按没给处理 / 姊妹文件里的证据能读到 / 姊妹文件缺失 不拖垮打分。 --- ## 三、推进顺序(原提案,已按此顺序全部做完) 1. **提案 A**(执行侧完整性闸门)——最推荐现在就做,闭环 §1.3 那个新发现的架构 缺口,改动小、可测、影响明确。✅ 已实施(commit `bab675c`) 2. **提案 B**(控制台证据可见性)——可以跟 A 一起做,纯 UI,风险低。✅ 已实施 3. **提案 C / D**——原计划"先攒几个更多真实任务的样本再决定要不要做",用户直接 要求一起做完,所以没有等到攒够样本——两条都已实施(commit `fadb6fa`), 实现记录见各自小节。样本量不足这条风险仍然存在:目前只有一次真实回归验证过 这套证据闸门,`evidence_hint` 关键词表是不是覆盖了所有该拦的场景、拦下来的 维度会不会引发新一轮无意义回退,都还没有大量真实任务的数据支撑,留意后续 真实使用中的反馈。