Error is shrinking by more than 3× per iteration. The loop is smashing it; the only correct move is to stay out of the way.
Stop guessing
max_iterations.
Every agent in production ships with a number somebody made up. LoopGain replaces it with the loop gain Aβ — a real-time Barkhausen-stability measurement — and tells you the iteration to stop on. It's the math from 1921. We're applying it to the loop you wrote last week.
pip install loopgain
copy
Every agent in production is running on a guess.
Search any agent codebase for max_iterations. You'll find 5.
Sometimes 10. Nobody can defend the number, because no one has a principled way to pick it.
It's the universal pre-crash hack. It fires too early on a loop that was three iterations from converging, or too late on one that started diverging in iteration two — the model is now five rewrites deep into a hallucination and the bill is real.
we just set it to five and hope.
three failure modes a static cap can't tell apart · what Aβ tells you in one number
Five named bands. One number per iteration. A decision either way.
Each iteration produces an error signal. The ratio of consecutive errors is the loop gain Aβ. Smooth it over a small window and you get a stability reading. Five bands. Two of them say "stop." Three say "keep going." No more guessing.
Healthy progress. Error contracts by a meaningful fraction every step. Most well-tuned agent loops live here.
The loop is moving, barely. Each iteration is shaving fractions off the error. Almost always a sign you're at a plateau that won't break on its own.
The model is undoing its own last revision. The next iteration is statistically guaranteed to be no better than two ago. Rollback. Stop.
Each iteration is making the output strictly worse. The longer it runs, the more money you light on fire. Rollback. Stop.
The whole library is a few hundred lines around this ratio. The result is a stop decision your model never had to make.
Three lines, then your loop knows when to stop.
The raw API is two methods: observe() and should_continue(). Drop them around the loop you already have.
Framework adapters wrap the same primitive — pick the one that matches your stack, or stay raw.
# amber stripe = the lines you add. everything else is your existing loop.
from loopgain import LoopGainlg = LoopGain(target_error=0.1, max_iterations=20)
while lg.should_continue():
errors = verifier.verify(output)
lg.observe(errors, output=output) output = reviser.revise(output, errors)
result = lg.result
# result.outcome → "converged" · "oscillating" · "diverged" · "max_iterations"
# result.best_output → argmin(E(n)) — the actual best draft, not the last
# lg.eta → log(ε_target / ε) / log(Aβ_smooth) — iterations to ε (live)
# result.gain_margin → 1 / max(Aβ_smooth) — > 1 means stable headroom
# amber stripe = the lines you add. pip install 'loopgain[langgraph]'
from loopgain import LoopGainfrom loopgain.integrations import LangGraphAdapter
graph = build_verify_revise_graph().compile()
lg = LoopGain(target_error=0.1, max_iterations=20)adapter = LangGraphAdapter(
lg=lg,
error_fn=lambda update: len(update.get("verifier", {}).get("errors", [])),
)final_state = adapter.run(graph, {"draft": initial})
# adapter.stream() yields each step if you want the full trace.
# adapter.arun() / adapter.astream() are the async counterparts.
# amber stripe = the lines you add. pip install 'loopgain[crewai]'
from loopgain import LoopGainfrom loopgain.integrations import CrewAIAdapter
crew = Crew(agents=[writer_agent, verifier_agent], tasks=[task])
lg = LoopGain(target_error=0.1, max_iterations=20)adapter = CrewAIAdapter(
lg=lg,
task_error_fn=lambda task_output: count_failed_checks(task_output.raw),
)with adapter: # installs callbacks; uninstalls on exit adapter.install(crew) result = crew.kickoff()
# Observations land on `lg.result` — same shape as the raw API.
# Existing callbacks you had installed are chained, not clobbered.
# amber stripe = the lines you add. pip install 'loopgain[autogen]'
from autogen_agentchat.teams import RoundRobinGroupChat
from loopgain import LoopGainfrom loopgain.integrations import AutoGenAdapter
team = RoundRobinGroupChat(participants=[generator, verifier])
lg = LoopGain(target_error=0.1, max_iterations=20)adapter = AutoGenAdapter(
lg=lg,
error_fn=lambda msg: parse_verifier_score(msg.content),
observe_sources={"verifier"}, # only verifier drives observe()
)result = await adapter.run(team, task="draft, verify, revise")
# Legacy v0.2 ConversableAgent.initiate_chat is not supported.
A small library that does one thing precisely.
Five-band decision engine
One smoothed reading per iteration. Three bands say go, two bands say stop. The library never asks you to interpret a number — the band is the decision.
Best-so-far buffer
Every iteration's output is held in a ring buffer keyed by error. On rollback you don't get the latest draft — you get argmin(E(n)), the actually-best one the loop produced.
Closed-form ETA
If you're CONVERGING and the gain is stable, the number of iterations to your target ε is a logarithm, not a guess. log(ε_t / ε) / log(Aβ). Displayed live.
Framework adapters
LangGraph conditional edge. CrewAI callback. AutoGen termination check. Raw LoopGain class if you have your own runner. All four wrap the same core.
Opt-in telemetry
Off by default. If you turn it on, we receive band transitions and gain readings — never your prompts or outputs. The contract is in the README and the receiver is open-source.
Optional adapter installs
The core wheel has zero runtime deps. Framework adapters are pip extras — pip install 'loopgain[langgraph]', [crewai], [autogen], or [all]. Your service tree stays clean.
The same readings, on a screen built for an operations room.
Six panels over your real fleet. Loop Health Map, Convergence Profiles, Waste Report, Gain Margin Distribution, Rollback Log, ETA Accuracy. Alerts on band transitions. A live demo runs against synthetic traffic so you can poke at it without integrating first.
Free if you self-host. Paid when you'd rather not.
Apache-2.0 across the stack — library, receiver, dashboard. Self-host the whole thing if you want; the code is there. The Team and Enterprise tiers are what you buy when you'd rather we run it: history, alerts, per-loop calibration, and the on-call pager.
Open Source
- Full LoopGain library — zero runtime deps
- Framework adapters (LangGraph, CrewAI, AutoGen)
- Best-so-far buffer + rollback
- Closed-form ETA + first-prediction capture
- Self-host telemetry receiver + dashboard
pip install loopgain
copy
Team
- Everything in Open Source
- Hosted dashboard — no infra, no auth, no patches
- 30-day run history, retained for you
- Alerts delivered to Slack, email, or webhooks
- Waste Report — dollar-accurate ROI for stakeholders
- Per-iteration scrubber + share links
paid plans launching soon — join the waitlist
Enterprise
- Everything in Team
- Unlimited history
- Custom Aβ thresholds per loop type
- Read & ingest API
- SSO (SAML / OIDC)
- Audit log + evidence pack for SOC 2 prep
- Dedicated support channel
One hundred and five years of control theory, finally pointed at the right loop.
The Barkhausen criterion is the foundational stability result in feedback engineering. It says: if the loop gain is greater than one, your system oscillates or diverges. If it's less than one, it converges. Every amplifier, every PID controller, every closed-loop electronic circuit since 1921 has used this idea.
An LLM agent loop is a feedback loop. Output in, scored output out, fed back as the next input. The math doesn't care that the gain element is a transformer instead of a vacuum tube. Apply Barkhausen and you get the same answer you'd get in any other control system: this loop is stable, this one isn't, stop the unstable one before it costs you anything else.