Skip to main content
benchspec

Benchmark what your agent does, not what it says.

CI PyPI Python License: MIT

benchspec runs an agent (Claude Code, Codex, or OpenCode) against a task in a fresh microVM, checks what it actually did in the workspace, and reports the result as a comparison: with your skill versus without, one model versus another, one harness versus another.

Animated terminal output: benchspec run prints a benchmark matrix with evals as rows, arms as columns, color-coded rates, and percentage-point deltas

End-of-run summary for a two-arm run of the in-repo hello suite (illustrative numbers). Rows are evals, columns are arms (baseline ran the agent bare, trial installed the skill), and every non-baseline cell shows its assertion pass rate plus the delta against the baseline in percentage points. The same matrix lands in benchmark.md, with machine-readable artifacts alongside.

Teams pick harnesses, models, and prompts by anecdote: run it once, eyeball the transcript, trust the vibe. benchspec turns that guess into a measurement. Write the goal once, run it across the configurations you care about, and read off — in percentage points — how good each one actually is at accomplishing it.

Getting Started

pip install "benchspec[microsandbox]"

Pre-1.0. The eval format and the artifact schemas are the surfaces most likely to change. microsandbox is the only sandbox backend today; docker is recognized in config but fails fast as not implemented.

An eval is one Markdown file: a prompt, then a checklist of plain-prose claims about the workspace after the agent is done. There is no checker syntax to learn; the wording is the spec. evals/hello/greets-by-name.eval.md:

---
---

## Prompt

You are working in a workspace rooted at your current working directory.
Greet Alice by name.

## Assertions

- [ ] ./Greetings/Alice.md contains the exact line 'Hello, Alice!'
- [ ] Skill `hello` invoked
- [ ] The greeting feels warm and personable, not curt or robotic

The benchmark is a block in pyproject.toml. Arms are the report columns; the baseline is what the others are measured against:

[tool.benchspec]
default-set = "default"

[tool.benchspec.sets.default]
harness  = "claude-code"
model    = "sonnet"
baseline = "baseline"
arms = [
  { name = "baseline" },   # installs nothing
  { name = "trial" },      # setup.sh installs the skill
]

Then:

benchspec lint      # static checks on the assertions
benchspec analyze   # which assertions grade deterministically, which go to the judge
benchspec run       # every (eval × arm) in its own microVM, graded, reported

What you need

