tool-loop-guard
Detect when an LLM agent gets stuck calling the same tool with the same args. One dep (serde_json). One struct.
use json;
use ;
let mut guard = with_capacity;
for step in agent_loop
Why
The most common agent failure isn't a runtime error. It's a search_web("anthropic prompt cache") that returns nothing useful, an agent that decides to "try the same search one more time," and the loop running until your wallet notices.
tool-loop-guard is a sliding-window detector that returns an error when the same (tool_name, args) tuple shows up more than threshold times within the last window calls. That's the entire library.
A circuit breaker won't catch this (no errors). A deadline won't catch it (each call is fast). A budget will eventually catch it (after blowing through cost). The guard catches it on the third repeat.
Install
[]
= "0.1"
= "1"
API
use json;
use LoopGuard;
let mut guard = with_capacity;
guard.record?; // Err on the offending call
let _ = guard.would_raise; // peek without mutating
guard.reset; // clear the buffer
let _len = guard.len; // calls currently buffered
let _keys = guard.recent_keys; // borrowed slice, oldest first
# Ok::
record returns Err(LoopDetectedError { tool_name, tool_args, count, window, threshold }) so you can route on the structured fields rather than parsing a message.
The default key is (tool_name, canonical_json(args)). Argument object keys are sorted recursively so {"a":1,"b":2} and {"b":2,"a":1} collide. To ignore noisy fields (request_id, timestamp) before comparison, pass your own key function:
use ;
use ;
let stable_key = ;
let mut guard = with_key_fn;
Construction errors
LoopGuard::with_capacity panics on invalid input because misconfiguring a guard is a programming bug, not a runtime condition. Use LoopGuard::try_new if you want a Result<Self, ConfigError> instead:
use ;
assert!;
Validation rules: window >= 2, threshold >= 2, threshold <= window.
Companion libraries
llm-circuit-breaker— opens on provider errors, not on agent confusion.llm-retry— exponential backoff retry. Pair with this guard: retry handles transient errors, guard catches "looping fast."token-budget-pool— shared token/USD budget. Catches looping eventually; this guard catches it sooner.
License
MIT OR Apache-2.0