Skip to content

feat(eval): frozen-task ablation benchmark — and three guard defects it uncovered#389

Open
ceilf6 wants to merge 11 commits into
developfrom
feat/architecture-ablation-eval
Open

feat(eval): frozen-task ablation benchmark — and three guard defects it uncovered#389
ceilf6 wants to merge 11 commits into
developfrom
feat/architecture-ablation-eval

Conversation

@ceilf6

@ceilf6 ceilf6 commented Jul 12, 2026

Copy link
Copy Markdown
Owner

What

A reproducible ablation benchmark for the agent's own architecture claims — and the defects it surfaced.

Harness (benchmarks/eval/)

  • Frozen 30-task set (10 query / 10 create / 5 bugfix / 5 refactor) run against an isolated fixture project with planted bug targets.
  • Machine-checkable acceptance only: tsc --noEmit, vitest, file existence/content assertions. No human grading.
  • Two arms, per-task workspace isolation, resume-on-interrupt, LLM usage accounting.
  • LLM supplied through a Claude Code CLI backend (tools disabled, empty cwd) — no API key required.
  • runtime-node: hallucinationGuard config is now passed through into AgentConfig (it was silently dropped), so the guard is at least addressable from the runtime API.

Results (benchmarks/results/2026-07-12-sdd-ablation.md — 27 runs per arm)

  • SDD on vs off: −3.7pp first-pass rate. No measurable benefit at this sample size.
  • Guard: zero interceptions across both arms, while syntactically invalid files (markdown fences written into .tsx, TS1127) still landed on disk.

Defects found (filed as issues)

Merging with the negative results intact is deliberate. Fixing #386#388 and re-running the same frozen set is the follow-up.

GitNexus Impact Summary

  • Risk level: LOW
  • Critical skeleton changes: packages/runtime-node/src/run.ts (mcp-boundary) adds one optional field hallucinationGuard, forwarded into AgentConfig at both assembly sites; README.md (authority-docs) documents the benchmark.
  • GitNexus impact: detect_changes reports no uncommitted diff; impact on runFrontAgentTask reports 65 downstream nodes (direct 30, processes 6, modules 8). The change is additive with an undefined default, so no existing caller behavior changes — covered by the new test "leaves hallucinationGuard undefined when the caller omits it".
  • Verification: pnpm build; vitest packages/runtime-node/src/run.test.ts (12 pass, 2 new); npm run test:workflows (33 pass, 1 new contract test); biome check clean; contract:local passed.

Sample completeness

3 refactor tasks (refactor-extract-listitem, refactor-app-title, refactor-button-memo) were not executed in either arm (quota), and are symmetrically excluded. All conclusions rest on the 27 tasks common to both arms.

🤖 Generated with Claude Code

ceilf6 added 11 commits July 11, 2026 19:07
…, fail fast on dead backend

Smoke run exposed two silent-degradation paths: expired claude CLI auth
produced zero-call 'completed' tasks, and balanced-mode overwrite approvals
were denied without an approval handler.
- disable external RAG for both arms (default repo pulls unrelated wiki
  content into query context; noise + token cost)
- query tasks get --files hints mirroring real CLI usage (deterministic
  query planning read a nonexistent README instead of target sources);
  two navigation tasks intentionally keep no hints
- fixture gains a minimal README (structure only, no interface docs)

Task set amended before the measured run; both arms use the identical set.
- disallow all tools + override system prompt: directory-listing style
  prompts triggered tool_use and error_max_turns under --max-turns 1
- run CLI from an empty cwd: Claude Code was injecting the host repo's
  CLAUDE.md and project context into every eval call (contamination + tokens)
- include stdout head and prompt size in failure diagnostics
Frozen 30-task benchmark (27 runs per arm; 3 refactor tasks unrun in both
arms due to quota, symmetrically excluded).

Findings:
- SDD on/off shows no measurable difference in first-pass rate (-3.7pp).
- The hallucination guard recorded zero interceptions across both arms,
  yet syntactically invalid files (markdown fences in .tsx, TS1127) landed
  on disk. Three root causes located and documented in the report.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ ceilf6/repo-guard

代码评审报告: feat(eval): frozen-task ablation benchmark

风险等级:
处理建议: 请求修改
决策摘要: 运行时改动本身是低风险的加法式变更,但 benchmark 夹具里植入的 bug fixture (clamp.ts) 与其自带测试逻辑冲突——测试断言上界返回 max,而 fixture 有意返回 min;这会让 fixture 的基线测试永远红,需在合并前澄清意图。

级联分析

  • 变更符号: runFrontAgentTask(新增可选 hallucinationGuard 透传)、benchmark harness(新增,独立于运行时)
  • 受影响流程: run.ts 两处 AgentConfig 组装点;benchmark orchestration 为独立入口,不进入生产路径
  • 变更集外调用方: runFrontAgentTask 报告 65 下游节点,但改动为 undefined 默认的加法字段,既有调用方行为不变;新测试覆盖 "leaves hallucinationGuard undefined"
  • 置信度: medium(run.ts 具体 diff 被截断,未能逐行确认两处组装点;结论依赖 PR 描述与 meta.json)

