Feat: benchmark agent token cost with and without QAMap on a fixed task suite - #217
Merged
Conversation
ivory-code
added a commit
that referenced
this pull request
Aug 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The README is careful not to claim that a calling agent spends zero model tokens, and
bench:contextmeasures context reuse, but there was no measured answer to the question adopters ask first: how much does a QA task cost with QAMap in the loop versus without it? This addspnpm bench:agent, a provider-neutral A/B harness over a fixed public task suite, reporting provider-reported tokens, tool calls, wall-clock, and deterministic task success as median plus range, with first-authoring cost in its own column.Behavioral Contract
test/agent-tasks/holds three tasks that reuse committedtest/benchmarks/fixtures — reproduce a duplicate-request regression, verify surface copy against a specification table, and re-verify a persistence fix after a seeded regression. Eachtask.jsonvalidates againsttest/agent-tasks/schema.json; success is judged only by deterministic local checks (file exists, command exit code, stdout includes, JSON path equals). The model's prose is never read.scripts/agent-bench.mjsmaterializes each fixture as a temporary Git repository and runs the same system prompt and task prompt through the same model twice:generic(bash, read_file, list_dir, grep) andqamap(the same tools plusqamap_qa,qamap_qa_run,qamap_e2e_draft_dry_runshelling out to the localdist/cli.js). Only the tool list differs.null. Tool calls are the tool-use blocks the provider returned. Provider, model, system prompt sha256, tool schema sha256, QAMap version, and run count are pinned in the report.QAMAP_BENCH_API_KEYthe command printsstatus: skippedand exits 0, also with--assert, so CI stays green without a key.--dry-runreplaces the provider with a scripted stand-in that returns null token counts; its report is deterministic and is never a measurement. Supported providers: Messages API and Chat Completions API through Node's built-infetch, no new dependency.scripts/lib/fixture-repo.mjs;bench:cioutput is byte-identical before and after the refactor apart from durations.Evidence
Closes #211.
test/agent-benchmark.test.mjs: two dry runs deep-equal; schema name anddry-runstatus; pinned hashes present; forbidden patterns absent ($<digits>, "times cheaper", "x cheaper", temp paths, key prefixes); no key and no--dry-runyieldsskippedwith exit 0 even under--assert.test/agent-benchmark-provider.test.mjs: usage parsers return exact numbers and throw when usage is missing; an injected fetch proves the key reaches only the request header and never the returned objects or error messages.test/agent-benchmark-aggregate.test.mjs: odd and even medians, range, first-authoring split, errored runs excluded.test/agent-tasks.test.mjs: every task validates, referenced fixtures exist, success criteria use local kinds only.nulltoken fields rather than numbers, so nothing reads as measured.Checks
pnpm testpnpm bench:cifor inference, routing, trace, or outputpnpm bench:executionfor E2E compiler or execution fixturespnpm scanfor scanner, security, or repository policypnpm plugin:checkandpnpm plugin:smokefor plugin changesPublic OSS Check
Review Notes
pnpm bench:agent --dry-run --assertruns in CI and inrelease:check;pnpm bench:context,pnpm plugin:check, andpnpm plugin:smokealso pass locally (plugin checks marked N/A, no plugin asset changed). No measured run has been executed yet: themeasuredpath is verified only through the injected fetch, so the first run against a live provider may surface response-shape differences. Success checks are static (files, exit codes, stdout, structured answers) and do not drive a browser;test/agent-tasks/README.mdsays so. Chat Completions does not report cache-write tokens, so that field staysnullrather than zero. Publishing a results table is deliberately left to a run with a pinned model and recorded provider usage.