<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: benchclawio</title>
    <description>The latest articles on DEV Community by benchclawio (@benchclawio).</description>
    <link>https://dev.to/benchclawio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4076446%2F43fbca5e-4748-482a-b7b1-22fcc0973c89.jpg</url>
      <title>DEV Community: benchclawio</title>
      <link>https://dev.to/benchclawio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/benchclawio"/>
    <language>en</language>
    <item>
      <title>We ran 160 agent tasks across two frameworks. The frameworks tied. Then we changed the model.</title>
      <dc:creator>benchclawio</dc:creator>
      <pubDate>Sat, 29 Aug 2026 13:27:52 +0000</pubDate>
      <link>https://dev.to/benchclaw/we-ran-160-agent-tasks-across-two-frameworks-the-frameworks-tied-then-we-changed-the-model-3hip</link>
      <guid>https://dev.to/benchclaw/we-ran-160-agent-tasks-across-two-frameworks-the-frameworks-tied-then-we-changed-the-model-3hip</guid>
      <description>&lt;p&gt;We ran 160 agent tasks across two frameworks. The frameworks tied. Then we changed the model — and one task failed 20 times in a row.&lt;/p&gt;

&lt;p&gt;The LangChain vs LangGraph question comes up every time someone starts a new agent project. We had a slightly different version of it: once you've chosen LangGraph as your runtime, does picking the library around it actually change production outcomes?&lt;/p&gt;

&lt;p&gt;So we ran a controlled benchmark to find out.&lt;/p&gt;




&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;LangGraph 1.2.9 against Pydantic AI 2.13.0. Four tasks, designed to test realistic agent work: order processing with tool calls, shipping quote calculation, refund eligibility with date logic, and inventory reorder decisions. All tasks shared the same tool implementations and the same scorer — the only variable was which library orchestrated the calls.&lt;/p&gt;

&lt;p&gt;We used gpt-4o, temperature 0, no parallel tool calls. The harness is public at &lt;a href="https://github.com/benchclawio/harness" rel="noopener noreferrer"&gt;github.com/benchclawio/harness&lt;/a&gt;. Every run was logged, nothing was cherry-picked, the raw JSONL is published.&lt;/p&gt;

&lt;p&gt;160 task executions total. Here's what happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  The frameworks tied
&lt;/h2&gt;

&lt;p&gt;LangGraph: 100% correctness across all four tasks.&lt;br&gt;
Pydantic AI: 100% correctness across all four tasks.&lt;/p&gt;

&lt;p&gt;Not close — exact tie. Zero correctness gap on any task.&lt;/p&gt;

&lt;p&gt;There was a performance gap: LangGraph was roughly 1.4–1.8 seconds faster per task on median wall time. The cause is Pydantic AI's async-to-sync bridge — when you call it synchronously, it spins an event loop internally, and that overhead is real and consistent. If you're building a latency SLO, that matters. If you're evaluating correctness first, the frameworks are identical with gpt-4o.&lt;/p&gt;

&lt;p&gt;This result is unsatisfying in a useful way. It tells you that the framework abstraction is not where correctness variation lives — at least on these tasks, at this model, at this scale.&lt;/p&gt;

&lt;p&gt;So we ran the same tasks again. Same harness, same task suite. Different model.&lt;/p&gt;




&lt;h2&gt;
  
  
  The model didn't tie
&lt;/h2&gt;

&lt;p&gt;We substituted gpt-4o-mini for gpt-4o. Everything else held constant.&lt;/p&gt;

&lt;p&gt;Overall correctness dropped to 75%. The framework split: same.&lt;/p&gt;

&lt;p&gt;One task failed completely, 20 for 20 runs: &lt;code&gt;refund-policy-minimal-tools&lt;/code&gt;. The task requires computing whether a return is within the 18-day return window. gpt-4o-mini calculated 19 days inclusive where the answer is 18 days exclusive — a date-arithmetic edge case. It failed this identically on every single run, regardless of which framework was handling the tool calls.&lt;/p&gt;

&lt;p&gt;The other three tasks: 100%. The failure wasn't variance — it was systematic. And it was entirely a model property, not a framework property.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this means for the framework debate
&lt;/h2&gt;

