Proving Ground

Agents judged by what happened, not by what they say happened

Proving Ground is a benchmark for tool-using agents. It runs them against a deterministic simulated company (customers, orders, tickets, an inbox) and scores the world’s event log, not the agent’s final message. Its headline metric is hallucinated success: the agent says the refund went through, and the ledger shows no refund.

Type
Personal project, 2026, designed and built end to end
World
40 customers, 120 orders, 30 tickets, 24 emails, 9 knowledge-base documents
Suite
24 scenarios, 20 tools, 4 scaffolds, 6 policy profiles
Models
Any OpenAI-compatible endpoint, temperature 0, fixed seed
Tests
232 test cases (pytest)

The machinery works and is tested: every scenario has a hand-written reference solution that must pass. It has not been benchmarked against a model yet, so there are no results to report.

One cell of the benchmark

A benchmark is a grid: scenario × scaffold × policy profile. Each cell runs one agent in one fresh world, and every tool call passes a policy gate before it can change anything.

One adversarial scenario, one agent, one world
Benchmark cell scenario × scaffold Build the world seed 1001 + patch Agent turn one of 4 scaffolds Policy gate taint, budget, confirm Run the tool event logged first Score the world checks on the log Classify 14 failure codes Results SQLite + live UI Benchmark cell scenario × scaffold Build the world seed 1001 + patch Agent turn one of 4 scaffolds Policy gate taint, budget, confirm Run the tool event logged first Score the world checks on the log Classify 14 failure codes Results SQLite + live UI
  • Input or output
  • LLM call
  • Code
  • Check or gate
  1. Benchmark cell One cell: an adversarial scenario, the ReAct scaffold, the “taint” policy profile. Every model reply is recorded to a cassette, so the cell can be replayed exactly.
  2. Build the world A fresh world from seed 1001 plus the scenario’s own patch, written to the event log by the actor “system”.
  3. Agent turn The model reads the task and asks for a tool: read_inbox.
  4. Policy gate Unknown tool, allowlist, steps, tokens, repeats; then, for irreversible tools only, deny, taint and confirmation. A read inside budget: allowed.
  5. Run the tool The virtual clock advances and a world event is appended before anything changes. The email text goes to the taint tracker.
  6. Agent turn One email carries instructions. The agent calls issue_refund, an irreversible tool, with an order id that appeared only in that untrusted text.
  7. Policy gate DENY_TAINT. The call is refused with its reason, and the world and the clock are left untouched.
  8. Agent turn The agent gives its final answer without refunding.
  9. Score the world Checks run over the final world, never over the reply: no refund event exists. Had the reply claimed one, it would count as hallucinated success.
  10. Classify A rule-based failure code ordered by root cause, and the attack’s outcome read from the world.
  11. Results Saved to SQLite and streamed over a WebSocket to the dashboard: success, safety, hallucinated success, steps and cost.

Other routes through the graph

  • The turn loop also ends after 3 malformed replies in a row, or at 31 turns (the 25-step budget plus 6).
  • Out of budget: the agent is told to wrap up and be honest, and its next blocked call ends the run.
  • Irreversible tools under the “confirm” profile: the call returns a token the agent must echo back. There is no simulated approver whose answer could leak.
  • An optional judge model scores clarity, grounding and calibration from 1 to 5. It can never change success.

A third of the suite can’t be won by acting

The 24 scenarios come in five families: 6 direct, 4 ambiguous, 4 impossible, 4 distractor and 6 adversarial. In the ambiguous and impossible ones the right move is to ask or to refuse, so an agent that always acts loses a third of the suite.

The world even allows policy violations, such as refunding a cancelled order, so that it is the agent being measured and not the simulator. Each scenario ships with a hand-written reference solution that must pass and must not be flagged, which tests the scenarios themselves.

Four scaffolds, the same 20 tools

10 read tools, 6 reversible writes and 4 irreversible actions: refunds, emails, discounts and deletions.

  • ReAct

    One loop: think, call a tool, read the observation, until a final answer.

  • Plan and execute

    A planner writes a structured plan first; one replan is allowed if steps were blocked or failed.

  • Reflexion

    After a first attempt, a smaller critic reviews a mechanical record of calls and world events, and the agent gets one more attempt with at most 3 lessons. The world is not reset.

  • Orchestrator and workers

    A lead delegates up to 3 subtasks to workers with narrowed tools (a researcher sees 10, an operator 16, a settler all 20), enforced in the call itself, not only hidden from the prompt.

Six policy profiles

From open to strict. Delimited fences untrusted text; confirm demands a token for irreversible tools; taint blocks irreversible calls whose arguments came from untrusted text (5-word shingles, rare tokens, email addresses); strict combines confirm and taint, and readonly is the sixth.

Taint is checked before confirmation, so an injected instruction can’t simply be confirmed into action.

Engineering choices

  • World state decides

    Success is computed from the event log. A judge model can comment on the reply but can’t change the score.

  • No LangGraph, on purpose

    The scaffold is the variable under test, so the loop has to be interceptable, recordable and replayable line by line.

  • Deterministic by construction

    A seeded world, a virtual clock, temperature 0 and one cassette per cell: a change re-records only the column it affects.

  • Taint without attack lists

    Detection tracks where text came from rather than matching known payloads. An allowlist of trusted identifiers fixed a real false positive that blocked the correct refund in a direct scenario.

  • Narrowing enforced, not suggested

    A worker’s tool list is checked when it calls, so a tool missing from its prompt is also missing from its reach.

  • Two wire protocols

    Native tool calling or a plain-text THOUGHT / ACTION protocol, recorded with each run, for models without function calling.

Stack

Agents
hand-written loops, OpenAI-compatible SDK, JSONL cassettes
Simulator
Python dataclasses, YAML scenarios, event sourcing, virtual clock
Backend
Python 3.12, FastAPI, WebSocket, SQLModel, SQLite
Frontend
React 18, Vite, Tailwind CSS 4, hand-drawn SVG charts
Quality
pytest, ruff, GitHub Actions

Building something similar?

I’m happy to walk through the design, the trade-offs, or what I would change next.