ActGuard validates agent behavior over time.
Your code validates what is being done. ActGuard validates whether it should be done, given how the agent got there.
Traditional checks handle one call at a time. Agent failures often happen across calls: wrong IDs carried between steps, retry loops, and budget drift over a run.
| Concern | Solve with |
|---|---|
| Is the input valid? | if/else |
| Is the caller authorized? | RBAC / Auth |
| Is the amount within limits? | Business logic |
| Did the agent hallucinate the ID? | ActGuard |
| Did required steps happen before this action? | ActGuard |
| Is the agent retrying in a loop? | ActGuard |
| Is the run within cost budget? | ActGuard |
import openai
from actguard import Client
from actguard.exceptions import (
ActGuardPaymentRequired,
BudgetExceededError,
BudgetTransportError,
)
ag = Client(
api_key="ag_live_agent_key",
gateway_url="https://api.actguard.ai",
)
oai = openai.OpenAI()
try:
with ag.run(user_id="alice"):
with ag.budget_guard(token_limit=50_000) as guard:
oai.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Summarize this ticket thread."}],
)
except BudgetExceededError:
pass
except ActGuardPaymentRequired:
pass
except BudgetTransportError:
passThis protects you from runaway model usage when an agent keeps exploring, retrying, or over-calling models. Budget scopes reserve on entry and settle on exit, so they require a configured Client with gateway credentials.
The hosted ActGuard gateway uses https://api.actguard.ai:
from actguard import Client
ag = Client(
api_key="ag_live_agent_key",
gateway_url="https://api.actguard.ai",
)If you run your own gateway, pass that base URL instead.
import actguard
from actguard.exceptions import PolicyViolationError
@actguard.prove(kind="order_id", extract="id")
def list_orders(user_id: str) -> list[dict]:
return [{"id": "o1"}]
@actguard.enforce([actguard.RequireFact("order_id", "order_id")])
def cancel_order(order_id: str) -> str:
return f"cancelled:{order_id}"
try:
with actguard.session("req-123", {"user_id": "alice"}):
list_orders("alice")
cancel_order("o1")
except PolicyViolationError as e:
hint_for_llm = e.to_prompt()This blocks actions that look valid by input but are invalid for the session journey.
After budget and workflow-integrity controls, these decorators cover common runtime guardrails:
rate_limit: cap call volume in a time windowcircuit_breaker: stop hammering unhealthy dependenciesmax_attempts: cap retries per runtimeout: bound wall-clock execution timeidempotent: deduplicate side-effectful operationstool(...): compose multiple guards in one declaration
import actguard
@actguard.tool(
idempotent={"ttl_s": 600, "on_duplicate": "return"},
max_attempts={"calls": 3},
rate_limit={"max_calls": 10, "period": 60, "scope": "user_id"},
circuit_breaker={"name": "search_api", "max_fails": 3, "reset_timeout": 60},
timeout=2.0,
)
def search_web(user_id: str, query: str, *, idempotency_key: str) -> str:
...
client = actguard.Client.from_env()
with client.run(user_id="alice"):
search_web("alice", "latest earnings", idempotency_key="req-1")pip install actguardactguard/
├── docs/
├── examples/
└── libs/
├── sdk-py/
└── sdk-js/
See libs/sdk-py/ for Python SDK setup, tests, and lint commands.