LoopGuard
Progress-aware runtime guardrails for LangGraph and LangChain agents.
LoopGuard watches agent runs and decides whether the agent should continue, warn, replan, pause, or stop. It does not stop an agent just because a tool repeats. It combines repeated or cyclic actions with stagnant results, repeated failures, and lack of progress to detect when the agent is actually stuck.
same tool repeated + changing results -> warn/continue
same tool repeated + same empty result -> stop
search -> summarize -> search + no progress -> stop
temporary failure then success -> continue
repeated permanent failure -> stop
Install
LoopGuard is published on PyPI as loopguard-runtime:
pip install loopguard-runtime
Or pin the current release:
pip install loopguard-runtime==0.2.1
The package name is loopguard-runtime, but the Python import is loopguard:
from loopguard import LoopGuardCallbackHandler
For LangGraph/LangChain callback dependencies:
pip install "loopguard-runtime[langgraph]"
For the full demo stack from GitHub source:
pip install "loopguard-runtime[demo,server] @ git+https://github.com/mahimathacker/loopguard.git"
Quickstart
Attach LoopGuard to a LangGraph/LangChain run with the callback handler:
from loopguard import LoopGuardCallbackHandler, LoopGuardInterrupt
handler = LoopGuardCallbackHandler()
try:
result = agent.invoke(my_input, config={"callbacks": [handler]})
except LoopGuardInterrupt as exc:
print("LoopGuard stopped the run:", exc)
print(handler.report())
The report includes:
events: normalized tool calls and observationssignals: detector evidence such as repeated calls or no progressdecisions: policy actions such as continue, warn, replan, pause, or stopmetrics: step counts, tool calls, repeat rate, and common actionsalerts: legacy compatibility alerts
To observe without stopping the run:
from loopguard import LoopGuardCallbackHandler
handler = LoopGuardCallbackHandler(interrupt_on_fatal=False)
result = agent.invoke(my_input, config={"callbacks": [handler]})
for decision in handler.report()["decisions"]:
print(decision["action"], decision["risk_score"])
Why LoopGuard
LLM agents run in a loop: think, act, observe, repeat. Sometimes that loop goes wrong and the agent keeps spending steps without getting closer to the answer. Normal tracing can show what happened, but it often leaves the developer to decide whether the run is still useful.
LoopGuard stays narrow: it turns the trace into stuck-risk decisions. It complements broad observability platforms rather than replacing them.
How It Works
Agent event -> Tracer -> Detectors -> DetectionSignals -> PolicyEngine -> GuardDecision
| Part | Job | File |
|---|---|---|
| Tracer | Records each agent event. | loopguard/tracer.py |
| Metrics | Counts steps, tool calls, repeats, and common actions. | loopguard/metrics.py |
| Detectors | Turn events into scored evidence signals. | loopguard/detectors.py, loopguard/signals.py |
| Policy | Combines signals into continue/warn/replan/pause/stop decisions. | loopguard/policy.py, loopguard/monitor.py |
Current detectors/signals:
LoopDetector: repeated normalized tool callsSemanticLoopDetector: repeated intent in different words, using embeddingsStallDetectorandProgressDetector: stagnant observations and no progressRepeatedFailureDetector: retryable vs likely permanent failuresCycleDetector: repeating action sequencesHandoffLoopDetector: closed multi-agent handoff cycles- Step/tool-call budget detectors
Detectors do not make the final execution decision alone. Repetition can warn; repetition plus stagnant output can stop.
Check Saved Traces
LoopGuard can analyze exported JSON traces without running the original agent:
python -m loopguard.ingest examples/sample_trace.json
python -m loopguard.ingest examples/sample_trace.json --json
loopguard-ingest examples/sample_trace.json --json
Use check mode for local scripts or CI-style pass/fail behavior:
python -m loopguard.check examples/sample_trace.json
python -m loopguard.check examples/sample_trace.json --fail-on stalled
python -m loopguard.check examples/sample_trace.json --max-steps 20 --max-tool-calls 10
python -m loopguard.check examples/sample_trace.json --config loopguard.yml
python -m loopguard.check examples/sample_trace.json --fail-on looping --fail-on stalled --json
loopguard-check examples/sample_trace.json --json
By default, only looping fails the check. Use --fail-on stalled to fail on no-progress
warnings too, or --fail-on alerts to fail on any alert. Step and tool-call budgets
always fail the check when exceeded.
The trace adapter is forgiving about field names such as tool, tool_name, name,
args, arguments, input, output, result, agent, and caller.
Configuration
Keep local/live policy in a small loopguard.yml:
live:
max_steps: 40
max_tool_calls: 15
exact_threshold: 3
exact_window: 12
stall_patience: 4
stall_fatal: false
handoff_repeats: 2
handoff_window: 16
handoff_max_cycle_length: 5
handoff_fatal: true
semantic: false
check:
fail_on:
- looping
- stalled
max_steps: 40
max_tool_calls: 15
For check-only config, top-level keys also work:
fail_on:
- looping
- stalled
max_steps: 40
max_tool_calls: 15
Use config with the callback handler:
from loopguard import LoopGuardCallbackHandler
handler = LoopGuardCallbackHandler.from_config("loopguard.yml")
agent.invoke(my_input, config={"callbacks": [handler]})
Set semantic: true to enable paraphrase-loop detection with embeddings. That path uses
OpenAI embeddings and requires OPENAI_API_KEY.
Local Demos
The repo includes a FastAPI/WebSocket backend, a Next.js + React Flow UI, and four demo scenarios:
exact: scripted identical tool loop, offlinesemantic: scripted paraphrase loop, requires embeddingscalc: realgpt-4o-miniagent that finishestrap: realgpt-4o-miniagent given an impossible goal
Screenshots:
Run the local demo stack:
git clone https://github.com/mahimathacker/loopguard.git
cd loopguard
python3.11 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
Set OPENAI_API_KEY in .env for semantic and real-agent scenarios.
Terminal 1:
uvicorn server:app --reload --port 8000
Terminal 2:
cd ui
npm install
npm run dev
Open http://localhost:3000.
Command-line demos:
python main.py # exact, offline
python main.py semantic # semantic loop
python main.py calc # real agent, finishes
python main.py trap # real agent, loops and gets caught
Evaluation And Tests
The core test suite is offline and uses fake agents/fake embeddings:
python -m unittest discover -v
The v0.2 fixture in examples/v02_labeled_traces.json covers healthy polling,
pagination, retryable recovery, permanent failures, exact loops, alternating cycles, and
multi-agent handoff cycles.
The eval harness reports detector precision/recall/F1:
python -m loopguard.evals
Package Status
loopguard-runtime v0.2.1 is published on PyPI:
https://pypi.org/project/loopguard-runtime/0.2.1/
The package builds cleanly with python -m build, passes twine check, and installs with:
pip install loopguard-runtime
Roadmap
LoopGuard is not trying to become a general tracing, dataset, eval, or monitoring platform. The next work is narrow:
- Tune thresholds against real production traces.
- Add clearer examples for healthy retries, polling, pagination, and cyclic failures.
- Add GitHub check annotations for saved-trace failures.
- Explore LangGraph.js/LangChain.js support later.
Project Structure
agent-loop/
loopguard/
tracer.py records events
metrics.py derives trace metrics
signals.py DetectionSignal, GuardDecision, GuardAction
policy.py combines signals into runtime decisions
detectors.py loop, progress, failure, cycle, budget, and handoff detectors
monitor.py records events, collects signals, and stores decisions
guard.py public LoopGuard wrapper for live runs
langgraph.py callback handler for LangGraph/LangChain live runs
embeddings.py OpenAI embeddings for semantic detection
runner.py stream_run helper
ingest.py offline trace analyzer
check.py saved-trace check command
evals.py detector evaluation harness
examples/
server.py
main.py
ui/
requirements.txt
pyproject.toml
Metadata
Release files for loopguard-runtime 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| loopguard_runtime-0.2.1.tar.gz | 41.2 kB | Without provenance | Details for loopguard_runtime-0.2.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| loopguard_runtime-0.2.1-py3-none-any.whl | Python 3 | none | any | Details for loopguard_runtime-0.2.1-py3-none-any.whl |
Total release size: 81.0 kB
Release files / loopguard_runtime-0.2.1.tar.gz
| Download URL | loopguard_runtime-0.2.1.tar.gz |
|---|---|
| Size | 41.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
623691a403f48efe7b68f6260b2564479c17c596e41424c98fa4d8cc147b93dc
|
|
BLAKE2b-256 checksum How to use checksums |
a6464b42979146dcb48fcda220849712c484e12e8eeade133a13400eb1998e90
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Release files / loopguard_runtime-0.2.1-py3-none-any.whl
| Download URL | loopguard_runtime-0.2.1-py3-none-any.whl |
|---|---|
| Size | 39.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a5b269ef28284c64dd047d2b4b0d50a1a29809e7060af3e8ae130f481043790a
|
|
BLAKE2b-256 checksum How to use checksums |
32c55cd750bf90a6f39afd5a3828e66584e51d828ab98a81771b6bbd5463e116
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Security for loopguard-runtime 0.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.