PageSourceSearch

https://loopgain.ai/blog/posts/2000-agent-loop-trials-what-surprised-us/

html loopgain.ai collected 2026-10-05 12:25:43 UTC 50,335 bytes, 105 lines download raw bytes

1<!doctype html><html lang=en dir=auto data-theme=dark><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name=robots content="index, follow"><title>We ran 2,000 paired agent-loop trials. Here's what surprised us. | LoopGain Blog</title><meta name=keywords content="benchmark,agent-loops,loop-gain,evaluation,open-source,pre-registration"><meta name=description content="A pre-registered benchmark of LoopGain vs fixed max_iterations across 2,000 paired trials and six agent frameworks. The headline is 92.8% cheaper — but the interesting findings are the five things that complicate it."><meta name=author content="Dave Fitzsimmons"><link rel=canonical href=https://loopgain.ai/blog/posts/2000-agent-loop-trials-what-surprised-us/><link crossorigin=anonymous href=/blog/assets/css/stylesheet.001a8fbb035dd56025beb7fec8d530e0d9660f21e6e8619e1952f91f830ebf42.css integrity="sha256-ABqPuwNd1WAlvrf+yNUw4NlmDyHm6GGeGVL5H4MOv0I=" rel="preload stylesheet" as=style><link rel=icon href=https://loopgain.ai/blog/favicon.ico><link rel=icon type=image/png sizes=16x16 href=https://loopgain.ai/blog/favicon-16x16.png><link rel=icon type=image/png sizes=32x32 href=https://loopgain.ai/blog/favicon-32x32.png><link rel=apple-touch-icon href=https://loopgain.ai/blog/apple-touch-icon.png><link rel=mask-icon href=https://loopgain.ai/blog/safari-pinned-tab.svg><meta name=theme-color content="#2e2e33"><meta name=msapplication-TileColor content="#2e2e33"><link rel=alternate hreflang=en href=https://loopgain.ai/blog/posts/2000-agent-loop-trials-what-surprised-us/><noscript><style>#theme-toggle,.top-link{display:none}</style></noscript>
1<script>localStorage.getItem("pref-theme")==="light"&&(document.querySelector("html").dataset.theme="light")</script>
1<link rel=alternate type=application/rss+xml title="LoopGain Blog" href=https://loopgain.ai/blog/index.xml><link rel=icon type=image/svg+xml href=https://loopgain.ai/blog/favicon.svg><meta property="og:url" content="https://loopgain.ai/blog/posts/2000-agent-loop-trials-what-surprised-us/"><meta property="og:site_name" content="LoopGain Blog"><meta property="og:title" content="We ran 2,000 paired agent-loop trials. Here's what surprised us."><meta property="og:description" content="A pre-registered benchmark of LoopGain vs fixed max_iterations across 2,000 paired trials and six agent frameworks. The headline is 92.8% cheaper — but the interesting findings are the five things that complicate it."><meta property="og:locale" content="en_us"><meta property="og:type" content="article"><meta property="article:section" content="posts"><meta property="article:published_time" content="2026-06-07T09:05:00-04:00"><meta property="article:modified_time" content="2026-06-07T09:05:00-04:00"><meta property="article:tag" content="Benchmark"><meta property="article:tag" content="Agent-Loops"><meta property="article:tag" content="Loop-Gain"><meta property="article:tag" content="Evaluation"><meta property="article:tag" content="Open-Source"><meta property="article:tag" content="Pre-Registration"><meta property="og:image" content="https://loopgain.ai/blog/og/2000-trials-what-surprised-us.png"><meta name=twitter:card content="summary_large_image"><meta name=twitter:image content="https://loopgain.ai/blog/og/2000-trials-what-surprised-us.png"><meta name=twitter:title content="We ran 2,000 paired agent-loop trials. Here's what surprised us."><meta name=twitter:description content="A pre-registered benchmark of LoopGain vs fixed max_iterations across 2,000 paired trials and six agent frameworks. The headline is 92.8% cheaper — but the interesting findings are the five things that complicate it.">
1<script type=application/ld+json>{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Posts","item":"https://loopgain.ai/blog/posts/"},{"@type":"ListItem","position":2,"name":"We ran 2,000 paired agent-loop trials. Here's what surprised us.","item":"https://loopgain.ai/blog/posts/2000-agent-loop-trials-what-surprised-us/"}]}</script>
1<script type=application/ld+json>{"@context":"https://schema.org","@type":"BlogPosting","headline":"We ran 2,000 paired agent-loop trials. Here's what surprised us.","name":"We ran 2,000 paired agent-loop trials. Here\u0027s what surprised us.","description":"A pre-registered benchmark of LoopGain vs fixed max_iterations across 2,000 paired trials and six agent frameworks. The headline is 92.8% cheaper — but the interesting findings are the five things that complicate it.","keywords":["benchmark","agent-loops","loop-gain","evaluation","open-source","pre-registration"],"articleBody":" We built a benchmark to find out what LoopGain actually saves on real agent loops, then pre-registered the predictions before we looked at the data so we couldn’t move the goalposts. It ran 2,000 paired trials — each loop run under three fixed max_iterations caps (5, 10, 20) and under LoopGain — across six agent frameworks: four conditions × 2,000 trials = 8,000 total loop runs.\nThe headline is a 92.8% reduction in API spend versus max_iter=20, with median wall-clock dropping from 30.9s to 2.1s. That number is real, and it’s the least interesting thing we found.\nTotal API spend across 2,000 trials Lower is better. LoopGain vs fixed max_iterations caps · 92.8% cheaper than max_iter=20. (source: loopgain-bench RESULTS.md (registered, loopgain 0.4.0)) Total API spend across 2,000 trials Lower is better. LoopGain vs fixed max_iterations caps · 92.8% cheaper than max_iter=20. max_iter=5 $6.83 max_iter=10 $13.65 max_iter=20 $27.05 LoopGain $1.94 Source: loopgain-bench RESULTS.md (registered, loopgain 0.4.0) Total API spend across all 2,000 trials, by stopping condition. The fixed caps climb with their iteration budget; LoopGain stops when the loop is done. The interesting findings are the ones that complicate it. Here are five.\nSurprise 1: the state we built to catch oscillation mostly catches stalls LoopGain classifies a loop into five trajectory states — FAST_CONVERGE, CONVERGING, STALLING, OSCILLATING, DIVERGING. We shipped all five. (A sixth row below, TARGET_MET, is the separate short-circuit that fires when your error signal hits its target — not one of the five trajectory states, but it shows up in the emission counts.) Across 2,000 trials and 3,191 state emissions, here’s how often each actually fired:\nState Emissions Share TARGET_MET 1,302 40.8% FAST_CONVERGE 836 26.2% STALLING 680 21.3% DIVERGING 364 11.4% CONVERGING 8 0.25% OSCILLATING 1 0.03% How often each state actually fired 3,191 state emissions across 2,000 trials. A stuck loop is a stall: STALLING is common; OSCILLATING and CONVERGING are near-theoretical at scale. (source: loopgain-bench RESULTS.md, Finding 3 (registered, loopgain 0.4.0)) How often each state actually fired 3,191 state emissions across 2,000 trials. A stuck loop is a stall: STALLING is common; OSCILLATING and CONVERGING are near-theoretical at scale. TARGET_MET 1,302 FAST_CONVERGE 836 STALLING 680 DIVERGING 364 CONVERGING 8 fired 8× OSCILLATING 1 fired once Source: loopgain-bench RESULTS.md, Finding 3 (registered, loopgain 0.4.0) State emission counts across 2,000 trials. OSCILLATING and CONVERGING are slivers at this scale; STALLING is the third-most-common signal. Here is the surprise. We originally built that third band thinking of it as the oscillation catcher — the loop bouncing between two near-but-not-equal answers. At scale, OSCILLATING fired once in 2,000 trials. The band that actually does the work is STALLING, with 680 emissions. The failure mode real agent loops produce is not a loop thrashing up and down — it is a loop getting stuck, error pinned at a constant, grinding out iterations that change nothing.\nThat distinction is the whole point of the band, and it’s worth being precise about: an oscillation is error bouncing between values; a stall is error pinned flat. A loop stuck at “11 failing tests, 11 failing tests, 11 failing tests” is stalling, not oscillating — and labeling it correctly is what lets the monitor stop it. (An earlier build of the classifier mislabeled flat trajectories as OSCILLATING; calling a stuck loop a stall is the more correct reading, and it’s what shipped.)\nCONVERGING fired eight times and OSCILLATING once — the two states that describe gradual grind-down or genuine bistable bouncing are, at this model capability and on these workloads, almost theoretical. The reason is a fact about 2026-era models, not about our classifier: on calibrated tasks, LLMs one-shot or they get stuck. They nail it on iteration one, or they pin at a constant error. The smooth textbook convergence trajectory that control theory loves to draw is something real agent loops rarely produce.\nThe product implication is uncomfortable and we took it: “detects all five trajectory modes” is technically true but oversells what’s validated. So we lead with what the data actually supports — catches stalls and divergence, and stops them — the states with 1,000+ real emissions between them, not the ones that fired a handful of times.\nSurprise 2: the 92.8% is loaded toward the easy cases A single headline number hides a distribution. Segment the savings by what the loop actually did:\nLoop outcome n Savings vs max_iter=20 converged (hit target early) 1,302 96.6% diverged 364 83.9% stalled 333 78.2% The 92.8% is loaded toward the easy cases Cost savings vs max_iter=20, split by what the loop actually did. (source: loopgain-bench (computed from by_band.json, registered, loopgain 0.4.0)) The 92.8% is loaded toward the easy cases Cost savings vs max_iter=20, split by what the loop actually did. converged 96.6% diverged 83.9% stalled 78.2% Source: loopgain-bench (computed from by_band.json, registered, loopgain 0.4.0) Cost savings vs max_iter=20, split by loop outcome. The 92.8% headline is the blend of a mostly-easy corpus;
1 failure-mode workloads land closer to 78–84%. LoopGain saves money in every segment — but if your loops usually succeed fast, you’ll see numbers closer to 96.6%; if you live in adversarial, long-tail failure-mode territory, closer to 78–84%. Both are real. “92.8%” is the blend of a mostly-easy corpus, and quoting it without the spread would be the kind of benchmark sleight-of-hand we built this to avoid. (Stalled loops save the least — 78.2% — because a stall takes two consecutive flat readings to confirm before LoopGain stops, so it runs a hair longer than a hard divergence. More correct, marginally less cheap, and we’d rather show it than hide it.)\nSurprise 3: on normal workloads, we mostly don’t make answers better This is the one we most wanted to be different. On natural-distribution workloads — ordinary code-gen, debate, planning, RAG — a blind judge preferred LoopGain’s output to the run-to-cap baseline 50–63% of the time. That’s not “better.” That’s preserved: LoopGain returns an answer just as good, at roughly 5% of the cost.\nLoopGain only improves quality when running longer actively hurts. On workloads engineered to degrade — where the model is pushed to keep “fixing” an already-good answer — the judge preferred LoopGain 92–95% of the time, because best-so-far rollback returns the iteration that worked instead of the one that got mangled.\nThis isn’t a single-trial anecdote — it’s a measurable pattern across the whole benchmark. Of the fixed-cap (max_iter=20) runs that ran past their best iteration, 35.3% shipped a final answer worse than the best they had already reached — a median of 3× the error, up to 11×. The other ~65% merely plateaued, burning tokens on an answer that never improved. A fixed cap can’t tell “done” from “keep going,” so about a third of the time it grinds a good answer into a worse one. Best-so-far rollback is what turns that around: LoopGain returns the iteration that actually worked, not the mangled one the cap stopped on. That’s why best-so-far rollback — not just early stopping — is the mechanism that protects quality.\nSo the honest claim is two sentences, not one: LoopGain preserves quality on workloads where the model usually succeeds, at a fraction of the cost. It improves quality on workloads where iterating past success degrades the output. Anyone who tells you their loop controller makes every answer better is selling you something.\nSurprise 4: we missed one of our own predictions We pre-registered specific numeric floors before collecting data. We beat most of them. One we hit right at the line, and one we missed — and pre-registration is the thing that forces us to say so:\nEarly-warning lead time. We predicted LoopGain would flag a diverging loop a median of ≥ 3 iterations before the fixed cap hits its catastrophic point. It flagged at a median of 3 — exactly the floor, with no margin. It did flag in 364 of 367 catastrophe trials, so it almost never misses the divergence; it just doesn’t give you more warning than we promised. Met, reported flat, not rounded up. Framework parity. We predicted ≤ 5 percentage-points of quality spread between adapters on the same task. On debate (AutoGen vs CrewAI) we saw 5.5 — missed by 0.5 pp. Well under the threshold that would have killed the claim, but past what we predicted, so it’s in the writeup, not a footnote. The parity miss didn’t fire a kill criterion. We’re reporting it because a careful reader finds it anyway, and honest is faster than spin.\nSurprise 5: aggressive stopping has a real cost on retrieval On the iterative-RAG cell, LoopGain cut cost 98.1% — the biggest single-cell saving in the bench. It also retrieved the gold document 4.5 percentage points less often than the run-to-cap baseline (85.0% vs 89.5% hit@5). LoopGain’s early stop occasionally cuts off a retrieval loop that would have found the answer with a few more revision attempts.\nThat’s a genuine trade-off, not a rounding error: on retrieval workloads you can buy back some of that 4.5 pp by making LoopGain’s stopping more conservative. We report the default-config result because shipping the number that flatters you and hiding the tuning knob is how benchmarks lose trust.\nWhy pre-register a benchmark for your own product? Because a benchmark’s job isn’t to produce the cleanest number — it’s to produce the truest one, and the only way to keep yourself honest is to write the predictions down before you can see the data. We locked the protocol, the hypotheses, and six kill criteria before any real cell ran. Zero of the six fired — but one predicted floor was missed and another landed right at the line, and you just read about both, because the registration made hiding them impossible.\nEverything is public: the protocol with its amendments, the full raw trial data (every iteration of all 8,000 runs), the per-cell tables, and the live dashboard populated with all 2,000 trials. Reproduce it, argue with it, or run it on your own workloads — the harness is in the repo.\nThe headline is 92.8% cheaper. The reason to believe it is everything above that complicates it.\nLoopGain is pip install loopgain, Apache-2.0. The benchmark, protocol, and raw data are open; the numbers here are the registered results from loopgain 0.4.0.\n","wordCount":"1705","inLanguage":"en","image":"https://loopgain.ai/blog/og/2000-trials-what-surprised-us.png","datePublished":"2026-06-07T09:05:00-04:00","dateModified":"2026-06-07T09:05:00-04:00","author":{"@type":"Person","name":"Dave Fitzsimmons"},"mainEntityOfPage":{"@type":"WebPage","@id":"https://loopgain.ai/blog/posts/2000-agent-loop-trials-what-surprised-us/"},"publisher":{"@type":"Organization","name":"LoopGain Blog","logo":{"@type":"ImageObject","url":"https://loopgain.ai/blog/favicon.ico"}}}</script>
1</head><body id=top><header class=header><nav class=header-nav><div class=logo><a href=https://loopgain.ai/blog/ accesskey=h title="LoopGain (Alt + H)">LoopGain</a></div><ul id=menu class=menu><li><a href=https://loopgain.ai/blog/posts/ title=Posts><span>Posts</span></a></li><li><a href=https://loopgain.ai/blog/tags/ title=Tags><span>Tags</span></a></li><li><a href=https://loopgain.ai/ title="loopgain.ai ↗"><span>loopgain.ai ↗</span></a></li><li class=theme-toggle-li><button id=theme-toggle class=theme-toggle accesskey=t title="(Alt + T)" aria-label="Toggle theme">
2<svg class="moon" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>
3<svg class="sun" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button></li></ul></nav></header><main class=main><article class=post-single><header class=post-header><nav class=breadcrumbs role=navigation aria-label=Breadcrumb><a href=/blog/>Home</a>
4<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right"><polyline points="9 18 15 12 9 6"/></svg>
5<a href=/blog/posts/>Posts</a>
6<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right"><polyline points="9 18 15 12 9 6"/></svg></nav><h1 class="post-title entry-hint-parent">We ran 2,000 paired agent-loop trials. Here's what surprised us.</h1><div class=post-description>A pre-registered benchmark of LoopGain vs fixed max_iterations across 2,000 paired trials and six agent frameworks. The headline is 92.8% cheaper — but the interesting findings are the five things that complicate it.</div><div class=post-meta><span title='2026-06-07 09:05:00 -0400 -0400'>June 7, 2026</span>&nbsp;·&nbsp;<span>9 min</span>&nbsp;·&nbsp;<span class=byline>By Dave Fitzsimmons, Founder & CEO @ LoopGain</span></div></header><details class=toc><summary accesskey=c title="(Alt + C)"><span class=title>Table of Contents</span></summary><div class=inner><ul><li><a href=#surprise-1-the-state-we-built-to-catch-oscillation-mostly-catches-stalls aria-label="Surprise 1: the state we built to catch oscillation mostly catches stalls">Surprise 1: the state we built to catch oscillation mostly catches stalls</a></li><li><a href=#surprise-2-the-928-is-loaded-toward-the-easy-cases aria-label="Surprise 2: the 92.8% is loaded toward the easy cases">Surprise 2: the 92.8% is loaded toward the easy cases</a></li><li><a href=#surprise-3-on-normal-workloads-we-mostly-dont-make-answers-better aria-label="Surprise 3: on normal workloads, we mostly don&rsquo;t make answers better">Surprise 3: on normal workloads, we mostly don&rsquo;t make answers better</a></li><li><a href=#surprise-4-we-missed-one-of-our-own-predictions aria-label="Surprise 4: we missed one of our own predictions">Surprise 4: we missed one of our own predictions</a></li><li><a href=#surprise-5-aggressive-stopping-has-a-real-cost-on-retrieval aria-label="Surprise 5: aggressive stopping has a real cost on retrieval">Surprise 5: aggressive stopping has a real cost on retrieval</a></li><li><a href=#why-pre-register-a-benchmark-for-your-own-product aria-label="Why pre-register a benchmark for your own product?">Why pre-register a benchmark for your own product?</a></li></ul></div></details><div class="post-content md-content"><p>We built a benchmark to find out what <a href=https://github.com/loopgain-ai/loopgain>LoopGain</a>
7actually saves on real agent loops, then pre-registered the predictions before we
8looked at the data so we couldn&rsquo;t move the goalposts. It ran 2,000 paired trials —
9each loop run under three fixed <code>max_iterations</code> caps (5, 10, 20) <em>and</em> under
10LoopGain — across six agent frameworks: four conditions × 2,000 trials = 8,000
11total loop runs.</p><p>The headline is a <strong>92.8% reduction in API spend</strong> versus <code>max_iter=20</code>,
12with median wall-clock dropping from 30.9s to 2.1s. That number is real, and it&rsquo;s
13the least interesting thing we found.</p><figure class=chart-figure><svg viewBox="0 0 720 312" width="100%" role="img" aria-labelledby="cost-by-condition.svg-t cost-by-condition.svg-d" font-family="'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" style="max-width:720px"><title id="cost-by-condition.svg-t">Total API spend across 2,000 trials</title><desc id="cost-by-condition.svg-d">Lower is better. LoopGain vs fixed max_iterations caps · 92.8% cheaper than max_iter=20. (source: loopgain-bench RESULTS.md (registered, loopgain 0.4.0))</desc><rect x=".5" y=".5" width="719" height="311" rx="12" fill="#111317" stroke="#23262D"/><text x="28" y="36" fill="#E6E8EC" font-size="19" font-weight="700">Total API spend across 2,000 trials</text><text x="28" y="60" fill="#A4A9B4" font-size="13.5">Lower is better. LoopGain vs fixed max_iterations caps · 92.8% cheaper than max_iter=20.</text><text x="188" y="107.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">max_iter=5</text><rect x="200" y="92" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="92" width="103" height="22" rx="4" fill="#5A6172"/><text x="313" y="107.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">$6.83</text><text x="188" y="149.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">max_iter=10</text><rect x="200" y="134" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="134" width="205" height="22" rx="4" fill="#5A6172"/><text x="415" y="149.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">$13.65</text><text x="188" y="191.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">max_iter=20</text><rect x="200" y="176" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="176" width="406" height="22" rx="4" fill="#5A6172"/><text x="616" y="191.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">$27.05</text><text x="188" y="233.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">LoopGain</text><rect x="200" y="218" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="218" width="29" height="22" rx="4" fill="#F5C451"/><text x="239" y="233.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">$1.94</text><text x="28" y="292" fill="#6B7180" font-size="11">Source: loopgain-bench RESULTS.md (registered, loopgain 0.4.0)</text></svg><figcaption>Total API spend across all 2,000 trials, by stopping condition. The fixed caps climb with their iteration budget;
13 LoopGain stops when the loop is done.</figcaption></figure><p>The interesting findings are the ones that complicate it. Here are five.</p><h2 id=surprise-1-the-state-we-built-to-catch-oscillation-mostly-catches-stalls>Surprise 1: the state we built to catch oscillation mostly catches stalls<a hidden class=anchor aria-hidden=true href=#surprise-1-the-state-we-built-to-catch-oscillation-mostly-catches-stalls>#</a></h2><p>LoopGain classifies a loop into five trajectory states — <code>FAST_CONVERGE</code>,
14<code>CONVERGING</code>, <code>STALLING</code>, <code>OSCILLATING</code>, <code>DIVERGING</code>. We shipped all five. (A
15sixth row below, <code>TARGET_MET</code>, is the separate short-circuit that fires when
16your error signal hits its target — not one of the five trajectory states, but
17it shows up in the emission counts.) Across 2,000 trials and 3,191 state
18emissions, here&rsquo;s how often each actually fired:</p><table><thead><tr><th>State</th><th style=text-align:right>Emissions</th><th style=text-align:right>Share</th></tr></thead><tbody><tr><td><code>TARGET_MET</code></td><td style=text-align:right>1,302</td><td style=text-align:right>40.8%</td></tr><tr><td><code>FAST_CONVERGE</code></td><td style=text-align:right>836</td><td style=text-align:right>26.2%</td></tr><tr><td><code>STALLING</code></td><td style=text-align:right>680</td><td style=text-align:right>21.3%</td></tr><tr><td><code>DIVERGING</code></td><td style=text-align:right>364</td><td style=text-align:right>11.4%</td></tr><tr><td><code>CONVERGING</code></td><td style=text-align:right>8</td><td style=text-align:right>0.25%</td></tr><tr><td><code>OSCILLATING</code></td><td style=text-align:right>1</td><td style=text-align:right>0.03%</td></tr></tbody></table><figure class=chart-figure><svg viewBox="0 0 720 396" width="100%" role="img" aria-labelledby="band-emissions.svg-t band-emissions.svg-d" font-family="'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" style="max-width:720px"><title id="band-emissions.svg-t">How often each state actually fired</title><desc id="band-emissions.svg-d">3,191 state emissions across 2,000 trials. A stuck loop is a stall: STALLING is common; OSCILLATING and CONVERGING are near-theoretical at scale. (source: loopgain-bench RESULTS.md, Finding 3 (registered, loopgain 0.4.0))</desc><rect x=".5" y=".5" width="719" height="395" rx="12" fill="#111317" stroke="#23262D"/><text x="28" y="36" fill="#E6E8EC" font-size="19" font-weight="700">How often each state actually fired</text><text x="28" y="60" fill="#A4A9B4" font-size="13.5">3,191 state emissions across 2,000 trials. A stuck loop is a stall: STALLING is common; OSCILLATING and CONVERGING are near-theoretical at scale.</text><text x="188" y="107.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">TARGET_MET</text><rect x="200" y="92" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="92" width="406" height="22" rx="4" fill="#F5C451"/><text x="616" y="107.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">1,302</text><text x="188" y="149.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">FAST_CONVERGE</text><rect x="200" y="134" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="134" width="261" height="22" rx="4" fill="#F5C451"/><text x="471" y="149.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">836</text><text x="188" y="191.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">STALLING</text><rect x="200" y="176" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="176" width="212" height="22" rx="4" fill="#F5C451"/><text x="422" y="191.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">680</text><text x="188" y="233.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">DIVERGING</text><rect x="200" y="218" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="218" width="114" height="22" rx="4" fill="#F5C451"/><text x="324" y="233.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">364</text><text x="188" y="275.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">CONVERGING</text><rect x="200" y="260" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="260" width="3" height="22" rx="4" fill="#5A6172"/><text x="213" y="275.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">8</text><text x="275" y="275" fill="#6B7180" font-size="11.5" font-style="italic">fired 8×</text><text x="188" y="317.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">OSCILLATING</text><rect x="200" y="302" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="302" width="3" height="22" rx="4" fill="#5A6172"/><text x="213" y="317.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">1</text><text x="275" y="317" fill="#6B7180" font-size="11.5" font-style="italic">fired once</text><text x="28" y="376" fill="#6B7180" font-size="11">Source: loopgain-bench RESULTS.md, Finding 3 (registered, loopgain 0.4.0)</text></svg><figcaption>State emission counts across 2,000 trials. <code>OSCILLATING</code> and <code>CONVERGING</code> are slivers at this scale; <code>STALLING</code>
18 is the third-most-common signal.</figcaption></figure><p>Here is the surprise. We originally built that third band thinking of it as the
19<em>oscillation</em> catcher — the loop bouncing between two near-but-not-equal answers.
20At scale, <code>OSCILLATING</code> fired <strong>once in 2,000 trials</strong>. The band that actually
21does the work is <code>STALLING</code>, with <strong>680 emissions</strong>. The failure mode real agent
22loops produce is not a loop thrashing up and down — it is a loop <strong>getting stuck</strong>,
23error pinned at a constant, grinding out iterations that change nothing.</p><p>That distinction is the whole point of the band, and it&rsquo;s worth being precise
24about: an <em>oscillation</em> is error bouncing between values; a <em>stall</em> is error
25pinned flat. A loop stuck at &ldquo;11 failing tests, 11 failing tests, 11 failing
26tests&rdquo; is stalling, not oscillating — and labeling it correctly is what lets the
27monitor stop it. (An earlier build of the classifier mislabeled flat trajectories
28as <code>OSCILLATING</code>; calling a stuck loop a stall is the more correct reading, and
29it&rsquo;s what shipped.)</p><p><code>CONVERGING</code> fired <strong>eight times</strong> and <code>OSCILLATING</code> <strong>once</strong> — the two states
30that describe gradual grind-down or genuine bistable bouncing are, at this model
31capability and on these workloads, almost theoretical. The reason is a fact about
322026-era models, not about our classifier: on calibrated tasks, LLMs <strong>one-shot
33or they get stuck</strong>. They nail it on iteration one, or they pin at a constant
34error. The smooth textbook convergence trajectory that control theory loves to
35draw is something real agent loops rarely produce.</p><p>The product implication is uncomfortable and we took it: &ldquo;detects all five
36trajectory modes&rdquo; is technically true but oversells what&rsquo;s validated. So we lead
37with what the data actually supports — <strong>catches stalls and divergence, and
38stops them</strong> — the states with 1,000+ real emissions between them, not the ones
39that fired a handful of times.</p><h2 id=surprise-2-the-928-is-loaded-toward-the-easy-cases>Surprise 2: the 92.8% is loaded toward the easy cases<a hidden class=anchor aria-hidden=true href=#surprise-2-the-928-is-loaded-toward-the-easy-cases>#</a></h2><p>A single headline number hides a distribution. Segment the savings by what the
40loop actually did:</p><table><thead><tr><th>Loop outcome</th><th style=text-align:right>n</th><th style=text-align:right>Savings vs <code>max_iter=20</code></th></tr></thead><tbody><tr><td>converged (hit target early)</td><td style=text-align:right>1,302</td><td style=text-align:right><strong>96.6%</strong></td></tr><tr><td>diverged</td><td style=text-align:right>364</td><td style=text-align:right>83.9%</td></tr><tr><td>stalled</td><td style=text-align:right>333</td><td style=text-align:right>78.2%</td></tr></tbody></table><figure class=chart-figure><svg viewBox="0 0 720 270" width="100%" role="img" aria-labelledby="savings-by-outcome.svg-t savings-by-outcome.svg-d" font-family="'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" style="max-width:720px"><title id="savings-by-outcome.svg-t">The 92.8% is loaded toward the easy cases</title><desc id="savings-by-outcome.svg-d">Cost savings vs max_iter=20, split by what the loop actually did. (source: loopgain-bench (computed from by_band.json, registered, loopgain 0.4.0))</desc><rect x=".5" y=".5" width="719" height="269" rx="12" fill="#111317" stroke="#23262D"/><text x="28" y="36" fill="#E6E8EC" font-size="19" font-weight="700">The 92.8% is loaded toward the easy cases</text><text x="28" y="60" fill="#A4A9B4" font-size="13.5">Cost savings vs max_iter=20, split by what the loop actually did.</text><text x="188" y="107.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">converged</text><rect x="200" y="92" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="92" width="392" height="22" rx="4" fill="#F5C451"/><text x="602" y="107.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">96.6%</text><text x="188" y="149.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">diverged</text><rect x="200" y="134" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="134" width="341" height="22" rx="4" fill="#E0AB2A"/><text x="551" y="149.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">83.9%</text><text x="188" y="191.5" text-anchor="end" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">stalled</text><rect x="200" y="176" width="406" height="22" rx="4" fill="#1B1E25"/><rect x="200" y="176" width="317" height="22" rx="4" fill="#E0AB2A"/><text x="527" y="191.5" fill="#E6E8EC" font-size="13.5" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace">78.2%</text><text x="28" y="250" fill="#6B7180" font-size="11">Source: loopgain-bench (computed from by_band.json, registered, loopgain 0.4.0)</text></svg><figcaption>Cost savings vs <code>max_iter=20</code>, split by loop outcome. The 92.8% headline is the blend of a mostly-easy corpus; failure-mode workloads land closer to 78–84%.</figcaption></figure><p>
40LoopGain saves money in every segment — but if your loops usually succeed fast,
41you&rsquo;ll see numbers closer to 96.6%; if you live in adversarial, long-tail
42failure-mode territory, closer to 78–84%. Both are real. &ldquo;92.8%&rdquo; is the blend of a
43mostly-easy corpus, and quoting it without the spread would be the kind of
44benchmark sleight-of-hand we built this to avoid. (Stalled loops save the least —
4578.2% — because a stall takes two consecutive flat readings to confirm before
46LoopGain stops, so it runs a hair longer than a hard divergence. More correct,
47marginally less cheap, and we&rsquo;d rather show it than hide it.)</p><h2 id=surprise-3-on-normal-workloads-we-mostly-dont-make-answers-better>Surprise 3: on normal workloads, we mostly don&rsquo;t make answers <em>better</em><a hidden class=anchor aria-hidden=true href=#surprise-3-on-normal-workloads-we-mostly-dont-make-answers-better>#</a></h2><p>This is the one we most wanted to be different. On natural-distribution workloads
48— ordinary code-gen, debate, planning, RAG — a blind judge preferred LoopGain&rsquo;s
49output to the run-to-cap baseline <strong>50–63%</strong> of the time. That&rsquo;s not &ldquo;better.&rdquo;
50That&rsquo;s <strong>preserved</strong>: LoopGain returns an answer just as good, at roughly 5% of
51the cost.</p><p>LoopGain only <em>improves</em> quality when running longer actively hurts. On workloads
52engineered to degrade — where the model is pushed to keep &ldquo;fixing&rdquo; an already-good
53answer — the judge preferred LoopGain <strong>92–95%</strong> of the time, because best-so-far
54rollback returns the iteration that worked instead of the one that got mangled.</p><p>This isn&rsquo;t a single-trial anecdote — it&rsquo;s a measurable pattern across the whole
55benchmark. Of the fixed-cap (<code>max_iter=20</code>) runs that ran <em>past</em> their best
56iteration, <strong>35.3% shipped a final answer worse than the best they had already
57reached</strong> — a median of 3× the error, up to 11×. The other ~65% merely plateaued,
58burning tokens on an answer that never improved. A fixed cap can&rsquo;t tell &ldquo;done&rdquo;
59from &ldquo;keep going,&rdquo; so about a third of the time it grinds a good answer into a
60worse one. Best-so-far rollback is what turns that around: LoopGain returns the
61iteration that actually worked, not the mangled one the cap stopped on. That&rsquo;s why
62best-so-far rollback — not just early stopping — is the mechanism that protects
63quality.</p><p>So the honest claim is two sentences, not one: <strong>LoopGain preserves quality on
64workloads where the model usually succeeds, at a fraction of the cost. It improves
65quality on workloads where iterating past success degrades the output.</strong> Anyone
66who tells you their loop controller makes every answer better is selling you
67something.</p><h2 id=surprise-4-we-missed-one-of-our-own-predictions>Surprise 4: we missed one of our own predictions<a hidden class=anchor aria-hidden=true href=#surprise-4-we-missed-one-of-our-own-predictions>#</a></h2><p>We pre-registered specific numeric floors before collecting data. We beat most of
68them. One we hit right at the line, and one we missed — and pre-registration is
69the thing that forces us to say so:</p><ul><li><strong>Early-warning lead time.</strong> We predicted LoopGain would flag a diverging loop a
70median of <strong>≥ 3 iterations</strong> before the fixed cap hits its catastrophic point. It
71flagged at a median of <strong>3</strong> — exactly the floor, with no margin. It did flag in
72364 of 367 catastrophe trials, so it almost never <em>misses</em> the divergence; it
73just doesn&rsquo;t give you more warning than we promised. Met, reported flat, not
74rounded up.</li><li><strong>Framework parity.</strong> We predicted ≤ 5 percentage-points of quality spread
75between adapters on the same task. On debate (AutoGen vs CrewAI) we saw <strong>5.5</strong> —
76missed by 0.5 pp. Well under the threshold that would have killed the claim, but
77past what we predicted, so it&rsquo;s in the writeup, not a footnote.</li></ul><p>The parity miss didn&rsquo;t fire a kill criterion. We&rsquo;re reporting it because a careful
78reader finds it anyway, and honest is faster than spin.</p><h2 id=surprise-5-aggressive-stopping-has-a-real-cost-on-retrieval>Surprise 5: aggressive stopping has a real cost on retrieval<a hidden class=anchor aria-hidden=true href=#surprise-5-aggressive-stopping-has-a-real-cost-on-retrieval>#</a></h2><p>On the iterative-RAG cell, LoopGain cut cost <strong>98.1%</strong> — the biggest single-cell
79saving in the bench. It also retrieved the gold document <strong>4.5 percentage points</strong>
80less often than the run-to-cap baseline (85.0% vs 89.5% hit@5). LoopGain&rsquo;s early stop
81occasionally cuts off a retrieval loop that <em>would</em> have found the answer with a
82few more revision attempts.</p><p>That&rsquo;s a genuine trade-off, not a rounding error: on retrieval workloads you can
83buy back some of that 4.5 pp by making LoopGain&rsquo;s stopping more conservative. We
84report the default-config result because shipping the number that flatters you and
85hiding the tuning knob is how benchmarks lose trust.</p><h2 id=why-pre-register-a-benchmark-for-your-own-product>Why pre-register a benchmark for your own product?<a hidden class=anchor aria-hidden=true href=#why-pre-register-a-benchmark-for-your-own-product>#</a></h2><p>Because a benchmark&rsquo;s job isn&rsquo;t to produce the cleanest number — it&rsquo;s to produce
86the truest one, and the only way to keep yourself honest is to write the
87predictions down before you can see the data. We locked the protocol, the
88hypotheses, and six kill criteria before any real cell ran. Zero of the six
89fired — but one predicted floor was missed and another landed right at the line,
90and you just read about both, because the registration made hiding them impossible.</p><p>Everything is public: the <a href=https://github.com/loopgain-ai/loopgain-bench>protocol with its amendments</a>,
91the full raw trial data (every iteration of all 8,000 runs), the per-cell tables, and the
92<a href=https://dashboard.loopgain.ai/benchmark>live dashboard</a> populated with all 2,000
93trials. Reproduce it, argue with it, or run it on your own workloads — the harness
94is in the repo.</p><p>The headline is 92.8% cheaper. The reason to believe it is everything above that
95complicates it.</p><hr><p><em>LoopGain is <code>pip install loopgain</code>, Apache-2.0. The benchmark, protocol, and raw
96data are <a href=https://github.com/loopgain-ai/loopgain-bench>open</a>; the numbers here
97are the registered results from loopgain 0.4.0.</em></p></div><footer class=post-footer><ul class=post-tags><li><a href=https://loopgain.ai/blog/tags/benchmark/>Benchmark</a></li><li><a href=https://loopgain.ai/blog/tags/agent-loops/>Agent-Loops</a></li><li><a href=https://loopgain.ai/blog/tags/loop-gain/>Loop-Gain</a></li><li><a href=https://loopgain.ai/blog/tags/evaluation/>Evaluation</a></li><li><a href=https://loopgain.ai/blog/tags/open-source/>Open-Source</a></li><li><a href=https://loopgain.ai/blog/tags/pre-registration/>Pre-Registration</a></li></ul><nav class=paginav><a class=prev href=https://loopgain.ai/blog/posts/how-to-design-a-strong-verifier/><span class=title>« Prev</span>
98<span>How to design a strong verifier for AI agent loops</span>
99</a><a class=next href=https://loopgain.ai/blog/posts/how-loop-gain-works/><span class=title>Next »</span>
100<span>How loop gain works: knowing when an AI agent loop has converged</span></a></nav></footer><div class=lg-giscus id=lg-comments style="margin-top:3rem;padding-top:1.5rem;border-top:1px solid var(--border)"></div>
100<script>(function(){var e,t,s="https://loopgain.ai/blog/giscus-loopgain-dark.css",o="https://loopgain.ai/blog/giscus-loopgain-light.css";function n(){return document.documentElement.getAttribute("data-theme")==="light"?o:s}if(t=document.getElementById("lg-comments"),!t)return;e=document.createElement("script"),e.src="https://giscus.app/client.js",e.setAttribute("data-repo","loopgain-ai/blog-comments"),e.setAttribute("data-repo-id","R_kgDOS4so8g"),e.setAttribute("data-category","Announcements"),e.setAttribute("data-category-id","DIC_kwDOS4so8s4C_BjL"),e.setAttribute("data-mapping","pathname"),e.setAttribute("data-strict","0"),e.setAttribute("data-reactions-enabled","1"),e.setAttribute("data-emit-metadata","0"),e.setAttribute("data-input-position","bottom"),e.setAttribute("data-theme",n()),e.setAttribute("data-lang","en"),e.setAttribute("data-loading","lazy"),e.crossOrigin="anonymous",e.async=!0,t.appendChild(e);function i(){var e=document.querySelector("iframe.giscus-frame");if(!e)return;e.contentWindow.postMessage({giscus:{setConfig:{theme:n()}}},"https://giscus.app")}new MutationObserver(i).observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]})})()</script>
100</article></main><footer class=footer><span>&copy; 2026 <a href=https://loopgain.ai/blog/>LoopGain Blog</a></span> ·
101<span>Powered by
102<a href="https://gohugo.io/?utm_source=papermod" rel=noopener target=_blank>Hugo</a> &
103<a href=https://github.com/adityatelange/hugo-PaperMod/ rel=noopener target=_blank>PaperMod</a></span></footer><a href=#top id=top-link class="top-link hidden" aria-label="go to top" title="Go to Top (Alt + G)" accesskey=g><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevrons-up"><polyline points="17 11 12 6 7 11"/><polyline points="17 18 12 13 7 18"/></svg></a><section class=lg-subscribe aria-labelledby=lg-sub-h><div class=lg-subscribe-inner><h2 id=lg-sub-h class=lg-sub-title>Get new posts by email</h2><p class=lg-sub-copy>New writing on agent-loop control, benchmarks, and what we keep learning the hard way. At most one email per post.</p><form id=lg-sub-form class=lg-sub-form novalidate><input id=lg-sub-email name=email type=email required autocomplete=email [email protected] aria-label="Email address" class=lg-sub-input>
104<input type=text name=company_website tabindex=-1 autocomplete=off aria-hidden=true class=lg-sub-hp><div class=cf-turnstile data-sitekey=0x4AAAAAADiBMO_v3Ti_3EcA data-appearance=interaction-only data-theme=auto></div><button type=submit class=lg-sub-btn>Subscribe</button></form><p class=lg-sub-status id=lg-sub-status role=status aria-live=polite></p><p class=lg-sub-fine>Double opt-in — we'll email you to confirm. Unsubscribe any time.</p></div></section>
104<script>(function(){var e,t=document.getElementById("lg-sub-form");if(!t)return;e=document.getElementById("lg-sub-status"),t.addEventListener("submit",function(n){n.preventDefault();var s,i,o=new FormData(t);if(o.get("company_website"))return;if(i=(o.get("email")||"").toString().trim(),!i)return;s=t.querySelector(".lg-sub-btn"),e.className="lg-sub-status",e.textContent="Sending…",s.disabled=!0,fetch("/api/subscribe",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({email:i,list:"blog",source:"blog:footer",consent:!0,company_website:"",cf_turnstile_response:(o.get("cf-turnstile-response")||"").toString()})}).then(function(e){return e.json().then(function(t){return{ok:e.ok&&t.ok,d:t}})}).then(function(t){t.ok?(e.className="lg-sub-status is-ok",e.textContent=t.d.message||"You're on the list.",s.textContent="Done ✓"):(e.className="lg-sub-status is-err",e.textContent=t.d&&t.d.error||"Something went wrong — try again, or email [email protected].",s.disabled=!1,window.turnstile&&turnstile.reset())}).catch(function(){e.className="lg-sub-status is-err",e.textContent="Network error — try again, or email [email protected].",s.disabled=!1,window.turnstile&&turnstile.reset()})})})()</script>
104<script src=https://challenges.cloudflare.com/turnstile/v0/api.js async defer></script>
104<script>let menu=document.getElementById("menu");if(menu){const e=localStorage.getItem("menu-scroll-position");e&&(menu.scrollLeft=parseInt(e,10)),menu.onscroll=function(){localStorage.setItem("menu-scroll-position",menu.scrollLeft)}}document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",function(e){e.preventDefault();var t=this.getAttribute("href").substr(1);window.matchMedia("(prefers-reduced-motion: reduce)").matches?document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView():document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView({behavior:"smooth"}),t==="top"?history.replaceState(null,null," "):history.pushState(null,null,`#${t}`)})})</script>
104<script>var toplink=document.getElementById("top-link");window.onscroll=function(){const e=window.innerHeight;document.body.scrollTop>e||document.documentElement.scrollTop>e?toplink.classList.remove("hidden"):toplink.classList.add("hidden")}</script>
104<script>document.getElementById("theme-toggle").addEventListener("click",()=>{const e=document.querySelector("html");e.dataset.theme==="dark"?(e.dataset.theme="light",localStorage.setItem("pref-theme","light")):(e.dataset.theme="dark",localStorage.setItem("pref-theme","dark"))})</script>
104<script>document.querySelectorAll("pre > code").forEach(e=>{const n=e.parentNode.parentNode,t=document.createElement("button");t.classList.add("copy-code"),t.innerHTML="copy";function s(){t.innerHTML="copied!",setTimeout(()=>{t.innerHTML="copy"},2e3)}t.addEventListener("click",t=>{if("clipboard"in navigator){navigator.clipboard.writeText(e.textContent),s();return}const n=document.createRange();n.selectNodeContents(e);const o=window.getSelection();o.removeAllRanges(),o.addRange(n);try{document.execCommand("copy"),s()}catch{}o.removeRange(n)}),n.classList.contains("highlight")?n.appendChild(t):n.parentNode.firstChild==n||(e.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName=="TABLE"?e.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(t):e.parentNode.appendChild(t))})</script>
104<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a45c6920d85b8459',t:'MTc5MTIwMzE0Mw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
104<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"2aad9ae387504776abe1d5183c0b5f5c","r":1,"spa":2}' crossorigin="anonymous"></script>
104
105</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.