savi-loop-guard
A zero-dependency Python library that detects when an AI agent is stuck in a loop: calling the same tool over and over, or firing calls far faster than any real workflow would. Drop it into your own agent code. No account, no API key, no network call, ever.
Built by SAVI as a standalone, dependency-free package, so you can detect these patterns in your own agent code without an account, an API key, or a dependency on SAVI's platform.
Install
pip install savi-loop-guard
No dependencies. Nothing else gets installed alongside it.
Quick start
Record each call as your agent makes it, then check for loops whenever you want (after every call, on a timer, whatever fits your loop):
from loop_guard import LoopGuard, CallEvent
from datetime import datetime, timezone
guard = LoopGuard()
guard.record(CallEvent(
span_id="call_1",
agent_id="doc-extractor",
timestamp=datetime.now(timezone.utc),
tool_call="search_web",
))
issues = guard.check()
for issue in issues:
print(issue["type"], issue["agent_id"])
An issue looks like:
{"type": LoopType.VELOCITY, "agent_id": "doc-extractor", "elapsed_s": 12.4, "call_count": 6}
# or
{"type": LoopType.STRUCTURAL, "agent_id": "doc-extractor",
"tool_call": "search_web", "tool_variants": ["search_web", "search_web_v2"], "call_count": 7}
Prevent the call instead of just observing it
check_before_call() records the event and raises immediately if it would
trip a threshold, for callers who want to stop the loop rather than find
out about it afterwards:
from loop_guard import LoopGuard, CallEvent, LoopDetected
guard = LoopGuard()
try:
guard.check_before_call(event)
except LoopDetected as e:
print(f"Blocked: {e.loop_type}") # "velocity_loop" or "structural_loop"
print(e.details) # the same dict check() would have returned
What it detects
Two independent checks:
- Velocity loop: more than 5 calls from the same
agent_idwithin a 30-second window. - Structural loop: the same tool called more than 5 times, with fuzzy
matching so a broken agent can't dodge detection by alternating between
near-identical tool names (
search_webvssearch_web_v2vsweb_searchcount as the same tool if their name tokens overlap enough).
All four numbers are configurable:
guard = LoopGuard(
velocity_window_seconds=30,
velocity_call_limit=5,
structural_call_limit=5,
tool_fuzzy_similarity_threshold=0.70,
)
How this compares
A few other standalone Python packages exist for this: agent-loop-detector,
agent-loop-guard, agentguard-kit. All of them, like savi-loop-guard's
record()/check() API, are post-hoc/observational; they analyze calls
after they happen. savi-loop-guard adds check_before_call() on top for
callers who want to prevent the call rather than just observe it, which none
of those currently offer.
Known limitations
LoopGuard keeps every recorded event in memory for the life of the
instance; nothing is ever pruned automatically. That's deliberate:
structural-loop detection is a total call count with no time bound by
design (a tool called 6 times over 3 hours is still a loop, not just a
tool called 6 times in 30 seconds), so silently dropping "old" events
would blind it to exactly the slow, steady loops it exists to catch.
In practice this means: for a short-lived task, a single LoopGuard()
is fine as-is. For a long-running process, create a fresh LoopGuard()
per logical unit of work (e.g. per agent run) rather than holding one
open indefinitely, so memory doesn't grow without bound.
License
MIT. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file savi_loop_guard-0.1.0.tar.gz.
File metadata
- Download URL: savi_loop_guard-0.1.0.tar.gz
- Upload date:
- Size: 10.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c07b36cbc2f3141b6b6da021fd32c27ac0d2159db523fdb55c8c2d5486bd6c65
|
|
| MD5 |
0e492efddcf86466b7ca1b519fb17f66
|
|
| BLAKE2b-256 |
4bcc6d4692f1fb80f708dfb11822be7ae2827424e76a7f419205e11deb7805be
|
Provenance
The following attestation bundles were made for savi_loop_guard-0.1.0.tar.gz:
Publisher:
publish-loop-guard.yml on data-gras/savi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
savi_loop_guard-0.1.0.tar.gz -
Subject digest:
c07b36cbc2f3141b6b6da021fd32c27ac0d2159db523fdb55c8c2d5486bd6c65 - Sigstore transparency entry: 2807097759
- Sigstore integration time:
-
Permalink:
data-gras/savi@46923c6829002d9617d93e85e5a14e62490afa22 -
Branch / Tag:
refs/tags/loop-guard-v0.1.0 - Owner: https://github.com/data-gras
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-loop-guard.yml@46923c6829002d9617d93e85e5a14e62490afa22 -
Trigger Event:
push
-
Statement type:
File details
Details for the file savi_loop_guard-0.1.0-py3-none-any.whl.
File metadata
- Download URL: savi_loop_guard-0.1.0-py3-none-any.whl
- Upload date:
- Size: 7.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4bdc3a817fc9e24c13fb137c6d8615659b75f2e0d79c1ee43d1ee46ae7334bc3
|
|
| MD5 |
9418059b8a74200dd826051a384bb695
|
|
| BLAKE2b-256 |
7571c6765a040ab6f318cf693ea47c428db12b224e13bf16ad8c1d0464fa772b
|
Provenance
The following attestation bundles were made for savi_loop_guard-0.1.0-py3-none-any.whl:
Publisher:
publish-loop-guard.yml on data-gras/savi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
savi_loop_guard-0.1.0-py3-none-any.whl -
Subject digest:
4bdc3a817fc9e24c13fb137c6d8615659b75f2e0d79c1ee43d1ee46ae7334bc3 - Sigstore transparency entry: 2807097812
- Sigstore integration time:
-
Permalink:
data-gras/savi@46923c6829002d9617d93e85e5a14e62490afa22 -
Branch / Tag:
refs/tags/loop-guard-v0.1.0 - Owner: https://github.com/data-gras
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-loop-guard.yml@46923c6829002d9617d93e85e5a14e62490afa22 -
Trigger Event:
push
-
Statement type: