Home/Blog/Guardrails and Policy Enforcement for OpenAI Agents
Engineering Blog10 min readJuly 17, 2026

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

Guardrail detection is posture — proof on the span. @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.

python
from traccia import init
from agents import Agent, Runner, input_guardrail, GuardrailFunctionOutput
init() # OpenAI Agents SDK tracing is on automatically
@input_guardrail
async 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 flowing

From 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:

TierSourceWhat it means
A — ExplicitYour annotated checks or OpenAI Agents SDK guardrail spansHigh confidence. Proves a named guardrail ran and whether it fired.
B — Provider-nativeLLM signals like content_filter finish reasonsCaptured automatically from model responses.
C — HeuristicDenial keywords in tool error messagesLow 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.

python
from traccia import init
from agents import Agent, Runner, input_guardrail, output_guardrail, GuardrailFunctionOutput
init()
@input_guardrail
async 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_guardrail
async 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 span

Each 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

For custom checks outside the SDK guardrail decorators, use 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

Posture is read-only observability. A triggered category in Posture means the run recorded a guardrail signal. It is not the same as a platform policy violation unless you connect them in your workflow.

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 thisTraccia expects
Calls an LLM with prompt datainput_validation, prompt_injection
Produces user-facing textoutput_validation, moderation
Uses toolstool_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
python
from traccia import init, govern
from 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

Use 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:

  1. Agent run — OpenAI Agents SDK with input/output guardrails; Traccia captures every tripwire on the trace
  2. Review when needed — human approval for irreversible or high-risk steps
  3. Side-effect function@govern hard 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.

StepControlTraccia role
User inputOpenAI SDK input guardrailTier A finding on trace
Agent runSDK tools + LLMFull agent trace + cost
Final outputOpenAI SDK output guardrailTriggered category if tripwire fired
Side effect@govern + platform policyHard 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.

OpenAI Agents docs