CESAR TORTOLERO Back to the Lab

Notes · method

How I evaluate agents

How I evaluate agents, shown on public evidence only: the agents that built this site and an open-data lab. No employer systems or data appear here.

Pass and fail for each code check across the 77 traces
CheckShare of the 77 tracesPassFail
Tool error rate653
Recovered after error249
Delegation on large tasks285
Review before commit1220
Tests before commit311
Checked after the last edit214
No stuck loop680
Changes after review39
Model latency budget689
Each bar is the 77 traces of the agent sessions that built this site, scored by one deterministic code check; not scored means the check did not apply. Figure 4 opens every trace.

The facts and numbers come from this site: my homepage, the evaluation lab, the recorded agent run that built the site and, for the figures, the exported telemetry of the agent sessions behind it.

Topic
Agent evaluation
Evidence
Lab · build trace · telemetry
Length
About a 6-minute read

What to measure

One score hides where the errors are.

An agent can give a fluent answer and still fail the task, so a single accuracy number is the wrong place to start. For each task I would want four things, reported separately:

  • Task success. Did the run do what was asked, judged against an expected outcome written down before the run, not against whether the answer sounds right?
  • Groundedness. Is each claim supported by context the agent retrieved or a tool result it actually received? A correct answer with no support is a lucky answer.
  • Tool-call errors. Wrong tool, wrong or malformed arguments, and failed calls the agent quietly ignored.
  • Latency and cost per successful task. Totals divided by the tasks that succeeded, not by all runs. An agent that is cheap per call but needs retries to finish is not cheap.

A single number also hides where the errors fall. In the evaluation lab, at the default threshold, model B has the higher F1 overall, 0.803 against 0.795. On the IMDb slice it is the less accurate of the two, 0.750 against 0.775. I evaluated a skill-extraction model on 2,400+ records with precision, recall, F1 and slice analysis, not one score.

Figure 1 · One number against four

  • Task successNot measuredNeeds an expected outcome for each task. The telemetry has no task labels.
  • GroundednessNot measuredNeeds each claim checked against its sources. The telemetry keeps no text.
  • Tool-call errors1.7%162 of 9,712 tool calls failed.
  • Latency and cost5.4 sMedian model step; p90 21.5 s over 7,515 steps. 797 generated tokens per tool call, all traces pooled. Per successful task needs the success labels.
The four measures, filled in from the telemetry of the agent runs that built this site where it can answer, and left empty where it cannot: task success and groundedness need labels the export does not have, so they are not estimated. Below, the lab’s one-number view: move the threshold, then switch to By slice to see what the single F1 hides.
Data table
The four measures, from the telemetry and the lab
MeasureValue
Task successNot measured, no task labels
GroundednessNot measured, no text kept
Tool-call errors162 of 9,712 calls failed, 1.7%
Model step latencymedian 5.4 s, p90 21.5 s, 7,515 steps
Generated tokens per tool call, all traces pooled797, from 7,739,418 output and reasoning tokens
Lab F1 at threshold 0.50A 0.795, B 0.803
Lab IMDb accuracy at 0.50A 0.775, B 0.750

A threshold is a decision, not a default. Raising it trades recall for precision, and the right point depends on what a false positive costs. The lab’s slider shows the trade on the test set, but a threshold you deploy should come from validation data. For an agent, the same choice decides when it acts on its own and when it stops and asks.

A small gap is often noise. In the lab, B leads A by 0.008 F1 on 596 test sentences. Across 1,000 bootstrap resamples of the test set, the interval for that gap runs from −0.021 to +0.038, and B comes out ahead in 70% of them. The interval crosses zero, so a 0.008 F1 gap is not a win, and I would not switch models on it. A hand-built agent test set with fewer cases gives a wider interval, so it needs one even more.

How to catch failures

Read the trace before you touch the prompt.

