Skip to content
HN On Hacker News ↗

GitHub - loopgain-ai/loopgain: An open-source cost controller for AI agent loops — stops a loop when it's actually converged and rolls back before it degrades, instead of running to a fixed max_iterations cap. Real-time loop-gain (Aβ) bands + best-so-far rollback. Adapters for LangGraph, CrewAI, AutoGen, LangChain, OpenAI Agents, and Claude Agent SDK; raw API for custom stacks.

▲ 31 points • 14 comments • by fitz2882 • 4mo ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully AI-generated

99 %

AI likelihood · overall

AI
0% human-written 100% AI-generated
SEGMENTS · HUMAN 0 of 6
SEGMENTS · AI 6 of 6
WORD COUNT 1,774
PEAK AI % 99% · §3
Analyzed
Jul 19
backend: pangram/v3.3
Segments scanned
6 windows
avg 296 words each
Distribution
0 / 100%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,774 words · 6 segments analyzed

Human AI-generated
§1 AI · 99%

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 spend than max_iter=20 — $27.05 → $1.94 in total benchmark spend ~15× faster — median wall-clock per trial 30.9s → 2.1s Quality 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 comparisons Zero 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 →

Home: 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.

Why 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).

§2 AI · 99%

Install pip install loopgain Pure Python, no dependencies, supports Python 3.10+. Using Claude Code? The 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

Usage Three lines of code wrap any iterative loop with a measurable error signal: 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.

Defining your error signal 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).

§3 AI · 99%

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.

How it works 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).

§4 AI · 99%

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.

Best-so-far rollback 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.

See it across a fleet (optional dashboard) 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:

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.

§5 AI · 99%

Repositories

Repo What it is

loopgain This library — the Apache-2.0 control loop (you are here)

telemetry-receiver Cloudflare Worker that ingests anonymized loop telemetry

dashboard The fleet dashboard — self-hostable

loopgain-bench The reproducible 2,000-trial benchmark behind the numbers above

loopgain-plugin Claude Code plugin — scans a repo for wrappable loops, proposes reviewed diffs

What LoopGain does and doesn't guarantee LoopGain saves money by stopping a loop once it stops improving — fewer iterations, fewer tokens. In our public benchmark, 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.

§6 AI · 99%

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. How to design a strong verifier is a field guide to exactly this.

API reference 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.

lg.observe(errors, output=None) -> str Record this iteration's errors and optional output.