Releases
What shipped in each version, and which interfaces are covered by semver.
All notable changes to Agent Arena. This file is the source for the Releases page on the documentation site — the page is rendered from this file, so publishing a release means editing here.
The format follows Keep a Changelog, and versions follow Semantic Versioning.
[2.0.0] - 2026-09-05
The 2.0 line, promoted from 2.0.0rc3 and the first release published to the
real index. Three release candidates went to TestPyPI; what changed since rc3 is
one fix, below.
The headline of 2.0 is that the arena stopped being only about models. A
target — any Python callable, now including an async def one — is graded,
ranked and disqualified by exactly the machinery a model is, so the thing on the
leaderboard can be a whole pipeline, an agent architecture, or an application
from another repository entirely. The rest is the browser UI, resumable sweeps,
rate limiting, bootstrap confidence intervals, and arena watch for continuous
evaluation. See the three release-candidate sections below for the detail.
Fixed
preflighthealthchecks pipeline targets.CallableConnector.healthcheckhas always imported the target before a run — "so a typo costs nothing" — butpreflightonly called it for local endpoints, so nothing ever ran it. A target with a wrong path passedarena validateas "valid and ready" and then failed identically on every call, which reads as a model that answers nothing rather than as a path that is wrong.
Targets are now healthchecked alongside local servers and skipped with their
import error as the reason, and arena validate's summary line names that
third cause.
Changed
projects/marachecks for the assistant it evaluates at import rather than on first call, so a missing checkout surfaces atarena validatewith the environment variable to set. It is the one example here that needs a repository other than this one, and CI cannot exercise it for that reason.
Tests
- 721, up from 719: two cover a target that cannot be imported being caught by preflight rather than by the sweep.
[2.0.0rc3] - 2026-09-04
Added
- Async targets. A target declared
async defis now awaited by the connector instead of returning a coroutine the scorer cannot read. Agent frameworks are async-first — LangGraph, LlamaIndex and most tool loops hand you a coroutine — so the previous behaviour taxed the common case: every adapter had to open with the sameasyncio.runwrapper, and the one that forgot got a confusing error about the return type rather than an honest one about the shape of its function.
The runner calls connectors on worker threads, so this is asyncio.run in
every real sweep. Embedding the runner inside your own async program is the
other branch: a loop is already running on that thread, so the coroutine gets
its own loop on a thread of its own rather than deadlocking.
projects/mara/— example 6: an external multi-agent application. Every other example evaluates something written for the arena. This one points at a separate codebase — a four-agent LangGraph research assistant (planner → executor → critic → synthesizer) in its own repository — and compares three of its depth settings, which is the configuration question you actually have to answer before shipping such a thing.
It runs offline out of the box on the assistant's own scripted provider, and
is deliberately honest about what that proves: the wiring and the report's
structure, not which depth is better, because a fixture answer is the same
answer at every depth. The leaderboard says so — within noise for a small
sweep — instead of ranking three identical rows and calling it a result.
Two details in the adapter are the reusable part: it drives the app through its local host (SQLite checkpointer, in-process event sink) rather than its HTTP API, and it gives every call a fresh temporary data directory, so no test case can warm a search cache that a later one then benefits from.
Documentation
docs/guides/comparing-pipelines.mdgains an async example and a section on connecting a separate codebase, including why the two adapter decisions above matter for a fair comparison.- The README's layout table lists example 6, and a new section covers pointing the arena at your own application.
Tests
- 719, up from 715: four cover async targets — awaited, self-reporting, raising through, and driven from inside a running event loop.
[2.0.0rc2] - 2026-09-04
Added
arena evaluate --resume <run-id>. A sweep that died at 90% has already paid for those calls; starting over spends the money twice. Only successful calls are skipped — an errored one is exactly what a resume is meant to retry.- Per-provider rate limiting.
providers[].rate_limitnow applies: token buckets forrpmandtpm, a semaphore forconcurrency, and an optionalburstfor smoothing. A run that waited says so on the leaderboard, because otherwise it is just mysteriously slow. - Statistics (
core/statistics.py): bootstrap confidence intervals, a paired comparison of the top two, a power calculation, and the cases where the leaders most disagree. Resampled over test cases, not trials — a case is the unit of generalisation, and resampling trials would shrink every interval by a factor the data does not support. Configurable via astatistics:block; on by default.
The sentence it emits names accuracy explicitly, because the leaderboard ranks on the composite. "Indistinguishable" without that word reads as "the ranking is meaningless", when it usually means the opposite: the models answer about equally well, so cost and speed are deciding.
arena watch. Re-evaluates a project and compares the fresh result against the mean of its own recent history, flagging a real composite move or a status change. A configuredwebhookfires on drift;--fail-on-driftturns it into a CI gate;--loop --intervalruns it as a long-lived process for anyone without their own scheduler. Being disqualified in every run is not itself drift — only a change in status is, so a steadily-failing model is not re-flagged every tick.- A published GitHub Action (
.github/actions/agent-arena-eval) that evaluates a project on a pull request and posts the leaderboard as a comment, updated in place on every push rather than duplicated. An optionalbaselineinput (anarena export --format jsonfile, typically from the base branch) adds a delta column. Dogfooded in this repo by.github/workflows/pr-eval-demo.ymlagainst an offline mock-model project, so the action is proven in real GitHub Actions and not only intests/test_pr_comment_action.py. - Pricing-catalog staleness detection.
arena modelsandarena validatewarn whenmodel_cards.json'sas_ofis more than 90 days old — the roadmap's own "warn past 90 days" wording, given a name (PriceBook.is_stale) other code can reference. Dockerfile,.dockerignore,.devcontainer/devcontainer.json. The image defaults toarena ui --host 0.0.0.0, since binding loopback inside a container reaches nothing outside it; the Dockerfile's own comment says plainly what that means for anyone publishing the port. Verified end to end against the2.0.0rc1build on TestPyPI: the image builds, the CLI and the UI both run, and a project mounted at/data/projectsis discovered.- CLI test coverage for the lifecycle commands (
tests/test_cli_lifecycle.py).arena rm,export,providers,secrets,config,duplicate,archive,vacuum,label,runs,projectsandenvhad been added across earlier commits with only their service-layer logic tested — the argument parsing and confirmation prompts had never been exercised throughmain()itself.
Changed
-
_rehydratemoved fromweb/api.pyintocore/runner.py. Both a resumed run and the browser's what-if need it, and core may not import web. -
A multi-page browser UI. A sidenav shell (Evaluate / Reference / Configure) over Overview, Projects, Runs, Models, Scorers, Providers and a five-tab Settings section, plus a per-case grid showing every case against every model. Destructive actions fetch the server's
dry_runplan, print exactly what will be removed, and require the name typed before anything irreversible — the pattern already recorded in AGENTS.md, now actually enforced in the interface.
Built in vanilla JS, revisiting the React + FastAPI plan approved
earlier. With the API finished at 27 tested routes the remaining work was
wiring rather than architecture, and vanilla keeps invariant 2 intact:
pip install agent-arena still ships a working UI with no extra
dependency, no npm, and no build step.
- Browser UI/UX design overhaul:
- Multi-font hierarchy pairing Plus Jakarta Sans for headings, Inter for interfaces, and JetBrains Mono for benchmark scores and code.
- Sidenav overhaul with theme switch shortcut in footer (Dark | Settings), clean divider, and active navigation indicators.
- Interactive visual theme selector cards (System, Light, Dark) with OS window wireframes, live preview switching, and synchronized settings persistence.
- Card-based settings sections (Theme & Appearance, Workspace & Navigation, CLI Engine & Automation) with responsive 2-column/3-column grids, animated iOS/Linear-style toggle switches, input path badges, and unit suffixes (trials, workers, seconds, retries, temp, tokens, $).
- Models catalog table split into Available & Ready vs Needs API Key with green/yellow status indicators and instant text filtering.
- Runs stream interface with status filters, clean text status badges, and cleaner timestamps.
- Overview dashboard refined with horizontal breathing room and multi-column organization.
- Provider routes over HTTP (GET/POST /api/providers, DELETE,
/test, /discover) — provider management had been CLI-only, and a
Providers page without a working "Test connection" button is not worth
having.
Fixed
[hidden]did not hide. A class rule settingdisplaybeats the user agent's[hidden] { display: none }, so the modal overlay rendered permanently over every page. Fixed globally rather than per-element, since the toast had the same latent bug.- Static assets were served
max-age=60, so an upgrade could pair a freshapp.jswith a cachedapp.cssand render a subtly broken page for a minute. They are nowno-store, and the asset URLs carry the running version so both refetch together. _last_runnever returned the run's cost, so the overview's spend column would have been permanently empty.NotFoundErrorfrom the service layer fell through to a generic HTTP 400, making "no such provider" indistinguishable from "malformed provider" while the project and run routes already answered 404. It now maps to 404, andConflictErrorto 409.- Fixed nested defaults settings binding in
web/static/app.js(defaults.*) to prevent unknown parameter rejection errors on save. - Fixed horizontal dropdown width in settings forms with constrained max-width and balanced grid layouts.
[2.0.0rc1] - 2026-09-03
First release candidate. Published to TestPyPI only.
Added
LICENSE(MIT),SECURITY.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md, a PR template andCODEOWNERS.- A PyPI release workflow using Trusted Publishing (OIDC, no stored token). Its verify job installs the built wheel into a clean venv and scaffolds and runs a project from it, because package-data omissions are the classic release bug in this layout.
- Run and project lifecycle.
arena projects,runs,label,archive,duplicate,rm,vacuumandenv, and the matching HTTP routes including the firstDELETEverbs the product has ever had. Every destructive operation takesdry_runand returns the same plan either way, so a confirmation cannot misdescribe what it is about to do. A non-interactive shell without--yesrefuses rather than assuming yes. - Run cancellation.
POST /api/jobs/{id}/cancelstops a sweep that is spending money. The runner checks between calls, keeps the results already collected, and labels them partial. - Budget enforcement.
budgets.max_run_usdandmax_model_usdstop a run when the cap is crossed;on_exceed: warnrecords it without stopping. - Named provider profiles under
providers:— two API keys for the same vendor in one run, gateways with custom headers, a private CA, a proxy and model-prefix rewriting. The runner routes through them; asserted against a recording server rather than against the config object, because a config field that parses and changes nothing is the worst state for one to be in. - Export —
arena exportandGET /api/projects/{name}/exportwrite a run as CSV, JSON, markdown or HTML. The HTML is a single self-contained file with no CDN and no script, so it opens offline on a locked-down laptop. arena providers,arena secrets,arena config. A literal key passed toproviders addis moved into the OS keyring and only the reference reachessettings.json.- Credential references —
${env:},${keyring:},${file:},${cmd:}— and aSecrettype whosereprandstrare***. The OS keyring is reached through the platform tool rather than a Python dependency. - User settings at
~/.config/agent-arena/settings.json, written atomically at mode 0600, plusGET/PUT /api/settings. .envloading, wired into every command. Real environment variables win.- Cross-vendor prices for OpenAI, Gemini and Mistral. Models whose current list price could not be sourced are omitted rather than estimated.
- A documentation tree of 51 pages across architecture, security, design, testing, reference, guides, operations and roadmap.
Changed
- Store schema version 2:
deleted_at,archived_atandtagsonruns, with an idempotent migration runner driven by sqlite'suser_version. Every read path excludes soft-deleted runs unlessinclude_deletedis passed. - Retry now distinguishes terminal from retryable failures — a 401 fails once
instead of sleeping three times — and adds full jitter and
Retry-Aftersupport. arena uiretains at most 50 finished jobs. It previously only ever inserted.- Archiving a project now actually hides it from the default listing.
- The credential primitives moved from
service.secretstocore.secrets, so the connector registry can resolve a profile's key without importingservice— which would have been a cycle and would have pointed the dependency arrow backwards.service.secretsre-exports them and keeps the management side.SecretErrorjoins the hierarchy incore.errors.
Security
- A non-
GETrequest carrying anOriginfrom another site is refused. The Host allow-list stops DNS rebinding but not a plain cross-site form POST, which carries a legitimate Host header. - A request with no
Hostheader is refused rather than allowed.
Fixed
- A
JobManagerleak that grew unbounded for the life of anarena uiprocess. - Eight concurrent workers retrying a rate limit in lockstep.
- Cost silently dropping out of a cross-vendor leaderboard because the catalog priced only Anthropic models.
1.0.0 — 2026-09-02
First stable release. The engine, the browser UI, pipeline targets and the documentation site are all in place, and the public interfaces below are now covered by semantic versioning.
What it does
Agent Arena answers one question about your project: which model — or which pipeline architecture — should you actually ship, and what does that choice cost? Public benchmarks rank models on someone else's task against criteria you did not choose. This ranks them on your test cases, your accuracy floor, your budget and your latency ceiling, and refuses to recommend anything that cannot clear your hard constraints.
Added
- Config-driven evaluation engine. A project is a folder —
config.yamlplustests.yaml— with no second code path and no plugin to write. Ten built-in scorers (classification,exact_match,contains,regex,numeric,json_match,semantic,code_exec,llm_judge,manual), project-local scorers, andpre_request/post_process/on_resulthooks. - A weighted composite with hard constraints. Weight accuracy, cost and
latency to match how your product actually works; a model that misses a floor
is
DISQUALIFIEDwith the reason printed rather than quietly ranked below the ones you can ship. - Honest resolution. When the top two are within 0.02 the report says the sweep cannot separate them and asks for more cases, instead of implying a twelve-case run settled it.
- Providers: Anthropic, OpenAI, Gemini, LiteLLM, any OpenAI-compatible local
endpoint (Ollama, vLLM, LM Studio), and deterministic
mock:models so a whole project runs offline with no API key. arena ui— the same engine from a browser, for the people who set the budget but do not write YAML. A wizard that asks what job the AI is doing in plain language, results written as sentences instead of scores, and what-if sliders that re-rank from answers already collected with no new model calls. Stdlib-only: no Flask, no npm, no CDN.- Pipeline targets —
run: pipelines/rag.py:answerputs a whole multi-agent system on the same leaderboard as a single model call, graded and disqualified by exactly the same machinery. A target may report its own end-to-end spend and custom metrics, which are trusted over the price catalog. - Four example projects, all offline:
support_triage,doc_extraction,local_demo, andpipeline_demo(three architectures compared on one task). - Results database. Every call lands in SQLite alongside the Markdown and
JSON reports, so
arena historycan track regressions across runs. - The multi-agent handoff study, frozen and reproducible: a 28-run sweep showing that splitting a task across agents loses information at the handoff independently of model capability.
- Documentation site at adityamhaske.github.io/agent-arena, rendered from this repository's own markdown so it cannot go stale, with a build-time link checker that fails on any broken internal link.
Stability
From 1.0.0, these are the public surfaces covered by semantic versioning:
| Surface | Covered |
|---|---|
arena CLI commands, flags and exit codes |
yes |
config.yaml / tests.yaml schema |
yes |
Scorer, ScoreResult, ScoringContext |
yes |
Connector, GenerationRequest, GenerationResult |
yes |
| Hook signatures | yes |
run(), ArenaRunner, ProjectConfig |
yes |
| SQLite schema | additive changes only |
agent_arena.web.* internals |
no — the UI's HTTP API may change |
studies/multi_agent_handoff/ |
no — frozen research code |
Requirements
Python 3.10+. PyYAML is the only runtime dependency, and even that is optional — JSON config and test files work without it. Every provider SDK imports lazily, so you install only what you actually call.
Source: CHANGELOG.md