Figure 2 · Illustration

  1. TraceEvery run, every spanHere: 77 traces, 17,412 spans
  2. ClassifyOne bucket per failureHere: 162 failed tool calls, Figure 3
  3. Fixed test setReal failures, expected outcomesNot in the telemetry yet
  4. GateBlock a release while it failsHere: 13 code evaluators per trace, Figure 4
The loop I would set up: a failure found in a trace becomes a test case, and the test set decides whether a change ships. Illustration: the moving highlight is a diagram; the counts under the steps come from the telemetry, which has no fixed test set yet. The dashboard’s release gate re-scores the traces offline and blocks nothing.

Traces come first. I set up LLM tracing and evaluation with Arize AX and OpenTelemetry/OpenInference. In OpenTelemetry terms a run is a trace and each step inside it is a span; OpenInference adds conventions for LLM steps such as model calls, retrievals and tool calls. When spans record inputs, outputs, timing and errors, a failed run shows which step went wrong instead of leaving you guessing from the final answer.

Then put each failure in one bucket:

  • Wrong tool. The agent called a tool that cannot answer the question, or answered without the tool it needed.
  • Bad arguments. The right tool with the wrong parameters or format.
  • Missing evidence. The answer states something that no span supports.
  • Timeouts and infrastructure. The call never had a fair chance. Some agent integration failures I have debugged were of this kind and had nothing to do with the model: MCP IAM request signing, OAuth credentials, missing deployment secrets and network rules.

The exporter behind these figures does a rough version of this step from the error text alone: the 162 failed calls split into not found 48, permission 44, timeout 30, other 20, network 16 and bad arguments 4. It cannot see a wrong tool or missing evidence. The dashboard’s failure analysis splits the classes by tool.

Figure 3 · Failed tool calls in the telemetry

Of 9,712 tool calls, 162 failed. Tool counts every failure. Duration bucket uses the 8 failures published as single spans (the rest sit inside merged steps, whose durations are not published) and sorts them only by how long they ran: with no tool inputs in the export, it cannot tell a wrong tool from bad arguments. Choose a bar to list its spans; each opens in the agent observability dashboard.
Data tables
Failed calls by tool, all 162
ToolFailedCallsFailed share
bash394,7610.8%
browser.evaluate_script351,0853.2%
read251,3231.9%
webfetch158118.5%
browser.list_pages114723.4%
browser.navigate_page94482.0%
task71096.4%
browser.take_screenshot65631.1%
browser.emulate42571.6%
browser.click2248.3%
write21641.2%
browser.lighthouse_audit1128.3%
browser.new_page1741.4%
browser.performance_start_trace1812.5%
browser.select_page1472.1%
browser.wait_for1250.0%
edit13530.3%
grep1185.6%
Published failed spans by duration, 8
BucketSpans
Fast failure (under a second)1
Slow failure (a second to a minute)1
Timeout-like (a minute or more)0
Delegated run stopped (a task call that failed after a minute or more)6

Each classified failure should become a case in a small, fixed test set with an expected outcome. If the set does not change between runs, a moving score means the agent changed. Next to it, keep a failure log: the trace, the bucket, the fix, and the test that now covers it. Learning from what went wrong also shaped the onboarding guide for the workshops I co-delivered. Each workshop showed where developers got stuck, such as missing secrets, access groups and network rules, and the guide changed to match. It has been revised more than 40 times.

The recorded run that built this site shows the pattern on a small scale. Of its 52 steps, 8 ended in an error, and 6 of those are followed by a recorded retry or fix. Two were delegated tasks that stopped midway, one interrupted and one after a lost connection, and both were resumed. One was a measurement: the first attempt to hold a figure on screen longer overshot, 6.4 s against 3.8 s before, and the saved version settled at about 5.2 s. The dependency download never cleared: it kept failing with a 503, and the trace records the normal full test command as blocked. Separately, a grounding test caught two homepage labels that the text did not support, and both were reworded before the commit.

Figure 4 · Code evaluators, trace by trace

