Skip to content
Blog

Guardrails for Agent Input and Output: From Validation to Policy

Screen input, validate output, and police tool calls with guardrails. Learn input, output, and tool guardrails in the OpenAI Agents SDK—and the cost of parallel vs blocking execution.

Published on August 20, 2026

AI Assistant

Every agent you ship is a potential attack surface. Users can inject instructions, the model can emit malformed or harmful output, and a misconfigured tool call can trigger a side effect you can’t undo. The fix is a guardrail: a check that runs around your agent and decides whether execution continues, pauses, or stops.

Guardrails are cheap to write and expensive to skip. In the OpenAI Agents SDK, they come in three kinds—input, output, and tool—and each one answers a different question.

In this tutorial, you will learn how to build all three guardrails, when to run them in parallel vs. blocking, and how tripwires halt execution.

Prerequisites

  • Python 3.9+ with pip install openai-agents
  • An OpenAI API key (or another provider)

The Three Kinds of Guardrails

KindRuns onStopsExample
Input guardrailsThe initial user inputInputGuardrailTripwireTriggeredOff-topic or malicious requests
Output guardrailsThe final agent outputOutputGuardrailTripwireTriggeredInvalid or harmful responses
Tool guardrailsEach function-tool invocationToolInputGuardrailTripwireTriggered / ToolOutputGuardrailTripwireTriggeredSecrets in args, dangerous side effects

A guardrail is a function that receives input (or output) and returns a GuardrailFunctionOutput with a tripwire_triggered flag.

Input Guardrails: The Cheap-First Model Pattern

The canonical use case: a fast, cheap model screens input before an expensive model runs. If the guardrail detects malicious usage, it can raise an error before the expensive model ever starts.

from agents import Agent, Runner, input_guardrail, GuardrailFunctionOutput

guardrail_agent = Agent(
    name="Guardrail check",
    instructions="Check if the user is asking you to do their math homework.",
    output_type=HomeworkCheck,
)

@input_guardrail
async def math_guardrail(ctx, agent, input):
    result = await Runner.run(guardrail_agent, input, context=ctx.context)
    return GuardrailFunctionOutput(
        output_info=result.final_output,
        tripwire_triggered=result.final_output.is_math_homework,
    )

agent = Agent(
    name="Customer support agent",
    instructions="You help customers with their questions.",
    input_guardrails=[math_guardrail],
)

try:
    await Runner.run(agent, "Can you help me solve: 2x + 3 = 11?")
except Exception as e:
    print("Guardrail tripped:", type(e).__name__)

Guardrails are attached to the agent, not the runner, because different agents need different checks. Input guardrails run only for the first agent in a chain.

Parallel vs. Blocking Execution

Input guardrails have two execution modes—and the choice is a cost/latency tradeoff:

@input_guardrail
async def safety_check(ctx, agent, input):
    ...

agent = Agent(
    name="assistant",
    input_guardrails=[
        safety_check,                    # parallel (default)
        blocking_check.configure(run_in_parallel=False),  # blocking
    ],
)
  • Parallel (run_in_parallel=True, default): the guardrail runs concurrently with the agent. Best latency—both start at the same time—but if the tripwire triggers, the agent may have already consumed tokens and executed tools.
  • Blocking (run_in_parallel=False): the guardrail completes before the agent starts. If it triggers, the agent never executes, preventing token spend and tool side effects. Ideal when cost and safety outweigh latency.

Use blocking when the cost or risk of starting the main agent is too high; use parallel when lower latency matters more than avoiding speculative work.

Output Guardrails: Validate the Final Answer

Output guardrails run after the agent completes and check the final output. They’re useful for validating structure, catching policy violations, or redacting content before it reaches the user:

from agents import output_guardrail

@output_guardrail
async def email_validator(ctx, agent, output):
    if not output.reply_text.strip():
        return GuardrailFunctionOutput(tripwire_triggered=True)
    if len(output.reply_text) > 2000:
        return GuardrailFunctionOutput(tripwire_triggered=True)
    return GuardrailFunctionOutput(tripwire_triggered=False)

Output guardrails run only for the agent that produces the final output in a chain. They always run after completion, so there’s no parallel mode.

Tool Guardrails: Police Every Call

The most important guardrail for side effects is the tool guardrail. It wraps a specific function tool and runs before and after every invocation:

from agents import (
    tool, tool_input_guardrail, tool_output_guardrail,
    ToolGuardrailFunctionOutput,
)
import json

@tool_input_guardrail
def block_secrets(data):
    args = json.loads(data.context.tool_arguments or "{}")
    if "sk-" in json.dumps(args):
        return ToolGuardrailFunctionOutput.reject_content(
            "Remove secrets before calling this tool."
        )
    return ToolGuardrailFunctionOutput.allow()

@tool_output_guardrail
def redact_output(data):
    text = str(data.output or "")
    if "sk-" in text:
        return ToolGuardrailFunctionOutput.reject_content(
            "Output contained sensitive data."
        )
    return ToolGuardrailFunctionOutput.allow()

@tool(
    tool_input_guardrails=[block_secrets],
    tool_output_guardrails=[redact_output],
)
def classify_text(text: str) -> str:
    return f"length:{len(text)}"

Tool guardrails run on the tools they’re attached to—not just the first or last agent. That makes them the right place for checks around side-effecting operations like shell commands, database writes, or payment calls.

Where Guardrails Don’t Run

A common footgun: agent-level guardrails don’t run everywhere.

  • Input guardrails run only for the first agent in a chain.
  • Output guardrails run only for the agent that produces the final output.
  • Tool guardrails run on the function tools they’re attached to.

If you need checks around every tool call in a manager-style workflow (agents as tools), don’t rely on agent-level input/output guardrails. Put validation next to the tool that creates the side effect.

Guardrails + Human Review

Guardrails are automatic checks; human review is the approval decision. Together they define when a run should continue, pause, or stop:

  • Guardrails block disallowed requests and invalid output.
  • Approvals pause the run so a person can approve or reject a sensitive action.
@tool(needs_approval=lambda ctx, args, agent: float(args["amount"]) > 5000)
def issue_refund(order_id: str, amount: float) -> str:
    return refund_api.refund(order_id, amount)

Guardrails screen, approvals gate. Use guardrails where the check is deterministic enough to automate, and approvals where judgment is required.

Putting It All Together

For complete, runnable guardrail examples across input, output, and tool levels, see:

Conclusion & Next Steps

You now know how to build layered policy for agents: screen input with cheap models, validate output structure, police tool calls at the side-effect boundary, and escalate to human approval when judgment is required.

Next steps:

  • Add a prompt-injection detector as a blocking input guardrail.
  • Wrap shell/refund/delete tools with input guardrails and approval gates.
  • Trace guardrail trips to tune false-positive rates over time.

An agent without guardrails is an unauthenticated endpoint. A few well-placed checks are the difference between a demo and a deployable system.

References