&lt;p&gt;We had to decide what to compare LangGraph against before we started. Our dependency analysis (&lt;a href="https://benchclaw.io/langchain-vs-langgraph/" rel="noopener noreferrer"&gt;benchclaw.io/langchain-vs-langgraph/&lt;/a&gt;) found that LangChain 1.3.14 now declares LangGraph as an unconditional dependency — installing LangChain installs LangGraph. The reverse isn't true. So "LangChain vs LangGraph" is less of a choice than it used to be; the more real decision is whether to write in Pydantic AI or in LangGraph primitives once you're in the LangGraph runtime.&lt;/p&gt;

&lt;p&gt;Even reframed that way, our benchmark says the framework abstraction doesn't move correctness outcomes on structured tasks at gpt-4o.&lt;/p&gt;

&lt;p&gt;What did move outcomes was the model. By a lot, on a task that sounds easy.&lt;/p&gt;

&lt;p&gt;The practical implication: if your task suite involves any temporal reasoning — eligibility windows, expiry dates, SLA calculations — test both your intended model and a cheaper fallback explicitly before you ship. "It works in testing" is not enough if testing only used gpt-4o and production routes some traffic to gpt-4o-mini or a smaller model.&lt;/p&gt;




&lt;h2&gt;
  
  
  The finding we didn't expect
&lt;/h2&gt;

&lt;p&gt;Before we ran the model comparison, we expected the framework difference to be small. We expected correctness to hold. What we didn't anticipate was how systematic the failure mode was.&lt;/p&gt;

&lt;p&gt;gpt-4o-mini didn't get the date arithmetic right on some runs and wrong on others. It got it wrong 20 out of 20 times, by exactly the same margin. That's not a probabilistic sampling failure — that's a knowledge gap baked into the model at this temperature and this task phrasing.&lt;/p&gt;

&lt;p&gt;The implication is that per-task model validation matters more than framework benchmarking for production agent decisions. You can swap LangGraph for Pydantic AI or back again in an afternoon. You can't patch a model's arithmetic mid-deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  The data
&lt;/h2&gt;

&lt;p&gt;Full benchmark with methodology, raw JSONL, and harness: &lt;a href="https://benchclaw.io/langgraph-vs-pydantic-ai-benchmark/" rel="noopener noreferrer"&gt;benchclaw.io/langgraph-vs-pydantic-ai-benchmark/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Harness repo (Apache 2.0, citable): &lt;a href="https://github.com/benchclawio/harness" rel="noopener noreferrer"&gt;github.com/benchclawio/harness&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to reproduce it, the harness runs offline on any machine with Python and an OpenAI key.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;LangGraph 1.2.9, pydantic-ai-slim[openai] 2.13.0, gpt-4o (temperature 0) and gpt-4o-mini (temperature 0). Benchmarked 2026-07-24. All runs logged; nothing omitted.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>testing</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>We Cut Our Agent's Token Bill by 21%. One Task in Four Got Nothing.</title>
      <dc:creator>benchclawio</dc:creator>
      <pubDate>Tue, 18 Aug 2026 08:53:02 +0000</pubDate>
      <link>https://dev.to/benchclaw/we-cut-our-agents-token-bill-by-21-one-task-in-four-got-nothing-5h9n</link>
      <guid>https://dev.to/benchclaw/we-cut-our-agents-token-bill-by-21-one-task-in-four-got-nothing-5h9n</guid>
      <description>&lt;p&gt;We turned on progressive disclosure for an agent with 20 tools. Deferring the tool schemas cut input tokens by about 30% and total cost by about 21%. Good result, shipped, write the blog post.&lt;/p&gt;

&lt;p&gt;Then I broke the numbers down by task, and the average turned out to be hiding something. Three of our four task types saved 21% to 30%. The fourth saved nothing on one transport and cost &lt;strong&gt;12.3% more&lt;/strong&gt; on the other.&lt;/p&gt;

&lt;p&gt;This post is about that fourth task, and about why the number you should care about when you defer tool schemas is not the mean.&lt;/p&gt;

&lt;p&gt;All 80 runs are public. Every command below is one I ran while writing this, with its real output pasted in. You can reproduce the whole thing offline for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Four business tasks, each needing exactly one tool. Twenty tool schemas registered, always. Two arms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;always-on&lt;/strong&gt;: all 20 schemas serialised into every request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;deferred&lt;/strong&gt;: all 20 wrapped in Pydantic AI's &lt;code&gt;DeferredLoadingToolset&lt;/code&gt;, so the model has to search for a capability and load it before calling it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Twenty runs per cell, sequential, no retries, &lt;code&gt;gpt-4o&lt;/code&gt; at temperature 0, &lt;code&gt;parallel_tool_calls=False&lt;/code&gt;. Run on 2026-08-06 against &lt;code&gt;pydantic-ai-slim[openai]==2.24.0&lt;/code&gt;. Both transports, Chat Completions and the Responses API, because tool search executes server-side on one and through a local fallback on the other.&lt;/p&gt;

&lt;p&gt;The whole thing cost $0.279585 to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The averages, which are fine
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cell     mean_in    stdev    min    max
A-chat    1361.5     10.3   1350   1372
B-chat     945.2    190.8    780   1264
A-resp    1353.8     10.6   1342   1364
B-resp    1001.0    263.1    807   1477
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;A&lt;/code&gt; is always-on, &lt;code&gt;B&lt;/code&gt; is deferred. Input tokens down 30.6% on Chat Completions and 26.1% on Responses. Cost down 21.2% and 16.8%. Correctness was 80/80 exact matches, so nothing broke.&lt;/p&gt;

&lt;p&gt;But look at the standard deviation column. The always-on arms sit within 22 tokens of each other across every run. The deferred arms swing across a 484 and a 670 token range. Deferral made the input size roughly twenty times more variable.&lt;/p&gt;

&lt;p&gt;My first assumption was run-to-run nondeterminism: the model writes a slightly different search query each time, gets back a slightly different set of schemas, and the prompt size wobbles. That assumption was wrong, and checking it is what produced the actual finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decomposing the variance
&lt;/h2&gt;

&lt;p&gt;Here is the check. Group by task inside each cell instead of pooling the cell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; bc025.jsonl https://raw.githubusercontent.com/benchclawio/harness/main/results/bc025-progressive-disclosure-2026-08-06/bc025-scored-raw-2026-08-06.jsonl

python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, statistics as s, collections
rows=[json.loads(l) for l in open('bc025.jsonl')]
for c in ('A-resp','B-resp'):
    print('==', c)
    g=collections.defaultdict(list)
    for r in rows:
        if r['cell']==c: g[r['task_id']].append(r['metrics']['tokens_in'])
    for t,v in sorted(g.items()):
        print(f'   {t:22s} n={len(v)} mean={s.fmean(v):7.1f} sd={s.stdev(v):6.1f}')
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;== A-resp
   currency-conversion    n=5 mean= 1342.0 sd=   0.0
   defect-threshold       n=5 mean= 1364.0 sd=   0.0
   inventory-reorder      n=5 mean= 1345.0 sd=   0.0
   shipment-delay         n=5 mean= 1364.0 sd=   0.0
== B-resp
   currency-conversion    n=5 mean=  930.2 sd=   2.7
   defect-threshold       n=5 mean= 1431.8 sd=  95.6
   inventory-reorder      n=5 mean=  807.0 sd=   0.0
   shipment-delay         n=5 mean=  835.0 sd=   0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things fall out of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run-to-run variance is near zero in both arms.&lt;/strong&gt; Repeat the same task five times under deferral and you mostly get the identical token count, standard deviation 0.0. The nondeterminism I assumed was there is not there, or is small enough not to matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The spread is between tasks, not between runs.&lt;/strong&gt; Under always-on, all four tasks land within 22 tokens of each other, because the 20 schemas dominate and the task text barely moves the total. Under deferral that flattening disappears and the tasks spread from 807 to 1,432 tokens.&lt;/p&gt;

&lt;p&gt;That reframes the whole thing. Deferral does not make your cost noisy. It makes your cost &lt;strong&gt;task-dependent&lt;/strong&gt;. Always-on charges you the same amount whatever the user asks. Deferred charges you according to what the model decides to search for and load, which means your bill now tracks your traffic mix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The task that lost money
&lt;/h2&gt;