问题发现

  1. [中] fixture clamp.ts 的植入 bug 与其自带测试自相矛盾

    • 证据: clamp.ts:3value > maxreturn min(植入的 bug),而 clamp.test.ts:9 断言 clamp(11, 0, 10) toBe 10。fixture 测试对未修复的 clamp 直接失败。
    • 受影响调用方/流程: bugfix-clamp 任务的基线;若 harness 在 agent 修复前跑该测试作为基线校验,会误判环境状态。
    • 最小可行修复: 明确这是"待修复 bug 的失败测试"(预期红),并在文档或 fixture README 中标注哪些 test 是 planted-failing baseline,避免与真正的环境损坏混淆。
  2. [低] hallucinationGuard 透传只让 guard "addressable",但据关联 issue #386 executor 路径仍不消费 enabledChecks

    • 证据: PR 描述与 #386 均指出 executor 走 validateFilePath/validateCode,不读 enabledChecks;本 PR 只把 config 转发进 AgentConfig
    • 受影响调用方/流程: ablation arm 的 guard 关闭实际无效(#386 已承认 voided guard arm)。
    • 最小可行修复: 无需在本 PR 修(负结果被有意保留),但建议在 run.ts 透传处或 README 加注释,说明该字段当前仅到达 config 层、尚未在 executor 生效,避免后续调用方误以为 guard 可通过此开关禁用。

行级发现

  • [benchmarks/eval/fixture/src/utils/clamp.ts:3] value > max 时返回 min 是植入 bug;请确认这是 bugfix-clamp 的故意缺陷,并标注对应 clamp.test.ts 为预期失败基线,否则 harness 基线校验会把它当环境故障。
  • [benchmarks/eval/run-eval.mjs:110] pass = !runError && checkResults.length > 0 && checkResults.every(c => c.ok):空 checks 数组会判 FAIL,但 query-structure 等任务只有 result_contains checks,逻辑正确;仅提示当任务漏配 checks 时会静默计入 FAIL 而非报错,建议对 checks.length === 0 显式抛错以区分"漏配"与"真失败"。
  • [benchmarks/eval/run-eval.mjs:31] sddPath: 'sdd.disabled.yaml' 依赖"指向不存在文件→existsSync 判无 SDD"的隐式约定;若某天 fixture 里恰好出现同名文件,ablation arm 会静默启用 SDD。建议改用显式 sdd: false/disableSdd 开关而非依赖文件缺失。

Karpathy 评审

  • 假设: harness 依赖多处隐式约定——ablation 通过"不存在的 sddPath"关闭 SDD、llmCalls === 0 即判后端损坏、fixture 植入 bug 的测试预期为红。这些假设未在代码内显式标注,后续维护者易踩坑。
  • 简洁性: harness 结构清晰、单一入口,无过度抽象;checks.mjs 的 switch 分发恰当。
  • 结构质量: 新增均为独立 benchmark 目录,未污染生产路径;运行时改动是最小加法式透传,符合外科手术式变更。
  • 变更范围: 38 文件中绝大多数是 fixture / lockfile / 结果数据,属 benchmark 自洽资产;meta.json 是索引产物,噪音大但无害。运行时真实改动面很小。
  • 验证: run.ts 加了 2 个新测试,workflow 加 1 个契约测试,声称全绿;但 run.ts diff 被截断,无法逐行确认两处 AgentConfig 组装点都正确透传。

缺失覆盖

  • run.tshallucinationGuard 透传:需确认两个组装点都有测试,而不仅是 "undefined 默认" 一例;应补一个"caller 传入 guard config → AgentConfig 两处都收到"的断言(diff 截断,无法核实是否已覆盖)。
  • fixture planted-bug 测试的基线状态:建议补一个 harness 层的自检,区分"植入失败测试"与"环境损坏",否则 bugfix-clamp 等任务的初始红态难以与真实故障区分。
  • ablation arm SDD 关闭的正向验证:应有测试断言 sdd.disabled.yaml 路径确实导致 SDD 未加载,防止隐式约定被未来改动破坏。

@@ -0,0 +1,5 @@
export function clamp(value: number, min: number, max: number): number {
if (value < min) return min;
if (value > max) return min;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value > max 时返回 min 是植入 bug;请确认这是 bugfix-clamp 的故意缺陷,并标注对应 clamp.test.ts 为预期失败基线,否则 harness 基线校验会把它当环境故障。

const usageAfter = getUsageTally();
const checkResults = runError ? [] : runChecks(task.checks, { workspace: ws, resultText });
const pass = !runError && checkResults.length > 0 && checkResults.every((c) => c.ok);
const llmCalls = usageAfter.calls - usageBefore.calls;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass = !runError && checkResults.length > 0 && checkResults.every(c => c.ok):空 checks 数组会判 FAIL,但 query-structure 等任务只有 result_contains checks,逻辑正确;仅提示当任务漏配 checks 时会静默计入 FAIL 而非报错,建议对 checks.length === 0 显式抛错以区分"漏配"与"真失败"。

ablation: {
// 指向不存在的文件 → run.ts existsSync 判定为无 SDD
sddPath: 'sdd.disabled.yaml',
hallucinationGuard: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sddPath: 'sdd.disabled.yaml' 依赖"指向不存在文件→existsSync 判无 SDD"的隐式约定;若某天 fixture 里恰好出现同名文件,ablation arm 会静默启用 SDD。建议改用显式 sdd: false/disableSdd 开关而非依赖文件缺失。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant