Core concepts
The mental model behind Guard, traces, steps, the Guard instance and fail-open.
The Guard instance
A Guard represents one agent. Create one instance per agent_id and reuse it across your app. Guard is a singleton per agent_id: creating it again with the same agent_id returns the existing instance and ignores the new arguments.
guard = Guard(api_key="...", agent_id="support-agent")Besides api_key and agent_id, Guard accepts:
| Argument | Purpose |
|---|---|
env | "production" (default) or "development" |
api_url | Override the server address, for example for an on-premise deployment |
fail_open | True (default): Guard problems never crash your agent. See Fail-open |
scrub_fields | Metadata field names to redact before anything is sent, for example ["credit_card", "password"] |
tags | Labels stamped on every trace, for example {"env": "production"} |
Traces and steps
A trace is one run of your agent: one call to a function decorated with @guard.track. A trace contains steps, the individual things that happened during the run. Steps are nested, so each one knows which step it ran inside.
Every step has an action type:
| Action type | What it represents |
|---|---|
llm_call | A call to any LLM, with model, tokens and cost |
tool_call | The agent calling an external tool or function |
http_request | An outbound HTTP request |
agent_call | A parent agent calling a sub-agent |
graph_node | A LangGraph node running |
retriever_call | A retriever lookup (RAG or otherwise) |
circuit_breaker | A circuit breaker decision, logged automatically |
content_safety | A content-safety scan, logged automatically |
custom | Anything else you track with @guard.track |
If a tracked function calls another tracked function, the inner call becomes its own trace, linked to the outer one. It is not a step inside the outer trace.
The @guard.track decorator
@guard.track wraps a function and turns each call into a trace. It works on sync and async functions, in two forms:
@guard.track # bare
def run(question): ...
@guard.track(action_type=ActionType.LLM_CALL) # with options
def run(question): ...The options are action_type, metadata (extra fields stored on the step) and tags.
Guard also stores a short input summary (the function's arguments) and output summary (its return value). Both are scrubbed of PII and cut to 500 characters. A function with no arguments has an empty input summary, which is expected.
Fail-open
Guard never takes your agent down. fail_open is a single setting on Guard, and it defaults to True. If the Guard service is unreachable, slow, or returns an error, the SDK logs a warning and your function continues as if Guard weren't there.
Set fail_open=False if you would rather raise an error whenever Guard can't be reached. A wrong API key always raises, in either mode.
The same setting decides what happens to actions protected by rules and approvals. With fail_open=True, an action is allowed if Guard cannot check it, so a risky action can run with no approval. For agents that handle risky actions, set fail_open=False.
Cross-agent escalation
When one agent hands work to another (for example a support agent escalating to a specialist), call the second agent's tracked function from inside the first agent's tracked function. Guard links the two traces automatically. You never set a parent id yourself, and each agent keeps its own Guard and agent_id.