&lt;p&gt;Once cost is a function of the task, some tasks can come out behind. Here is the per-task cost, both transports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, statistics as s, collections
rows=[json.loads(l) for l in open('bc025.jsonl')]
g=collections.defaultdict(list)
for r in rows: g[(r['cell'],r['task_id'])].append(r['metrics']['cost_usd'])
hdr=f&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{'task':22} {'A-resp':&amp;gt;10} {'B-resp':&amp;gt;10} {'delta':&amp;gt;8}   {'A-chat':&amp;gt;10} {'B-chat':&amp;gt;10} {'delta':&amp;gt;8}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;
print(hdr)
for t in ('currency-conversion','defect-threshold','inventory-reorder','shipment-delay'):
    ar=s.fmean(g[('A-resp',t)]); br=s.fmean(g[('B-resp',t)])
    ac=s.fmean(g[('A-chat',t)]); bc=s.fmean(g[('B-chat',t)])
    print(f'{t:22} {ar:10.6f} {br:10.6f} {br/ar*100-100:+7.1f}%   {ac:10.6f} {bc:10.6f} {bc/ac*100-100:+7.1f}%')
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;task                       A-resp     B-resp    delta       A-chat     B-chat    delta
currency-conversion      0.003795   0.003001   -20.9%     0.003795   0.002791   -26.5%
defect-threshold         0.003944   0.004428   +12.3%     0.003840   0.003810    -0.8%
inventory-reorder        0.003873   0.002707   -30.1%     0.003828   0.002770   -27.6%
shipment-delay           0.003970   0.002828   -28.8%     0.003846   0.002691   -30.0%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;defect-threshold&lt;/code&gt; is the outlier. On the Responses API it cost 12.3% &lt;strong&gt;more&lt;/strong&gt; with progressive disclosure on. On Chat Completions it saved 0.8%, which after four decimal places is a rounding error and not a saving.&lt;/p&gt;

&lt;p&gt;Its deferred prompt came in at 1,431.8 input tokens against 807.0 for the cheapest task in the same cell. That is 625 extra tokens of loaded schema for a task that, like every other task in the suite, needed exactly one tool.&lt;/p&gt;

&lt;p&gt;I want to be careful about the mechanism here, because the bundle does not record it. Every deferred run made exactly 2 tool searches and 3 model requests, &lt;code&gt;defect-threshold&lt;/code&gt; included, so it is not doing extra round-trips. The most likely explanation is that its search matched more of the 20 capabilities and pulled more schemas back into the prompt than the other tasks did. But the raw JSONL logs token counts and the tool that was finally called, not the schema set the search returned, so I cannot prove that from the published data. Treat it as the obvious inference, not a measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the distribution looks like
&lt;/h2&gt;

&lt;p&gt;The cost distribution per run makes the point better than a standard deviation does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json
rows=[json.loads(l) for l in open('bc025.jsonl')]
for c in ('B-chat','B-resp'):
    v=sorted(r['metrics']['cost_usd'] for r in rows if r['cell']==c)
    print(c, [f'{x:.6f}' for x in v]); print()
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002470&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002587&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002717&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002717&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002717&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002717&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002740&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002800&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002800&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002800&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002815&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002845&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002845&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002845&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002845&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.003810&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.003810&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.003810&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.003810&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.003810&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002707&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002707&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002707&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002707&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002707&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002828&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002828&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002828&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002828&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002828&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002992&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002992&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002992&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.002992&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.003037&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.003922&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.004497&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.004573&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.004573&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.004573&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not a bell curve with a fat tail. It is a cluster and then a cliff, and the cliff is one task type.&lt;/p&gt;