Platform Apple Silicon Mac, or Linux with /dev/kvm. Python 3.11+.
Agent CLI claude, codex, or opencode on PATH, with its credential (for Claude Code, CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY).
GEMINI_API_KEY The binder: a fixed Gemini call that classifies each assertion. Required for every analyze and run.
Judge credential The judge runs on the host through an agent CLI; the default is claude-code with sonnet. Prefer a different vendor from the arms (this repo's own suite judges Claude arms with Codex).

Credentials can live in a repo-root .env. A graded run can touch up to three vendors: the agent's, Gemini for the binder, and the judge's. lint is free; analyze and run spend API calls, and run also boots VMs. Preflight lists every missing piece and exits before anything is spent.

How a run works

flowchart LR
    E["greets-by-name.eval.md<br/>prompt + assertions"] --> A1["arm: baseline<br/>fresh microVM,<br/>setup.sh installs nothing"]
    E --> A2["arm: trial<br/>fresh microVM,<br/>setup.sh installs the hello skill"]
    A1 --> F1["facts: files, SHAs,<br/>final message, tool calls"]
    A2 --> F2["facts"]
    F1 --> G["binder: deterministic checkers<br/>everything else: LLM judge"]
    F2 --> G
    G --> R["benchmark.md + benchmark.json<br/>meta.json + index.jsonl"]

Each (eval × arm) pair is one parametrized pytest test. A cell:

  1. Boots a microVM from a cached snapshot with the agent CLI already installed. The first run builds the snapshot (a few minutes); later runs reuse it, or pay the cost up front with benchspec sandbox:build.
  2. Seeds the clean room — the eval's optional workspace/ files land in a fresh directory mounted at /workspace, the agent's working directory.
  3. Runs setup.sh, where arms diverge: it sees $BENCHSPEC_ARM, so the baseline branch exits early and the trial branch copies the skill into place.
  4. Invokes the agent on the eval's prompt.
  5. Collects the facts — file tree, contents, SHA-256s, the final message, the tool calls.
  6. Grades — the binder maps each assertion to a deterministic checker where it can do so without risk; the judge grades everything else from the collected evidence alone.

Two guarantees hold throughout. Nothing in the guest can write back to your checkout: setup.sh reaches the skill under test through a read-only staged copy of your repo at /project (what a git clone would contain — never .env, .git, or earlier runs' artifacts). And provider credentials are injected at the network boundary, never as readable environment variables in the guest.

benchspec run is pytest underneath, and everything after -- goes to pytest verbatim: benchspec run -- -k greets-by-name (equivalently pytest -k greets-by-name) runs one eval, -n 8 fans cells across eight microVMs, and --count 5 samples each cell five times so the report can flag a delta that sits within noise.

Why benchspec

  • Comparison is first-class. A single pass rate is a number without a reference point. Arms and a baseline make the headline a delta; skip the baseline when absolute rates are what you want.
  • Deterministic where possible, judged where necessary. The binder is tuned so a false positive, a surface check passing on wrong output, is the one unacceptable error; anything doubtful goes to the judge, which sees the collected evidence and never grades from recall.
  • Self-describing artifacts. Every run writes meta.json (planned config plus observed provenance, down to the agent version inside the guest), index.jsonl (one row per sample), and benchmark.json, so other tools can aggregate runs without knowing the directory layout.

Documentation

docs/quickstart.md Empty directory to a graded two-arm run.
docs/concepts.md The vocabulary: eval, arm, set, baseline, binder, judge.
docs/writing-evals.md The eval format, workspaces, setup.sh, and how grading decides what binds.
docs/configuration.md Sets, arms, the judge, every CLI flag, exit codes.
docs/sandbox.md Snapshots, mounts, credentials, host requirements.
docs/results.md Reading benchmark.md and the machine-readable artifacts.
docs/harnesses.md claude-code, codex, opencode, and adding your own.

Contributing

Issues and pull requests are welcome at github.com/theycallmeswift/benchspec. Until CONTRIBUTING.md lands, docs/style/development.md is the code style, and make test plus make lint are the bar.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

benchspec-0.0.1.tar.gz (274.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

benchspec-0.0.1-py3-none-any.whl (140.1 kB view details)

Uploaded Python 3

File details

Details for the file benchspec-0.0.1.tar.gz.

File metadata

  • Download URL: benchspec-0.0.1.tar.gz
  • Upload date:
  • Size: 274.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for benchspec-0.0.1.tar.gz
Algorithm Hash digest
SHA256 31391296c4cbfac55af3eb3e6c70139b473421415663176dafa7e3abb85e7ac5
MD5 552b1dba8e933d5e3f218f8b56d7f04c
BLAKE2b-256 8a99343fecadb3ba5f4eab78c37c1bebbebaac98a2a35ba889898c188a871da4

See more details on using hashes here.

Provenance

The following attestation bundles were made for benchspec-0.0.1.tar.gz:

Publisher: release.yml on theycallmeswift/benchspec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file benchspec-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: benchspec-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 140.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for benchspec-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 11b1df6eeaa54b9cfa7908b5f6a805ad3341900b322125f28370ce239933b4a0
MD5 487f5ec356a62d03d58ea5dba5ea9426
BLAKE2b-256 11221659b19cea6dc9ff63ac96142f75fc67ca9f12641e59bd2a234e3cd2a2dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for benchspec-0.0.1-py3-none-any.whl:

Publisher: release.yml on theycallmeswift/benchspec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page