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.
| Check | Share of the 77 traces | Pass | Fail |
|---|---|---|---|
| Tool error rate | 65 | 3 | |
| Recovered after error | 24 | 9 | |
| Delegation on large tasks | 28 | 5 | |
| Review before commit | 12 | 20 | |
| Tests before commit | 31 | 1 | |
| Checked after the last edit | 21 | 4 | |
| No stuck loop | 68 | 0 | |
| Changes after review | 3 | 9 | |
| Model latency budget | 68 | 9 |
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.
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.
Evaluation lab · test set
| Score | Model A | Model B |
|---|
Data table
| Measure | Value |
|---|---|
| Task success | Not measured, no task labels |
| Groundedness | Not measured, no text kept |
| Tool-call errors | 162 of 9,712 calls failed, 1.7% |
| Model step latency | median 5.4 s, p90 21.5 s, 7,515 steps |
| Generated tokens per tool call, all traces pooled | 797, from 7,739,418 output and reasoning tokens |
| Lab F1 at threshold 0.50 | A 0.795, B 0.803 |
| Lab IMDb accuracy at 0.50 | A 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
- TraceEvery run, every spanHere: 77 traces, 17,412 spans
- ClassifyOne bucket per failureHere: 162 failed tool calls, Figure 3
- Fixed test setReal failures, expected outcomesNot in the telemetry yet
- GateBlock a release while it failsHere: 13 code evaluators per trace, Figure 4
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
Data tables
| Tool | Failed | Calls | Failed share |
|---|---|---|---|
| bash | 39 | 4,761 | 0.8% |
| browser.evaluate_script | 35 | 1,085 | 3.2% |
| read | 25 | 1,323 | 1.9% |
| webfetch | 15 | 81 | 18.5% |
| browser.list_pages | 11 | 47 | 23.4% |
| browser.navigate_page | 9 | 448 | 2.0% |
| task | 7 | 109 | 6.4% |
| browser.take_screenshot | 6 | 563 | 1.1% |
| browser.emulate | 4 | 257 | 1.6% |
| browser.click | 2 | 24 | 8.3% |
| write | 2 | 164 | 1.2% |
| browser.lighthouse_audit | 1 | 12 | 8.3% |
| browser.new_page | 1 | 74 | 1.4% |
| browser.performance_start_trace | 1 | 8 | 12.5% |
| browser.select_page | 1 | 47 | 2.1% |
| browser.wait_for | 1 | 2 | 50.0% |
| edit | 1 | 353 | 0.3% |
| grep | 1 | 18 | 5.6% |
| Bucket | Spans |
|---|---|
| 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
Data table
| Evaluator | Pass | Fail | Not scored |
|---|---|---|---|
| Tool error rate | 65 | 3 | 9 |
| Recovered after error | 24 | 9 | 44 |
| Delegation on large tasks | 28 | 5 | 44 |
| Review before commit | 12 | 20 | 45 |
| Tests before commit | 31 | 1 | 45 |
| Checked after the last edit | 21 | 4 | 52 |
| No stuck loop | 68 | 0 | 9 |
| Changes after review | 3 | 9 | 65 |
| Model latency budget | 68 | 9 | 0 |
| Generated tokens per tool call | median 747.3, range 164.9 to 2,951 | 9 | |
| Parallel subagents | median 2, range 1 to 4 | 45 | |
| Prompt cache reuse | median 0.9336, range 0 to 0.9993 | 0 | |
| Most common error | most often not found, in 11 of 33 traces | 44 | |
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
Show the numbers
| Trace | Tool calls | Failed calls | Failed, % | Generated tokens per call | Delegated |
|---|
Data table
| Measure | Delegated | No delegation |
|---|---|---|
| Traces | 32 | 45 |
| Traces with tool calls | 32 | 36 |
| Tool calls | 9,391 | 321 |
| Failed calls | 145 | 17 |
| Share of calls that failed | 1.5% | 5.3% |
| Median generated tokens per call | 749 | 714 |
| Median tool calls per trace | 247 | 3 |
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.
Not done until all are true.
- Every run is traced, and any failing case can be opened in its trace.
- A fixed test set built from real failures, each with an expected outcome, runs on every change.
- Task success, groundedness and tool-call errors are reported separately, overall and by slice.
- Latency and cost are reported per successful task, not per call.
- Any comparison between versions comes with an interval. If it crosses zero, it is not a win.
- Thresholds and settings were chosen on validation data, never on the test set.
- Every open failure is in the log with its bucket and the test that will cover it.
- Permissions are the smallest that work, and secrets and network rules are checked in the deployed environment.