&lt;p&gt;On the Responses API, 5 of 20 deferred runs cost more than the &lt;strong&gt;average always-on run&lt;/strong&gt; ($0.003895). The worst deferred run cost $0.004573, which is 17.4% above the always-on mean. The optimisation that saves 16.8% on average was, for a quarter of these runs, not an optimisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd actually take from this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Compute your saving per task type, not per corpus.&lt;/strong&gt; A single blended percentage is the one number that cannot tell you whether to ship this. If your traffic is 80% the &lt;code&gt;defect-threshold&lt;/code&gt; shape, deferral loses you money while your dashboard reports a saving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The saving is capped by the schema share of your prompt.&lt;/strong&gt; Deferral removes tool schemas. It does not remove your system prompt, the user message, the conversation history, or the tool results coming back. In our tasks the schemas were roughly a third of the request, so removing nearly all of them saved roughly a third of input tokens. If you have five tools and a 4,000-token system prompt, there is nothing here for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Budget the extra round-trip as a certainty.&lt;/strong&gt; Always-on completed in 2 model requests. Deferred took 3, in all 40 deferred runs, on both transports. Not an average with spread, a constant. If your latency budget is per-request rather than per-token, you are buying a variable token reduction with a fixed 50% increase in requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not verify deferral through the framework's own view of its tools.&lt;/strong&gt; &lt;code&gt;AgentInfo.function_tools&lt;/code&gt; lists a deferred tool both before and after it loads, and the local &lt;code&gt;search_tools&lt;/code&gt; fallback is present either way. That surface tells you what the agent knows about, not what got serialised to the provider. Every number in this post comes from provider-reported request token counts instead, which is the only thing that maps to the invoice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy was not the thing that broke.&lt;/strong&gt; All 80 runs produced exact matches, 20 out of 20 in every cell. I expected correctness to be the risk and it was not, though our tasks each needed exactly one capability. Tasks needing several loads would compound the round-trips and give the model more chances to pick wrong. We did not test that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The bundle is 80 raw runs, both manifests with SHA-256s, the deterministic task-suite generator, the worker, the collector and the analysis script:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/benchclawio/harness/tree/main/results/bc025-progressive-disclosure-2026-08-06" rel="noopener noreferrer"&gt;https://github.com/benchclawio/harness/tree/main/results/bc025-progressive-disclosure-2026-08-06&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;python3 analyze_bc025.py&lt;/code&gt; regenerates the confidence intervals. &lt;code&gt;python3 bc025_capabilities.py&lt;/code&gt; regenerates the task suite byte for byte. Both are offline and cost nothing, because they read the recorded runs rather than calling a model.&lt;/p&gt;

&lt;p&gt;The full benchmark, including the bootstrap confidence intervals and the part about where the widely quoted "90% to 98% savings" figure comes from, is written up at &lt;a href="https://benchclaw.io/agent-progressive-disclosure-token-cost/" rel="noopener noreferrer"&gt;benchclaw.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One caveat on the version, since it moved while we were running: &lt;code&gt;pydantic-ai-slim&lt;/code&gt; 2.25.0 landed on PyPI hours before these runs. We benchmarked 2.24.0 and then diffed the tags. &lt;code&gt;_tool_search.py&lt;/code&gt; and &lt;code&gt;toolsets/deferred_loading.py&lt;/code&gt;, the entire mechanism under test, are unchanged between them. We have not re-run on 2.25.0.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>performance</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Tried to Verify an AI Agent Benchmark. Here's the Bundle I Wish Everyone Shipped</title>
      <dc:creator>benchclawio</dc:creator>
      <pubDate>Fri, 14 Aug 2026 06:51:35 +0000</pubDate>
      <link>https://dev.to/benchclaw/i-tried-to-verify-an-ai-agent-benchmark-heres-the-bundle-i-wish-everyone-shipped-2okn</link>
      <guid>https://dev.to/benchclaw/i-tried-to-verify-an-ai-agent-benchmark-heres-the-bundle-i-wish-everyone-shipped-2okn</guid>
      <description>&lt;p&gt;Nearly every AI agent benchmark you read is unfalsifiable.&lt;/p&gt;

&lt;p&gt;Not wrong, necessarily - unfalsifiable. There's a blog post with a bar chart, a claim that framework A beat framework B, and no way for you to check it. No run count. No model version. No raw output. Often no cost. You are asked to trust a summary statistic produced by people with an interest in the result.&lt;/p&gt;

&lt;p&gt;We publish agent benchmarks, so this is our problem too. This post is about the evidence bundle we settled on, and how you can pull one down and take it apart in about two minutes. Every command below is one I actually ran while writing this, with its real output pasted in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The claim we're going to try to break
&lt;/h2&gt;

&lt;p&gt;From one of our pilot runs: &lt;strong&gt;LangGraph 1.2.9 and Pydantic AI 2.13.0 both completed 20 of 20 tasks under &lt;code&gt;gpt-4o&lt;/code&gt;, at a total spend of $0.094275.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the sort of sentence you'd normally have to take on faith. Let's not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two minutes to verify it yourself
&lt;/h2&gt;

