Ahmed Doghri Logo Image
Ahmed Doghri

agentbudget

Your agent does not crash when it fails. It looks busy forever, retrying the same broken call like insisting harder will change the API, while every hard limit sits comfortably unbroken. This is the guardrail that actually notices.

agentbudget, an agent loop and budget guardrail

The Failure That Looks Exactly Like Progress

An agentic loop, plan, call a tool, observe, repeat, has no natural stopping point, kind of like a group chat that refuses to die. Give it a task it cannot quite finish and it will not crash. It will do something worse: it will look busy forever. It retries the same failing call. It bounces between two states like a revolving door. It cycles through three approaches without ever admitting none of them are working, and your step counter and your cost counter both sit comfortably under their limits the whole time, because a stall is not a runaway. It is a very well-behaved way to set money on fire.

agentbudget catches the well-behaved failure too. Hard limits (steps, dollars, wall-clock) stop the obvious runaway. Loop detection catches the sneaky one: it hashes every action-and-observation pair and flags the moment the same transition repeats too many times in a short window, whether or not any hard ceiling was ever close.

One Small Algorithm, No Tuning Curve

Hash the last handful of steps into (action, observation) signatures. If any signature repeats three or more times inside the window, the agent is stuck. No embeddings, no similarity threshold, just exact repeats of the same action producing the same result, because an agent that is truly stuck usually repeats itself in ways that are easy to catch exactly.

The Number

On five simulated agent traces, three genuinely stalled and two that actually complete the task, limits-only catches zero of the three stalls, because none of them ever cross a hard ceiling. Loop detection catches all three, usually within a handful of steps, and it never once false-positives on the two traces that finish the job.

The Loop Detector Has Its Own Well-Behaved Blind Spot

Exact-match hashing catches an agent that repeats itself byte for byte. It has nothing to say about an agent stuck on an API that never repeats itself byte for byte, which is most real APIs. A rate-limit error commonly embeds a retry-after countdown, a request ID, a timestamp, something that changes on every single call even while the underlying failure stays exactly the same.

I fed the guardrail six calls to the same broken endpoint, "error: rate limited, retry after 1247ms," then 983ms, then 1502ms, and so on. Six different observation strings. It ran all six steps and never once flagged a loop, the exact well-behaved failure this tool exists to catch, invisible to it because of one countdown nobody thought to normalize.

The fix cannot be "strip every digit." Some digits in an observation are the actual progress signal, "5 tests failed" moving to "3 tests failed" moving to "0 tests failed" is real convergence, and blindly collapsing those numbers would turn genuine progress into a false loop alarm on an agent that is doing exactly the right thing. So the canonicalization step only neutralizes tokens that structurally look volatile: durations like "1247ms," ISO-8601 timestamps, UUIDs, and long hex-looking request IDs. A bare count sitting next to an ordinary word is left completely alone, and I verified directly that a genuinely converging trace never trips either version of the guardrail.

0% recall became 100% on three adversarial volatile-field patterns and a two-stall holdout evaluated exactly once, with zero regression on the original five-trace benchmark. The original exact-match guardrail is untouched, so its published numbers still reproduce exactly; the fix ships as an explicit opt-in.

26 tests pass in CI across Python 3.9, 3.11, and 3.13.

Tools Used

Python
Agent Guardrails
Loop Detection
Adversarial Testing
pytest
Ruff