Each column is a trace (one user turn), each row a deterministic check the exporter runs; there is no model acting as judge. Pick a trace, or point at a column, to read every result and its reason. One caveat: Review before commit only counts a quality-reviewer run recorded since the previous commit in the same session. A review by another agent type, or by a person, does not count, so its 20 fails mean no recorded reviewer run, not an unreviewed change. Rows that hold a number are shaded from low to high, and Most common error names the error class a trace hit most.
Data table
Results across 77 traces
EvaluatorPassFailNot scored
Tool error rate6539
Recovered after error24944
Delegation on large tasks28544
Review before commit122045
Tests before commit31145
Checked after the last edit21452
No stuck loop6809
Changes after review3965
Model latency budget6890
Generated tokens per tool callmedian 747.3, range 164.9 to 2,9519
Parallel subagentsmedian 2, range 1 to 445
Prompt cache reusemedian 0.9336, range 0 to 0.99930
Most common errormost often not found, in 11 of 33 traces44

When fewer agents beat more

Add a specialist only where it pays for the handoff.

Each handoff between agents costs something. The receiving agent gets a summary instead of the full context, and a task that dies midway has to be handed off again. So start with one agent, and add a specialist only when it does a job the first one does badly and has a clear input and output.

In the build trace, one orchestrator, my primary agent working under my direction, recorded 37 of the 52 steps and made all 16 commits. The implementation-worker recorded 9, the quality-reviewer 4 and the ui-design-director 2. Both tasks that stopped midway and left partial edits behind were delegated worker tasks.

The specialist reviewers added value at review points. The quality-reviewer’s first pass found frozen review notes, blank waits and copy hidden on short screens. A later pass on the integrated changes found no high-severity issues and six smaller findings. A second pass on the security changes reported five low-priority findings, all fixed before saving. The ui-design-director’s review found tiny text, crowded labels and uneven tour pacing. A reviewer with fresh context reads the change, not the conversation that produced it.

There is no second run with more or fewer agents, so the trace cannot tell you whether this split beat one agent doing everything. It counts role types, not separate agent instances. Most times are estimates, so it says nothing about latency or cost per role. And it covers one project, the Nov 10–11 redesign only.

Figure 5 · Traces with and without delegation

Each dot is a trace with tool calls, 68 of the 77, placed by how many calls it made (log scale). A trace counts as delegated when it has at least one subagent run. The Delegation on large tasks check in Figure 4 is narrower: it scores only the 33 large traces, and passes the 28 where subagents made at least half of the tool calls. Point at a dot, or use the slider, to read it. The traces that delegated were also the long ones: a median of 247 tool calls against 3. Their size, task and day all differ, and no task was run both ways, so this is not a controlled comparison and cannot show that delegation lowered errors or cost. Tokens are output plus reasoning; cache reads are left out. In the 77 traces of this site’s build, review and verification were selective: a verifier ran in 2 of 108 delegations and a reviewer in 14, and “Review before commit” passed in only 12 of 32 scored traces.
Data table
Traces with and without a subagent run
MeasureDelegatedNo delegation
Traces3245
Traces with tool calls3236
Tool calls9,391321
Failed calls14517
Share of calls that failed1.5%5.3%
Median generated tokens per call749714
Median tool calls per trace2473

My conclusion is narrow. Keep one agent in charge of integration and commits, and add specialists where an independent check is worth more than the handoff. The multi-agent workflow I built is set up that way: one subagent implements, one reviews, one verifies, and they get read-only access to delivery tools.

A checklist for calling an agent done

Every item should be true, not mostly true.

  1. Every run is traced, and any failing case can be opened in its trace.
  2. A fixed test set built from real failures, each with an expected outcome, runs on every change.
  3. Task success, groundedness and tool-call errors are reported separately, overall and by slice.
  4. Latency and cost are reported per successful task, not per call.
  5. Any comparison between versions comes with an interval. If it crosses zero, it is not a win.
  6. Thresholds and settings were chosen on validation data, never on the test set.
  7. Every open failure is in the log with its bucket and the test that will cover it.
  8. Permissions are the smallest that work, and secrets and network rules are checked in the deployed environment.

Read next