Show HN: LoopGain – Stop agent loops with control theory, not max_iterations LoopGain, an open-source Python library, replaces fixed max_iterations in AI agent loops with a control-theoretic stop-and-rollback policy based on the Barkhausen criterion, reducing API spend by 92.8% and median runtime by ~15× while preserving output quality in benchmarks across 2,000 trials. An open-source cost controller for AI agent loops. AI agent loops waste time and money when they don't know when to stop. LoopGain measures the loop in real time and stops it the moment it has actually converged — and rolls back before it degrades — instead of running to a fixed max iterations cap. Benchmark — 2,000 paired trials across 10 workload cells run it yourself : 92.8% less API spendthan max iter=20 — $27.05 → $1.94 in total benchmark spend~15× faster— median wall-clock per trial 30.9s → 2.1sQuality preserved, not traded for speed— judge win-rate 0.50–0.63 on natural-distribution workloads W1–W4, CI excluding null on most cells , 0.92–0.95 on engineered-failure workloads W5 ; 0.678 weighted preference across 1,800 judge comparisonsZero of six kill criteria fired all six pre-registered with thresholds before the run Honest limits, up front: LoopGain detects convergence, not correctness — it knows when more iterations won't help, not whether the answer is right, and it's only as good as the verifier behind your error signal. The full list of what it can't do → what-loopgain-does-and-doesnt-guarantee Home: loopgain.ai https://loopgain.ai Works for any iterative AI workflow with a measurable error signal — verify-revise loops, refinement passes, tool-use retry chains, RAG with self-correction, code-gen with linter feedback, multi-step reasoning loops. Pre-built adapters for LangGraph, CrewAI, AutoGen, LangChain, OpenAI Agents SDK, and Claude Agent SDK ; drop-in via the raw API for any custom stack. Pure Python, no runtime dependencies. Production agent loops universally use max iterations=N as their termination policy. It's the embarrassing default of agentic AI: you either waste compute loop stops too late or ship bad output loop stops too early . LoopGain replaces it with a control-theoretic stop-and-rollback policy grounded in the Barkhausen criterion — a foundational result from electrical-engineering feedback-oscillator analysis 1921 . pip install loopgain Pure Python, no dependencies, supports Python 3.10+. Using Claude Code? The loopgain-plugin https://github.com/loopgain-ai/loopgain-plugin scans your whole repo for wrappable loops — literal, recursive, graph-cycle, and semantic — and proposes reviewed diffs one file at a time never auto-applied : /plugin marketplace add loopgain-ai/loopgain-plugin /plugin install loopgain Three lines of code wrap any iterative loop with a measurable error signal: python from loopgain import LoopGain lg = LoopGain target error=0.1 while lg.should continue : errors = verifier.verify output lg.observe errors, output=output output = reviser.revise output, errors result = lg.result print result.outcome "converged" | "oscillating" | "diverged" | "stalled" | "max iterations" print result.best output the lowest-error iteration's output print result.iterations used print result.savings vs fixed cap observe accepts either a numeric error magnitude or any sequence whose length becomes the magnitude . Pass output=... to enable the best-so-far buffer. The one thing you provide is the error signal : a single non-negative number, every iteration, that says how wrong the current output is. Lower is better; zero means done. LoopGain doesn't know what your loop does — it just watches that number's trajectory and decides whether to keep going, stop, or roll back. Your loop already has some way of knowing the output isn't good yet or it wouldn't keep revising . Turn that into a number: | Loop | Error signal = | |---|---| | Agentic coding write code → run tests | number of failing tests 10 → 3 → 0 | | JSON / structured extraction | number of schema violations | | RAG with self-correction | number of required facts still missing | | Self-refinement with an LLM judge | judge's gap to target e.g. 10 − quality score | | Lint / format loop | lint error count | The only rules: non-negative, and smaller as the output gets better . Returning the raw list of problems works directly — observe uses its length as the magnitude e.g. hand it the list of failing tests . If your quality is fuzzy and has no natural "zero," run with target error=None : LoopGain then stops when the number stops improving , wherever that plateau is, instead of waiting for an exact target. Every stop/continue decision is made from this one number, so LoopGain is only as good as the error signal you give it — pick one that genuinely tracks output quality. LoopGain measures empirical loop gain Aβ = E n / E n-1 at every iteration and exposes it as a smoothed time series for visualization. The decision engine, however, classifies the full error trajectory using four features: E ratio = E current / E first cumulative reduction slope log = OLS slope of log10 E geometric trend direction slope p = t-test p-value of slope statistical significance osc std = std of detrended log10 E oscillation magnitude It routes the trajectory into one of five named states: | State | Condition | Action | |---|---|---| FAST CONVERGE | cumulative reduction to ≤ 10% of E first | Continue | CONVERGING | negative slope with p < 0.05 , OR cumulative ≤ 50% | Continue, watch for upward drift | STALLING | no significant slope, no detectable oscillation | Stop after 2 consecutive readings — return best-so-far | OSCILLATING | high residual variance with flat trend | Stop — return best-so-far | DIVERGING | positive slope with p < 0.05 AND cumulative 110% | Abort — roll back to best-so-far | Plus a short-circuit: if observed error drops at or below target error , the loop stops immediately with state TARGET MET . The default target error=0.0 short-circuits on exactly zero error — the natural completion signal for verifier-driven loops. Pass target error=None to disable the short-circuit and rely on stability detection alone. The decision is conservative by design : requiring both statistical significance and meaningful cumulative motion before terminating prevents false-positive aborts on noisy real-LLM error series. Validated at 98.8% macro-averaged accuracy across 5 regimes on N=1000 deterministic-mock trajectories see RESULTS v2 classifier.md . The STALLING ceiling of ~94% is the t-test's irreducible 5% type-I error rate, not a classifier weakness. Recommended minimum: 6 iterations for reliable trend significance. At n≤4 the t-test is severely underpowered df=2 requires |t| 4.3 for p<0.05 — the classifier conservatively falls back to STALLING when evidence is thin. The thresholds are derived analytically control theory + statistical convention , not fitted; tune them per domain via the TrajectoryThresholds argument once you have production traces. Legacy single-feature classifier: the original v0.1 single-Aβ-band classifier thresholds 0.3 / 0.85 / 0.95 / 1.05 is still available via LoopGain classifier='legacy bands' for callers that have empirically tuned the bands to a specific workload. LoopGain keeps a buffer of all observed outputs paired with their error scores. On termination it returns argmin error , not the last iteration: | Terminal state | Returned output | |---|---| TARGET MET | Current output by definition, the best | OSCILLATING | Lowest-error iteration in the buffer | DIVERGING | Lowest-error iteration which is not the last one | This transforms divergence detection from "abort with garbage" into "abort with the best you've seen so far" — a free quality floor. The library is the whole product locally — telemetry is opt-in and self-hostable. If you want a fleet view of every loop's stability, cost, and rollbacks across a team, there's a hosted dashboard fed by the telemetry receiver https://github.com/loopgain-ai/telemetry-receiver : Open the live demo → — no signup, real benchmark data. The receiver and dashboard are both open-source — self-host to keep telemetry entirely under your control. | Repo | What it is | |---|---| loopgain | telemetry-receiver dashboard loopgain-bench loopgain-plugin LoopGain saves money by stopping a loop once it stops improving — fewer iterations, fewer tokens. In our public benchmark https://github.com/loopgain-ai/loopgain-bench , that was a 92.8% cut in total API spend vs max iterations=20 , with output quality preserved. Two honest limits: Savings depend on your workload. Loops that usually succeed fast save the most ~96% ; adversarial, failure-prone loops save less ~78–84% . The headline is a blend — run the benchmark on your own loops before quoting a number. LoopGain detects convergence, not correctness. It stops when your error signal stops improving — which means more iterations won't help, not that the loop succeeded. On the benchmark this preserved quality it rarely stopped early on a worse output; false-stop rate ≤4.5% , but a loop can stall with the error still above zero — a plateau at, say, 2 failing tests. So check result.best error or your own pass/fail before you trust the output: if it plateaued short of your target, that's a quality gap LoopGain can't see, and a false stop that forces a rerun is the one way it eats into the savings. LoopGain decides when to stop ; you decide whether the answer is good enough . LoopGain is only as right as your verifier. It acts on the error signal you give it. If your verifier reports zero errors, LoopGain trusts that and stops — so a verifier with blind spots can report success on an answer that is still wrong, and LoopGain will confidently stop there. This is not the plateau case above: the error reads zero and the loop looks like a clean success, so neither LoopGain nor its convergence signal can flag it. The quality of the stop is bounded by the quality of the check behind your error signal. We measured this on the benchmark's code-gen workload: 4.5% of converged runs 16/355 passed every check the loop ran but failed the full held-out test suite — and that's a floor, not a ceiling, because the in-loop verifier there was strong; a weaker verifier exposes more. Distinct from the ≤4.5% false-stop rate above — the numbers coincide, the failure modes don't. Pair LoopGain with the strongest verifier you can afford at the stop — executable tests over a sampled subset, a schema or type check over a vibe, a held-out check the loop didn't optimize against.is a field guide to exactly this. How to design a strong verifier https://loopgain.ai/blog/posts/how-to-design-a-strong-verifier/ LoopGain target error=0.0, max iterations=50, thresholds=None, trajectory thresholds=None, classifier='trajectory', smoothing window=3, assumed fixed cap=10 Construct the monitor. target error — Stop when an observed error drops at or below this. Default 0.0 short-circuits on exactly zero error the natural completion signal for verifier-driven loops . Pass None to disable the short-circuit entirely. max iterations — Hard safety backstop. Default 50 so the loop can never run unbounded; a stability verdict normally terminates it well before this. Pass None to opt into a fully unbounded loop only safe if your loop is guaranteed to reach target error or a stop-state , or a smaller integer to cap tighter. thresholds — Custom ThresholdBands for the legacy single-Aβ-band classifier. Ignored when classifier='trajectory' . trajectory thresholds — Custom TrajectoryThresholds for the multi-feature classifier the default . Override only with workload-specific evidence. classifier — 'trajectory' default, v0.2 multi-feature classifier or 'legacy bands' v0.1 single-Aβ-band classifier . smoothing window — EMA window for the smoothed Aβ series always maintained for visualization, regardless of classifier choice . Default 3. assumed fixed cap — Used to compute savings vs fixed cap . Default 10. Record this iteration's errors and optional output. Returns the current state name. errors accepts a number used directly or any sequence length used as magnitude . Returns False once a terminal state fires. Current state name. One of INIT , FAST CONVERGE , CONVERGING , STALLING , OSCILLATING , DIVERGING , TARGET MET , MAX ITERATIONS . The corresponding terminal result.outcome values are converged , oscillating , diverged , stalled v0.2 trajectory mode only — STALLING terminating after 2 consecutive readings , max iterations , or in progress . Terminal result with outcome , iterations used , best index , best output , best error , convergence profile , error history , savings vs fixed cap . Safe to call mid-loop. lg.send telemetry endpoint=None, token=None, workload id=None, timeout=2.0, allow insecure=False, framework=None, loop type=None, team=None, include per iteration=True, retries=2, retry backoff=0.25, actual dollars spent=None, actual dollars saved=None - bool Opt-in. Send a single anonymized telemetry POST after the loop terminates. Best-effort — never raises, returns True on 2xx, False otherwise. Adapters auto-stamp framework ; loop type and team are free-form labels that surface as filters in the dashboard. Pass include per iteration=False to send aggregate summary only. endpoint and token are optional v0.6.3+ : with LOOPGAIN TELEMETRY ENDPOINT and LOOPGAIN TELEMETRY TOKEN exported, a bare lg.send telemetry is fully configured — the endpoint may be the receiver base URL https://telemetry.loopgain.ai or the full /v1/aggregate path. Nothing configured → returns False , sends nothing. actual dollars spent and actual dollars saved are optional real-cost fields v0.6.1+ . Populate them only when you have a genuinely measured dollar figure — summed real API usage x list price, or an actually-executed paired-baseline comparison run. Never a formula-derived estimate. When populated, the dashboard displays your real number instead of its iter-count x $/iter extrapolation; passing an estimate through this field would present it as ground truth to every consumer of your tenant's data, not just you. python from loopgain import LoopGain lg = LoopGain target error=0.1 ... run the loop ... lg.send telemetry workload id="my-rag-pipeline" endpoint/token from env v0.6.3+ Verify the pipeline before wiring a real loop — loopgain doctor runs a tiny in-process loop no model calls, $0 and sends one test event: export LOOPGAIN TELEMETRY ENDPOINT="https://telemetry.loopgain.ai" export LOOPGAIN TELEMETRY TOKEN="lgk ..." loopgain doctor - event accepted by the receiver - appears in your dashboard as 'loopgain-doctor' Recommended setup: store the token outside source. Two clean options: Option A: environment variable simplest export LOOPGAIN TELEMETRY ENDPOINT="https://telemetry.loopgain.ai/v1/aggregate" export LOOPGAIN TELEMETRY TOKEN="lgk ..." add to ~/.zshrc or ~/.bashrc Option B: macOS Keychain more secure pip install keyring python3 -c "import keyring; keyring.set password 'loopgain', 'telemetry', input 'Token: ' " Then in code: keyring.get password 'loopgain', 'telemetry' What is sent: state transitions, Aβ summary min/max/median , rollback flag, iterations used, savings, library version, optional opaque workload id , threshold config, hour-bucketed timestamp — and, unless you pass include per iteration=False , a length-capped per-iteration trajectory smoothed Aβ values and numeric error magnitudes; this is what drives the dashboard's convergence-profile scrubbing . What is NEVER sent: prompts, completions, error contents, the output buffer, or any customer identity beyond the bearer token. Numeric error magnitudes are sent they're the loop-gain signal ; error contents never are. Privacy contract is enforced by the payload-shape unit tests in tests/test telemetry.py . The hosted endpoint at telemetry.loopgain.ai is one acceptable destination. The receiver https://github.com/loopgain-ai/telemetry-receiver and dashboard https://github.com/loopgain-ai/dashboard are both open-source — self-host to keep telemetry fully under your control. This is not the same as anonymous usage telemetry. send telemetry sendsyourloop data toyourdashboard, and only when you call it. There's a separate, opt-infunneltelemetry described below. The two never share data or code. LoopGain can report anonymous usage counts so a solo maintainer can tell whether the library is actually being used — install → first observe → recurring use. It is opt-in and default-decline: nothing is sent unless you explicitly turn it on. loopgain telemetry --show status + exactly what would be sent loopgain telemetry --enable opt in or: export LOOPGAIN TELEMETRY=1 loopgain telemetry --disable opt out or: export LOOPGAIN TELEMETRY=0 DO NOT TRACK=1 is honored as a hard opt-out, and CI environments are auto-detected and declined silently. When enabled, payloads carry only a locally-generated random id not derived from your machine , hour-bucketed timestamps, library/Python/OS versions, the adapter in use, and a coarse outcome count. Prompts, outputs, error contents, keys, paths, and IPs are never collected. Delivery is batched, async, https-only, and fail-silent — it can never break your loop. Full details and the privacy contract: TELEMETRY.md . If LoopGain is useful to you, opting in is the cheapest way to support the project — these counts are the only signal a solo-maintained library has that it's working for anyone. loopgain --version or: loopgain version loopgain telemetry --show inspect / control anonymous funnel telemetry python -m loopgain telemetry --show equivalent, without the console script Thin wrappers under loopgain.integrations drive each major agent framework's iteration with a LoopGain monitor and auto-stamp framework="