35-validation-rigor-and-body-readiness-proposal. 复盘结论 + 改进方案
日期: 2026-08-22(复盘)/ 2026-08-22(提案 A 当天拍板并实施) 性质: 复盘 + 改进方案提案。承接 31 / 32 / 33 / 34 四份文档的真实回归验证 与调研——那几份记的是"发现了什么",这份记的是"复盘下来该怎么办"。 触发:用户问"任务结果这么差,是 agent 能力问题、架构问题、还是 graph workflow 缺陷",本文档是那次复盘的书面结论 + 据此提出的改进方案。 状态:四条提案全部已实施。提案 A(commit
bab675c);提案 B/C/D 一起做完(commitfadb6fa,全量单测 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在零指导的情况下,还是主动写出了一份能真实解析 CSSclamp()算出字号比例、能识别锚点跳转反模式的静态断言脚本——这是相当不错的即兴发挥, 不是能力不足的表现。
结论:这次任务结果差,主要是架构 + 一个具体的可靠性 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-<n>.json),只是这份数据从来没被 goal-contract.js run
读过(它只读 --target 指向的 attempt-<n>.scores.json,只有分数)。所以没有
发明新格式,而是让 runBenchmark 多看一眼同目录的姊妹判词文件——
readSiblingEvidence():attempt-<n>.scores.json → 同目录 attempt-<n>.json →
取 dimensions[].evidence。--target 文件里如果也显式给了 evidence.<dim>,
优先用那份。6 条新单测覆盖:命中关键词但无证据判不过 / 给了证据后正常通过 /
普通维度不受约束 / 空字符串按没给处理 / 姊妹文件里的证据能读到 / 姊妹文件缺失
不拖垮打分。
三、推进顺序(原提案,已按此顺序全部做完)
- 提案 A(执行侧完整性闸门)——最推荐现在就做,闭环 §1.3 那个新发现的架构
缺口,改动小、可测、影响明确。✅ 已实施(commit
bab675c) - 提案 B(控制台证据可见性)——可以跟 A 一起做,纯 UI,风险低。✅ 已实施
- 提案 C / D——原计划"先攒几个更多真实任务的样本再决定要不要做",用户直接
要求一起做完,所以没有等到攒够样本——两条都已实施(commit
fadb6fa), 实现记录见各自小节。样本量不足这条风险仍然存在:目前只有一次真实回归验证过 这套证据闸门,evidence_hint关键词表是不是覆盖了所有该拦的场景、拦下来的 维度会不会引发新一轮无意义回退,都还没有大量真实任务的数据支撑,留意后续 真实使用中的反馈。