&lt;p&gt;The bundle is a directory in a public repo. Pull it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://raw.githubusercontent.com/benchclawio/harness/main/results/gpt-4o-vs-gpt-4o-mini-tool-calling-2026-07-24"&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in &lt;/span&gt;SHA256SUMS README.md gpt4o-pilot-manifest-v0.4.0.json &lt;span class="se"&gt;\&lt;/span&gt;
         scored-pilot-gpt4o-raw-2026-07-24.jsonl &lt;span class="se"&gt;\&lt;/span&gt;
         scored-pilot-raw-2026-07-24.jsonl &lt;span class="se"&gt;\&lt;/span&gt;
         scored-pilot-analysis-2026-07-24.json &lt;span class="se"&gt;\&lt;/span&gt;
         scored-pilot-gpt4o-analysis-2026-07-24.json &lt;span class="se"&gt;\&lt;/span&gt;
         real-pilot-status-manifest-v0.3.0.json&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-sfO&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First question: is this the same data we published, or has something drifted?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; SHA256SUMS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;README.md: OK
gpt4o-pilot-manifest-v0.4.0.json: OK
real-pilot-status-manifest-v0.3.0.json: OK
scored-pilot-analysis-2026-07-24.json: OK
scored-pilot-gpt4o-analysis-2026-07-24.json: OK
scored-pilot-gpt4o-raw-2026-07-24.jsonl: OK
scored-pilot-raw-2026-07-24.jsonl: OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the cheapest integrity control there is and almost nobody ships it. It costs one line in your run script and it means a reader can tell the difference between the file you published and a file someone edited afterwards.&lt;/p&gt;

&lt;p&gt;Now the actual test. Ignore our summary entirely and recompute it from the raw runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scored-pilot-gpt4o-raw-2026-07-24.jsonl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total cost  $&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cost_usd&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;langgraph_1_2_9_gpt4o_live       20/20
pydantic_ai_2_13_0_gpt4o_live    20/20
total cost  $ 0.094275
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The claim reproduces to the cent, from the raw file, without going through our analysis code. That is the whole point of the exercise. Our published &lt;code&gt;scored-pilot-gpt4o-analysis-2026-07-24.json&lt;/code&gt; says &lt;code&gt;total_cost_usd: 0.094275&lt;/code&gt; and &lt;code&gt;total_runs: 40&lt;/code&gt; - and now you know the summary wasn't doing anything clever on the way there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually earns trust: finding the failure
&lt;/h2&gt;

&lt;p&gt;A benchmark where everything passes tells you very little. The same four tasks under &lt;code&gt;gpt-4o-mini&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scored-pilot-raw-2026-07-24.jsonl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;  &amp;lt;-- FAILED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inventory-reorder                10/10
dependent-shipping-quote         10/10
recover-stale-revision           10/10
refund-policy-minimal-tools      0/10  &amp;lt;-- FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero out of ten. Now the question a raw bundle can answer and a bar chart cannot: &lt;strong&gt;what kind of failure was it?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;refund-policy-minimal-tools&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status  :&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;failure :&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;failure&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tool calls:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tool_calls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;status&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'failure'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;failure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"{'type': 'invalid_final_answer', 'stage': 'scoring',
            'message_sanitized': 'final output does not exactly match expected output'}"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;calls:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the interesting shape. &lt;code&gt;stage: scoring&lt;/code&gt;, not &lt;code&gt;stage: execution&lt;/code&gt;. The agent made both tool calls it was supposed to make, raised no exception, hit no timeout, and returned a confident, well-formed, &lt;strong&gt;wrong&lt;/strong&gt; answer. (For the curious: it computed a 19-day window inclusive where the policy needed 18 exclusive, then applied a correct eligibility rule to a wrong number.)&lt;/p&gt;

&lt;p&gt;If all we had shipped was "75% completion rate", you would have no way to tell that apart from a crash loop or a rate limit. The failure taxonomy is doing real work here, and it's three fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wart I found writing this post
&lt;/h2&gt;

