From e61b071699d7015c3f4cd27983f5a426ba7a5ffb Mon Sep 17 00:00:00 2001 From: Vader Yang Date: Mon, 8 Jun 2026 12:00:12 +0800 Subject: [PATCH] feat(evals): harness qualification bench MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A maintainer tool to benchmark a candidate agent CLI BEFORE wiring it into the live loop — so the roster (codex now; pi agent, kilo cli, … later) gets qualified, not just plugged in. Reuses Part A's agent_run adapter, so a candidate is invoked exactly how the loop would invoke it (harness.kind=custom + a command template). - evals/run.sh — per task: throwaway sandbox → agent_run (the configured harness) → the task's objective check → record JSONL. --harness/--command/--model/--repeat/ --label/--list. Runs on demand (needs the model endpoint + the CLI); NOT in CI. - evals/score.py — aggregate results → per-task pass-rate (over --repeat), per-surface, overall, latency; prints a table + writes a scorecard.json. aggregate() is unit-tested. - evals/tasks/ — 4 starter tasks, objective binary scoring (no judge): triage/do-issue → JSON verdict ∈ {do,try} triage/skip-issue → verdict ∈ {skip,needs_info} implement/failing-test → agent's edit makes a fixture's python test pass review/planted-bug → review flags a hardcoded secret in a diff - evals/checks_lib.py — shared, dependency-free check helpers (JSON extract, verdict + must_contain matching). - evals/tests/test_checks.py — feeds canned outputs to the real checks + score, validating the scoring logic deterministically (no agents). Wired into ci.yml alongside shellcheck of run.sh. Qualifying a new CLI: write a custom template → evals/run.sh --harness custom --command "" → read the scorecard → decide. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 7 +- evals/README.md | 76 +++++++++++++++ evals/checks_lib.py | 63 ++++++++++++ evals/run.sh | 86 ++++++++++++++++ evals/score.py | 71 ++++++++++++++ evals/tasks/implement/failing-test/check.sh | 10 ++ evals/tasks/implement/failing-test/prompt.md | 4 + .../tasks/implement/failing-test/repo/add.py | 3 + .../implement/failing-test/repo/test_add.py | 5 + evals/tasks/implement/failing-test/task.json | 2 + evals/tasks/review/planted-bug/check.py | 14 +++ evals/tasks/review/planted-bug/prompt.md | 18 ++++ evals/tasks/review/planted-bug/task.json | 2 + evals/tasks/triage/do-issue/check.py | 14 +++ evals/tasks/triage/do-issue/prompt.md | 12 +++ evals/tasks/triage/do-issue/repo/greet.py | 3 + evals/tasks/triage/do-issue/task.json | 2 + evals/tasks/triage/skip-issue/check.py | 14 +++ evals/tasks/triage/skip-issue/prompt.md | 12 +++ evals/tasks/triage/skip-issue/task.json | 2 + evals/tests/test_checks.py | 97 +++++++++++++++++++ 21 files changed, 515 insertions(+), 2 deletions(-) create mode 100644 evals/README.md create mode 100644 evals/checks_lib.py create mode 100644 evals/run.sh create mode 100644 evals/score.py create mode 100755 evals/tasks/implement/failing-test/check.sh create mode 100644 evals/tasks/implement/failing-test/prompt.md create mode 100644 evals/tasks/implement/failing-test/repo/add.py create mode 100644 evals/tasks/implement/failing-test/repo/test_add.py create mode 100644 evals/tasks/implement/failing-test/task.json create mode 100755 evals/tasks/review/planted-bug/check.py create mode 100644 evals/tasks/review/planted-bug/prompt.md create mode 100644 evals/tasks/review/planted-bug/task.json create mode 100755 evals/tasks/triage/do-issue/check.py create mode 100644 evals/tasks/triage/do-issue/prompt.md create mode 100644 evals/tasks/triage/do-issue/repo/greet.py create mode 100644 evals/tasks/triage/do-issue/task.json create mode 100755 evals/tasks/triage/skip-issue/check.py create mode 100644 evals/tasks/triage/skip-issue/prompt.md create mode 100644 evals/tasks/triage/skip-issue/task.json create mode 100644 evals/tests/test_checks.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 286a4c1..0540cb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,10 @@ jobs: # Gate at warning+ (errors + warnings — real bugs). info/style nits in # the ported heron-origin linters are below threshold; .shellcheckrc # additionally silences SC1091 (dynamically-pathed sibling sources). - shellcheck --severity=warning scripts/agent-bot/*.sh scripts/lib/*.sh scripts/lint/*.sh + shellcheck --severity=warning scripts/agent-bot/*.sh scripts/lib/*.sh scripts/lint/*.sh evals/*.sh - name: bash -n (syntax) - run: for f in scripts/agent-bot/*.sh scripts/lib/*.sh scripts/lint/*.sh; do bash -n "$f"; done + run: for f in scripts/agent-bot/*.sh scripts/lib/*.sh scripts/lint/*.sh evals/*.sh; do bash -n "$f"; done - name: triage composer tests run: python3 scripts/agent-bot/tests/test_triage.py @@ -37,6 +37,9 @@ jobs: - name: harness adapter tests run: python3 scripts/agent-bot/tests/test_harness.py + - name: eval bench check/score tests + run: python3 evals/tests/test_checks.py + - name: reviewer post-logic tests run: python3 scripts/pr-review/test_post_review.py diff --git a/evals/README.md b/evals/README.md new file mode 100644 index 0000000..b94cbb0 --- /dev/null +++ b/evals/README.md @@ -0,0 +1,76 @@ +# evals/ — harness qualification bench + +Benchmark a candidate agent CLI on standard tasks **before** wiring it into the +live loop. New CLIs (codex now; pi agent, kilo cli, … later) get *qualified*, +not just plugged in. + +It reuses Part A's `agent_run` adapter — so a candidate is invoked **exactly how +the loop would invoke it** (`harness.kind=custom` + your command template). +Scoring is **objective binary** (no judge): build passes / bug caught / valid +JSON verdict. + +## Run it + +```bash +# baseline: the built-in claude harness (needs ANTHROPIC_BASE_URL/API_KEY in env) +evals/run.sh + +# a candidate CLI (codex shown). --repeat N for pass-rate over stochastic runs. +evals/run.sh --harness custom \ + --command 'codex exec --model {model} --full-auto < {prompt_file} > {out}' \ + --model gpt-5 --repeat 3 --label codex + +evals/run.sh --list # list task ids +``` + +Each run writes `runs/