Wheat Field with Cypresses by Vincent van Gogh

Artwork: Wheat Field with Cypresses by Vincent van Gogh. The Metropolitan Museum of Art · Public domain

Evaluation

Eval-Driven Development: Red-Green-Refactor for AI Agents

The fastest way to make an AI agent worse is to “improve” it speculatively. You tweak a prompt to fix one anecdote, ship it, and quietly break five things nobody was watching. Borrowing from test-driven development — and lessons from teams at Microsoft, Anthropic, and OpenAI — we adopted eval-driven development: every agent change must be justified by a failing evaluation.

The core rule

If it ain’t broke, don’t fix it. Every modification originates from a failing evaluation that reproduced a real problem.

That’s the whole philosophy. No speculative prompt edits, no “this feels better” refactors. If you can’t write an evaluation that fails, you don’t have a problem worth changing the agent for.

Agent evaluation architecture: a scenario suite feeds an offline Waza harness that gates deploys, production is watched by an online monitor, failures feed back into new scenarios, and a shared binary judge library serves both lanes.

The process below runs on a loop: the offline harness gates deploys, the online monitor watches production, and confirmed failures become new scenarios — with one shared judge library keeping both lanes calibrated. Source: diagrams/agent-evaluation-architecture.drawio (editable in draw.io).

The process

It’s Red-Green-Refactor, adapted for agents:

Hand-drawn evaluation loop showing a real failure becoming a failing scenario, a small agent change, a passing full suite, and linked evidence that remains as regression coverage.

1. Failure detected

A failure surfaces from one of three channels:

2. Open a ticket

Capture three things so the failure is reproducible and reviewable:

3. Write a scenario

Turn the failure into a YAML scenario that reproduces it systematically — not a one-off script, but a repeatable case that lives in the suite forever. Say the agent forgot to flag a billable emergency repair; the scenario encodes the conversation and the expected end state:

name: emergency_repair_billable
scripted_turns:
  - role: user
    text: "Emergency call — compressor failed. I replaced the compressor and contactor."
  - role: user
    text: "Contract only covers scheduled maintenance, so this should be billable."
success_criteria:
  required_topics_covered: [repair, parts, billable]
  must_complete: true
metadata:
  expected_classification: billable      # <- the outcome the bug got wrong
  parts_replaced: [compressor, contactor]

4. Validate the failure is detected (Red)

Run the new scenario. Something must fail — an assertion, a judge, or an outcome check. If nothing fails, your suite is blind to the problem, so you add a judge that catches it. This is the red step: you don’t get to fix anything until you can measure the failure.

5. Iterate the agent (Green)

Now, and only now, change the prompt, tools, or configuration — until the failing scenario passes.

6. Validate nothing broke

Run the full suite to confirm no regressions. This is the step speculative changes skip, and it’s the one that saves you.

7. Merge with linked runs

A PR isn’t complete without two linked evaluation runs:

The reviewer sees the red and the green. The change is self-justifying.

A worked example

The two linked runs make the fix legible at a glance. For the billable-repair bug above, three trials per scenario before and after the prompt change:

Run emergency_repair_billable Full suite (pass^3)
Before fix (step 4) 0/3 — classified as contract_covered 23/24 scenarios
After fix (step 6) 3/3 — classified as billable 24/24 scenarios

The left column proves the scenario reproduces the bug; the right column proves the fix didn’t regress the other 23 scenarios. No dashboard, no anecdote — just red → green with the suite as witness.

The supporting rules

Running it

# Run all scenarios with 3 trials each
dotnet run -- --scenario-dir=scenarios --trials=3

# Run a single scenario while iterating a fix
dotnet run -- --scenario scenarios/new-scenario.yaml --trials=1

# Run judge calibration
dotnet test --filter "Category=Calibration"

Why it works

Agents are non-deterministic, and their failure modes are long-tailed. You cannot brute-force quality by staring at prompts. What you can do is make every regression reproducible, every fix measurable, and every merge guarded by the same suite. Over time the scenario suite becomes an executable memory of every real problem the agent has ever had — which is exactly the asset you want when the next “small prompt tweak” comes along.

Takeaways

The next posts in the series cover the machinery this process runs on: a hybrid evaluator design and binary judges with outcome scoring.

#evaluation#ai-agents#testing#llm-judge#process