&lt;p&gt;Honesty is cheaper than getting caught. While writing the snippets above I hit this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scored-pilot-raw-2026-07-24.jsonl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scored-pilot-gpt4o-raw-2026-07-24.jsonl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mini keys:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;4o   keys:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mini&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;failure&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;model_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;run_index&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subject_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;   &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;failure&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;model_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;run_index&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wall_time_outer_s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two files in the same bundle, and one calls it &lt;code&gt;subject_id&lt;/code&gt; while the other calls it &lt;code&gt;subject&lt;/code&gt;. The &lt;code&gt;gpt-4o&lt;/code&gt; file also gained &lt;code&gt;score&lt;/code&gt; and &lt;code&gt;wall_time_outer_s&lt;/code&gt;. That's schema drift between two runs a few hours apart, and it's why the two snippets above key on different field names - which I'd rather explain than quietly paper over.&lt;/p&gt;

&lt;p&gt;It doesn't invalidate anything: the numbers are the numbers, and the checksums prove the files haven't moved since. But it's a real defect, it's the kind of thing a raw bundle exposes and a summary hides, and the fix is boring - a &lt;code&gt;schema_version&lt;/code&gt; on every row and a validator in CI, which is what we should have had.&lt;/p&gt;

&lt;p&gt;You are going to find this in your own bundles the first time someone reads them properly. Better that than nobody ever reading them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually goes in the bundle
&lt;/h2&gt;

&lt;p&gt;Stripped down, here's what we ended up shipping per run set, and why each piece is there:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*-raw-*.jsonl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One JSON object per run. The source of truth. Everything else is derived.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*-analysis-*.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Our summary. Included so you can check it against the raw.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*-manifest-*.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What we declared &lt;strong&gt;before&lt;/strong&gt; running: model, temperature, task suite, run count.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SHA256SUMS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Integrity. One line to generate, ends an entire category of argument.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What this run was and - importantly - what it is &lt;strong&gt;not&lt;/strong&gt; eligible to claim.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The manifest is the one people skip and it's the one that matters most. Ours pins &lt;code&gt;model_id&lt;/code&gt;, &lt;code&gt;temperature: 0&lt;/code&gt;, &lt;code&gt;parallel_tool_calls: false&lt;/code&gt;, and the per-token pricing used to compute cost. Declaring the run count &lt;em&gt;before&lt;/em&gt; execution is what stops a benchmark quietly becoming "we ran it until it looked right".&lt;/p&gt;

&lt;p&gt;And the README carries the eligibility verdict. That pilot is explicitly marked &lt;strong&gt;not publication-eligible&lt;/strong&gt; - the task suite changed mid-run, and there was an OOM restart. We still published the bundle, because a flawed run you can inspect is worth more than a clean-looking one you can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist, if you're building one
&lt;/h2&gt;

&lt;p&gt;Nothing here is hard. It's about ten lines of extra code in your run loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One row per run, JSONL, written as you go.&lt;/strong&gt; Not at the end - you want the partial data when something dies at run 32 of 40.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record tokens, cost, wall time, tool calls per run&lt;/strong&gt;, not just the aggregate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taxonomise failures with a &lt;code&gt;stage&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;execution&lt;/code&gt; vs &lt;code&gt;scoring&lt;/code&gt; is the single most informative bit you can store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the manifest before the first run&lt;/strong&gt;, and include the declared run count.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ship &lt;code&gt;SHA256SUMS&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version your row schema&lt;/strong&gt;, which I clearly need to take my own advice on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State what the run may not be used to claim.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a benchmark you're reading doesn't ship these, that isn't proof it's wrong. It just means neither of you can find out.&lt;/p&gt;




&lt;p&gt;The full harness, the task suite and every bundle mentioned here are at&lt;br&gt;
&lt;a href="https://github.com/benchclawio/harness" rel="noopener noreferrer"&gt;github.com/benchclawio/harness&lt;/a&gt;. Our write-up of the&lt;br&gt;
harness itself is at &lt;a href="https://benchclaw.io/harness/" rel="noopener noreferrer"&gt;benchclaw.io/harness&lt;/a&gt;, and the methodology&lt;br&gt;
we hold ourselves to - including what disqualifies a run from publication - is at&lt;br&gt;
&lt;a href="https://benchclaw.io/methodology/" rel="noopener noreferrer"&gt;benchclaw.io/methodology&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you spot something wrong in those numbers, that's the idea. Open an issue.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>python</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
