A benchmark for local models running inside the Hermes Agent harness. Captures full conversation traces (every tool call + result + reasoning + token IDs), asciinema recordings, and 5 Hz hardware telemetry. Designed to be the ground truth for "how good is this model at using hermes-agent?" — not just at generating text.
Repo:
github.com/am423/hermes-bench-tool-callPlan: seeproject.md(1,813 lines, 11 sections, 75 answered design questions) Rubric: seerubric.md(the self-grade)
Agents: see AGENTS.md for install, hermesbench run, flags, and repo rules.
- 61 tasks — 48 core tasks across 11 categories (terminal smoke,
file read, patch, search, write, process, todo, execute_code,
web_lookup, memory, error_recovery), 3
t12_real_worldintegration tasks, plus 10t13_humaneval_microcoding tasks - Runs the real
AIAgentfrom~/.hermes/hermes-agent/in a subprocess with a customtmux_isolatedenvironment backend - Captures three artifacts per task run:
trace.jsonl— every system/user/assistant/tool message with token IDs and reasoning content (loss-masked SFT-ready)trace.cast— asciinema v2 recording of the model's terminal session (X-shareable, replayable)stats.jsonl— 5 Hz hardware telemetry (CPU, GPU, RAM, NVMe, host power, model process tree) with thermal warnings
- Deterministic verifiers for every task (stdlib-only, no LLM-as-judge, no flakiness from network calls)
- 6 metric groups + 9 hardware metrics in the per-model summary: pass rate, tool efficiency, token efficiency, wall clock, recovery rate, format compliance, GPU power/temp, joules-per-token, thermal AUC, throttle seconds
git clone https://github.com/am423/hermes-bench-tool-call.git
cd hermes-bench-tool-call
./scripts/bootstrap.sh # .venv + pip install -e .
source .venv/bin/activate
hermesbench doctor --install # fix missing Python deps
hermesbench validatePrimary benchmark (hermesbench run → real Hermes run_agent.py; see AGENTS.md):
hermesbench run --use-hermes-config --model YOUR_MODEL \
--task t01_terminal_smoke/t01_echo --toolsets all
hermesbench run --use-hermes-config --model YOUR_MODEL --all --toolsets all
hermesbench report --run-id <run_id>Legacy engine (local OpenAI server + tmux runner + statsd telemetry):
hermesbench run --engine legacy --task t01_terminal_smoke/t01_echo \
--model qwen2.5-coder-7b-instruct-q4_k_m \
--base-url http://127.0.0.1:8080/v1| Command | What it does |
|---|---|
hermesbench setup |
Create .venv and editable install |
hermesbench doctor --install |
pip install missing Python dependencies |
hermesbench run |
Benchmark via real Hermes Agent (run_agent.py, default --engine real) |
hermesbench run --engine legacy |
Legacy GitHub runner (tmux + statsd) |
hermesbench report |
REPORT.md + video timeline from summary.json |
hermesbench list |
List all 61 tasks |
hermesbench list --difficulty 2 |
Filter by difficulty |
hermesbench validate |
Lint all task.yaml + verifier files |
hermesbench fixture-integrity |
Detect polluted tracked fixtures before scoring |
hermesbench run --task <id> |
Run one task |
hermesbench run --all |
Run all 61 tasks |
hermesbench run --all --dry-run |
Validate without spawning hermes (Q72) |
hermesbench run --resume <run_id> |
Resume a crashed run (Q24) |
hermesbench run --n-runs 3 |
Run each task 3× for variance (Q34) |
hermesbench doctor |
Pre-flight checks (Q70) |
hermesbench score results/<run>/ |
Re-score from existing results |
hermesbench render <cast> |
.cast → .gif/.mp4 with stats overlay (Q3.1a) |
hermesbench render --examples |
Show 5 common render invocations (Q71) |
hermesbench export-sft <runs> |
Traces → SFT jsonl with loss masks (Q45-Q47) |
By default, legacy task results land under results/<run_id>/<task_id>/ and
traces under traces/<run_id>/<task_id>/. If you pass --results-dir OUT to
the legacy engine, verifier results are written under OUT/<run_id>/<task_id>/;
traces are kept under OUT/traces/<run_id>/<task_id>/ unless OUT is literally
named results, in which case the historical sibling traces/ directory is used.
Before trusting patch/search/write scores, run:
hermesbench fixture-integrityThis catches tracked fixture pollution from prior real-agent runs while ignoring
new untracked fixture files by default. Use --include-untracked for stricter
release checks.
task.yaml + fixtures/
│
▼
runner.py ────► statsd (subprocess, niced, pinned core)
│ │
│ ▼
│ .stats.jsonl (5 Hz telemetry)
│
├──► hermes-agent (subprocess, --print-mode jsonl, --line-buffered)
│ │
│ │ TERMINAL_ENV=tmux_isolated
│ ▼
│ tmux session ──► .cast (asciinema v2, via pipe-pane)
│ (worktree, isolated $HOME, unshare --net, ulimit)
│ │
│ └─► read_file, patch, search_files, terminal, …
│
├──► .trace.jsonl (system/user/assistant/tool + token IDs + reasoning)
│
▼
scoring.py ──► results/<run_id>/<task_id>/
│
├──► pass_rate, J/tok, thermal warnings, hardware table
├──► export-sft ──► sft_dataset.jsonl (with loss masks)
└──► render ──► .gif / .mp4 (with --overlay-stats HUD)
See project.md §3 for the full design rationale.
hermes-bench-tool-call/
├── README.md # this file
├── project.md # the design plan (1,813 lines)
├── rubric.md # the self-grade (95/100)
├── Makefile # demo / doctor / test / lint / install
├── pyproject.toml # Python 3.11+, ruff, mypy strict
├── requirements.lock # Q75: pinned versions
├── .pre-commit-config.yaml
├── .github/workflows/ci.yml
├── hermesbench/ # the package
│ ├── types.py # TaskSpec, VerifierResult, HardwareMetrics
│ ├── backend/ # base, registry, tmux_isolated, recorder, worktree
│ ├── statsd/ # 5 Hz telemetry collector
│ ├── trace.py # Q52 trace reader/normalizer
│ ├── scoring.py # metrics, thermal compare, J/tok
│ ├── hermes_invocation.py # Q22 path, Q50 SHA, Q57 smoke
│ ├── runner.py # full task lifecycle
│ └── cli.py # click + rich CLI
├── tasks/ # 61 tasks in 13 categories
│ ├── _template/ # canonical task shape
│ ├── t01_terminal_smoke/ # 5 tasks
│ ├── t02_file_read/ # 6 tasks (incl. Q61 parallel)
│ ├── t03_patch_edit/ # 5 tasks
│ ├── t04_search_grep/ # 5 tasks
│ ├── t05_write_new/ # 5 tasks
│ ├── t06_process_mgmt/ # 5 tasks
│ ├── t07_todo_plan/ # 3 tasks
│ ├── t08_execute_code/ # 5 tasks
│ ├── t09_web_lookup/ # 3 tasks (mocked)
│ ├── t10_memory_facts/ # 3 tasks
│ ├── t11_error_recovery/ # 3 tasks (Q58)
│ ├── t12_real_world/ # 3 tasks
│ └── t13_humaneval_micro/ # 10 tasks
├── fixtures/ # task input data (small_repo/, broken_code/, …)
├── scripts/
│ ├── generate_tasks.py # idempotent task generator (Q28)
│ └── fake_model_server.py # for end-to-end testing
├── tests/ # regression and integration tests
└── docs/
├── trace_format_reconciliation.md # Q52
├── adding_backends.md # Q9.3
└── glossary.md # Q9.4
make test # run unit/integration-light regression suitetest_smoke.py— package import, pytest collecttest_recorder.py— asciinema v2 roundtrip (unit + integration)test_statsd.py— 5 Hz samples for 2s, schema verificationtest_statsd_pinning.py— priority lowering + core detectiontest_statsd_sources.py— per-source shape validationtest_scoring.py— hardware metrics, J/tok, thermal comparetest_cli.py— every subcommand + exit codestest_verifier_contract.py— every verifier returns VerifierResult-liketest_trace.py— Q52 reconciliationtest_lint_verifiers.py— AST walk, stdlib allowlisttest_lint_fixtures.py— injection pattern scannertest_lint_fixture_sizes.py— 100 KB cap
cp -r tasks/_template tasks/t12_my_category/t01_my_task/
$EDITOR tasks/t12_my_category/t01_my_task/task.yaml
# ... author verifier.py ...
python3 -m hermesbench validate tasks/t12_my_category/t01_my_task/See tasks/_template/ for the full schema and
docs/glossary.md for terminology.
See docs/adding_backends.md. In short:
subclass BaseHermesBenchEnvironment, register with
@register_backend("name"), import from
hermesbench/backend/__init__.py.
MIT.