Guardrails and Policy Enforcement for OpenAI Agents
You built the agent with the OpenAI Agents SDK. Traccia shows whether your guardrails actually fired — and enforces runtime policy before irreversible actions run.
The Gap After You Ship
The OpenAI Agents SDK gives you a solid starting point: input and output guardrails that trip a wire and halt the run, tool calls with structured spans, and multi-agent handoffs.
Two production questions still land on your team:
- Did the guardrails actually run — and did any of them fire?
- Can this agent still act when spend, error rate, or org policy says it should not?
Traccia answers both from the same instrumentation path. Guardrail detection classifies what your traces show about controls on each run. Policy enforcement with @govern hard-blocks or pauses execution before a governed function body runs.
Info
@govern is enforcement at the agent boundary. They complement each other; neither replaces the other.Instrument Once
Call init() at app startup. When openai-agents is installed, Traccia registers as the Agents SDK TracingProcessor and traces agent runs, tool calls, handoffs, LLM generations, and guardrail spans — no extra wiring.
from traccia import initfrom agents import Agent, Runner, input_guardrail, GuardrailFunctionOutput
init() # OpenAI Agents SDK tracing is on automatically
@input_guardrailasync def scope_check(ctx, agent, input_data): # your OpenAI Agents SDK guardrail return GuardrailFunctionOutput( output_info={"allowed": True}, tripwire_triggered=False, )
agent = Agent( name="Assistant", instructions="Help the user with their request.", input_guardrails=[scope_check],)
result = await Runner.run(agent, user_message)# Full trace: agent spans, guardrail spans, LLM cost — already flowingFrom here, guardrail findings and summaries are built automatically as spans end. See the OpenAI Agents SDK integration guide for the full trace hierarchy.
Guardrail Detection
Traccia's guardrail engine runs as an OTel span processor. After each span ends, it inspects attributes and emits structured GuardrailFinding objects. When the root span closes, it writes a guardrail.summary to the trace: detected categories, triggered categories, missing categories, and coverage confidence.
Three detection tiers:
| Tier | Source | What it means |
|---|---|---|
| A — Explicit | Your annotated checks or OpenAI Agents SDK guardrail spans | High confidence. Proves a named guardrail ran and whether it fired. |
| B — Provider-native | LLM signals like content_filter finish reasons | Captured automatically from model responses. |
| C — Heuristic | Denial keywords in tool error messages | Low confidence. Surfaced for investigation, not counted as coverage. |
Detection is passive. It does not block traffic or rewrite output. It gives you an audit trail on the same spans you already use for debugging.
OpenAI SDK Guardrails, Captured Automatically
This is the piece that matters most for OpenAI Agents SDK users: Traccia captures SDK input and output guardrails as Tier A explicit findings when the integration is active. You do not need to wrap each guardrail in guardrail_span() or add @observe(as_type="guardrail") — the SDK guardrail spans are already there.
from traccia import initfrom agents import Agent, Runner, input_guardrail, output_guardrail, GuardrailFunctionOutput
init()
@input_guardrailasync def safety_check(ctx, agent, input_data): is_unsafe = "jailbreak" in str(input_data).lower() return GuardrailFunctionOutput( output_info={"reason": "injection pattern"}, tripwire_triggered=is_unsafe, )
@output_guardrailasync def output_check(ctx, agent, output): text = str(output) # example: block if the model returns empty or obviously invalid text is_invalid = len(text.strip()) == 0 return GuardrailFunctionOutput( output_info={"violations": ["empty_output"] if is_invalid else []}, tripwire_triggered=is_invalid, )
agent = Agent( name="Assistant", instructions="Help the user. Stay within product scope.", input_guardrails=[safety_check], output_guardrails=[output_check],)
await Runner.run(agent, user_message)
# On the root span you get:# guardrail.summary.detected_categories — e.g. input_validation, output_validation# guardrail.summary.triggered_categories — whichever tripwires fired# guardrail.findings — structured Tier A entries per guardrail spanEach SDK guardrail run becomes an agent.guardrail.{name} span in the trace. Traccia reads agent.guardrail.triggered and maps it into findings your team can query, alert on, and export — without building a separate logging pipeline.
Tip
guardrail_span() or @observe(as_type="guardrail"). See Guardrail Detection in the SDK docs.Guardrail Posture in the Dashboard
Detection data surfaces as Guardrail Posture in the Traccia dashboard — per trace and rolled up per agent over your selected time window.
On a single trace you see:
- Coverage confidence — how reliable the detection signal is for that run
- Findings — each guardrail name, status, confidence, and source
- Triggered — categories where a control actually fired
- Missing — categories expected for this kind of agent but not observed in the trace
On the agent overview, Posture rolls up triggered and missing categories across recent runs so you spot drift — for example, an agent that suddenly stops recording output guardrails after a deploy.
Warning
Missing Categories
When the root span ends, Traccia infers what the agent did — called LLMs, handled user text, used tools — and flags guardrail categories that should be present but were not detected.
| Agent does this | Traccia expects |
|---|---|
| Calls an LLM with prompt data | input_validation, prompt_injection |
| Produces user-facing text | output_validation, moderation |
| Uses tools | tool_permission |
If an agent has input guardrails but no output guardrail, the summary will call that out on every run. That is useful during code review and incident response: you can see the gap in the trace, not just in a spreadsheet.
For internal-only or batch agents where certain categories do not apply, suppress missing warnings per agent in the dashboard or via span attributes. Triggered findings are never hidden.
Policy Enforcement with @govern
Guardrail detection tells you what happened on a run. @govern decides whether the next invocation is allowed — before the function body executes.
Wrap functions with real side effects — anything that writes, deletes, or spends. Platform policies (spend cap, retry protection, duration and token limits, error rate) evaluate against live agent status. Modes:
- Flag — log and continue
- Soft block — pause for human approval in the Governance Hub
- Hard block — raise
AgentBlockedError; the function never runs
from traccia import init, governfrom traccia.governance import AgentBlockedError
init(api_key="...", endpoint="https://api.traccia.ai/v2/traces")
@govern(agent_id="my-agent", fail_open=False)def run_side_effect(payload: dict) -> str: return execute_action(payload)
try: run_side_effect(payload)except AgentBlockedError: return "Agent blocked by policy — spend, status, or limit."OpenAI Agents SDK guardrails halt a run when a tripwire fires. @govern sits on the functions that create blast radius — so a spend-capped or disabled agent cannot reach those side effects even if an earlier agent step completed successfully.
Tip
fail_open=False for irreversible actions. If the status API is unreachable, the governed function does not run.A Production Pattern
A simple stack that works for most OpenAI Agents SDK apps:
- Agent run — OpenAI Agents SDK with input/output guardrails; Traccia captures every tripwire on the trace
- Review when needed — human approval for irreversible or high-risk steps
- Side-effect function —
@governhard block; only runs when policy allows
Guardrails protect the run. Humans and @govern protect the actions that cannot be undone. Traccia holds proof that controls fired and enforces policy before the next invoke.
| Step | Control | Traccia role |
|---|---|---|
| User input | OpenAI SDK input guardrail | Tier A finding on trace |
| Agent run | SDK tools + LLM | Full agent trace + cost |
| Final output | OpenAI SDK output guardrail | Triggered category if tripwire fired |
| Side effect | @govern + platform policy | Hard block before invoke |
One init(). One trace stream. Posture for what fired. Enforcement for what may run next.
The Bottom Line
If your agent is built with the OpenAI Agents SDK, Traccia already sees guardrail spans when you call init(). You get structured findings, missing-category coverage, and Guardrail Posture in the dashboard — without a separate audit pipeline.
Add @govern on the functions that matter — anything with real side effects — and platform policies gate the agent as a production unit, not just a single run.
Build the guardrails in the SDK. Prove they fired in Traccia. Enforce what the agent is allowed to do next.
Further Reading
Instrument your OpenAI Agents SDK app
Call init() once — guardrail detection on every run, @govern when you are ready to enforce.