Observed since 20 Oct, 22:06 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Production-grade fault tolerance for AI agentsidempotency, loop detection, fallback chains, async support, health monitoring, and budget enforcement for LangChain, AutoGen, CrewAI, and any LLM pipeline
agentguard is a production-ready Python library that adds circuit breakers, LLM-aware retry logic, idempotency, loop detection, and timeout enforcement to any AI agent or LLM pipeline.
AI agents fail at 91%+ rates in production. agentguard stops that.
AI agents built with LangChain, AutoGen, CrewAI, or custom LLM pipelines fail catastrophically in production due to:
Existing tools like tenacity, LangGraph, and CrewAI are not LLM-aware and don't address agent-specific failure modes.
pip install agentguard
from agentguard import GuardedAgent
agent = GuardedAgent(
name="my_llm_agent",
max_retries=3,
circuit_threshold=5,
timeout=30.0,
loop_detection=True,
max_repeated_actions=3,
)
def call_llm(prompt: str) -> str:
# Your actual LLM call here (OpenAI, Anthropic, etc.)
return f"Response to: {prompt}"
result = agent.run(call_llm, "What is the capital of France?", action_label="llm_call")
print(result)
print(agent.get_stats())
# {'name': 'my_llm_agent', 'total_calls': 1, 'total_failures': 0, 'circuit': {...}}
from agentguard import CircuitBreaker
cb = CircuitBreaker(failure_threshold=3, recovery_timeout=60.0, name="openai")
try:
response = cb.call(call_llm, "Hello")
except Exception as e:
print(f"Protected from cascading failure: {e}")
print(cb.get_stats())
# {'name': 'openai', 'state': 'closed', 'failure_count': 0, ...}
from agentguard import llm_retry
@llm_retry(max_attempts=3)
def my_agent_step(query: str) -> str:
# Automatically retries on rate limits (429), provider outages (503)
# Stops immediately on token limit errors (non-retryable)
return call_llm(query)
result = my_agent_step("Summarize this document")
from agentguard import LLMRetry, FailureClassifier
retry = LLMRetry(max_attempts=5, on_retry=lambda attempt, ftype, err: print(f"Retry {attempt}: {ftype}"))
result = retry.execute(call_llm, "Hello")
from agentguard import IdempotentAgent
agent = IdempotentAgent(ttl=3600.0)
# First call executes the function
result1 = agent.run(call_llm, "Summarize report", idempotency_key="report-summary-v1")
# Second call with same key returns cached result instantly
result2 = agent.run(call_llm, "Summarize report", idempotency_key="report-summary-v1")
assert result1 == result2 # True — function only ran once
from agentguard import FailureClassifier, FailureType
fc = FailureClassifier()
err = Exception("429 Too Many Requests: rate limit exceeded")
ftype = fc.classify(err)
# FailureType.RATE_LIMIT
print(fc.is_retryable(ftype)) # True
print(fc.get_retry_delay(ftype, attempt=1)) # 10.0 seconds
agentguard/
├── agent_wrapper.py # GuardedAgent — orchestrates all protections
├── circuit_breaker.py # CircuitBreaker — CLOSED/OPEN/HALF_OPEN state machine
├── retry.py # LLMRetry + llm_retry decorator — intelligent backoff
├── idempotency.py # IdempotentAgent + IdempotencyStore — result caching
├── failure_classifier.py # FailureClassifier — LLM error pattern recognition
└── exceptions.py # Typed exception hierarchy
Agent call
│
├─► Loop Detector (infinite loop check)
│
├─► Circuit Breaker (OPEN? → reject immediately)
│
├─► Idempotency Store (seen this key? → return cached)
│
├─► LLM Retry (classify failure → smart backoff → retry)
│
└─► Timeout Thread (hard deadline enforcement)
| Error Pattern | Classified As | Retryable | Backoff |
|---|---|---|---|
429, rate limit, quota exceeded | RATE_LIMIT | Yes | Exponential (5s base, max 60s) |
503, service unavailable, overloaded | PROVIDER_OUTAGE | Yes | Exponential (10s base, max 120s) |
context length, token limit | TOKEN_LIMIT | No | — |
tool not found, invalid tool | HALLUCINATED_TOOL_CALL | No | — |
| anything else | UNKNOWN | Yes | Exponential (1s base, max 30s) |
GuardedAgentGuardedAgent(
name: str = "agent",
max_retries: int = 3,
circuit_threshold: int = 5,
circuit_recovery: float = 60.0,
timeout: Optional[float] = None,
loop_detection: bool = True,
max_repeated_actions: int = 3,
idempotency_ttl: float = 3600.0,
enable_idempotency: bool = True,
)
Methods:
run(func, *args, action_label=None, idempotency_key=None, **kwargs) — Execute with all protectionsget_stats() — Returns dict with call counts, failure counts, circuit statereset_loop_detector() — Clear the loop detection historyCircuitBreakerCircuitBreaker(
failure_threshold: int = 5,
recovery_timeout: float = 60.0,
half_open_max_calls: int = 1,
name: str = "default"
)
Methods:
call(func, *args, **kwargs) — Protected function callget_stats() — Returns state, failure count, last failure timeLLMRetryLLMRetry(
max_attempts: int = 3,
classifier: Optional[FailureClassifier] = None,
on_retry: Optional[Callable] = None,
)
Methods:
execute(func, *args, **kwargs) — Execute with retry logicllm_retry decorator@llm_retry(max_attempts=3, classifier=None)
def my_func(): ...
IdempotentAgentIdempotentAgent(store=None, ttl=3600.0)
Methods:
run(func, *args, idempotency_key=None, **kwargs) — Execute with deduplicationFailureClassifierMethods:
classify(error: Exception) -> FailureTypeis_retryable(failure_type: FailureType) -> boolget_retry_delay(failure_type: FailureType, attempt: int) -> float| Exception | When Raised |
|---|---|
AgentGuardError | Base class for all agentguard errors |
CircuitOpenError | Circuit breaker is OPEN, call rejected |
MaxRetriesExceededError | All retry attempts exhausted |
IdempotencyError | Idempotency store conflict |
AgentTimeoutError | Agent exceeded timeout limit |
import openai
from agentguard import GuardedAgent
agent = GuardedAgent(name="openai-agent", max_retries=3, timeout=30.0)
def gpt_call(prompt: str) -> str:
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
result = agent.run(gpt_call, "Explain quantum computing", action_label="gpt_call")
from agentguard import llm_retry
@llm_retry(max_attempts=3)
def run_chain(input_text: str):
return my_langchain_chain.invoke({"input": input_text})
from agentguard import GuardedAgent
guard = GuardedAgent(name="crew-agent", loop_detection=True, max_repeated_actions=5)
# Wrap any crew task execution
result = guard.run(crew.kickoff, action_label="crew_task")
Contributions are welcome! Here's how to get started:
git checkout -b feature/your-featurepytest tests/ -vPlease open an issue first for major changes to discuss the approach.
Mahesh Makvana — GitHub · PyPI
MIT License
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.2.13 | 17 May 2026 | 0 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 22:06 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:1670d6a754cb5f54c7decfa64150157655d90e62baba85f10b3e76851c2239ff