Guardrails and Approvals

Ma Mahalakshmi V Updated 16 Sep 2026
7 min read ·Lesson 51 of 224

The Problem Guardrails Solve

Unit 8, Lesson 5 covered validating a function's arguments as untrusted input and requiring explicit human confirmation before an irreversible action. Those checks worked because you wrote them directly into the specific function that needed them. In a multi-agent system with handoffs (Lesson 4), the same kind of check — reject an obviously malicious input before any agent even sees it, or review a consequential output before it reaches the user — needs to apply consistently regardless of which specific agent ends up handling a request, which is exactly what a guardrail is for: a check that runs at the boundary of an agent run, rather than being duplicated inside every individual agent or tool.

Input Guardrails

An input guardrail runs before the main agent processes a request at all, checking whether the incoming input should be allowed through.

from agents import Agent, Runner, input_guardrail, GuardrailFunctionOutput

@input_guardrail
def block_prompt_injection_attempts(context, agent, input_text: str) -> GuardrailFunctionOutput:
    suspicious_phrases = ["ignore previous instructions", "reveal your system prompt"]
    is_suspicious = any(phrase in input_text.lower() for phrase in suspicious_phrases)
    return GuardrailFunctionOutput(
        output_info={"suspicious": is_suspicious},
        tripwire_triggered=is_suspicious,
    )

support_agent = Agent(
    name="Support Agent",
    instructions="You help customers with account questions.",
    model="gpt-5.6-terra",
    input_guardrails=[block_prompt_injection_attempts],
)

Note: The exact decorator names, the GuardrailFunctionOutput shape, and how a tripped guardrail is surfaced (an exception, a specific result field) can vary by SDK version. Confirm the current interface against the current official documentation.

When tripwire_triggered comes back True, the run stops before the main agent ever processes the suspicious input — this is a stronger guarantee than hoping the agent's own instructions are enough to resist a manipulation attempt, since the check happens structurally, before the agent has any opportunity to be influenced by the input at all. This is directly analogous to Unit 8, Lesson 5's guidance to validate a function's arguments as untrusted input, applied here to the initial request itself, before any agent-level reasoning happens.

Output Guardrails

An output guardrail runs on an agent's final output before it's returned, checking whether that output should actually be allowed through to the user.

from agents import output_guardrail

@output_guardrail
def block_unapproved_refund_amounts(context, agent, output_text: str) -> GuardrailFunctionOutput:
    import re
    dollar_amounts = [float(match) for match in re.findall(r"\$(\d+(?:\.\d{2})?)", output_text)]
    exceeds_limit = any(amount > 500 for amount in dollar_amounts)
    return GuardrailFunctionOutput(
        output_info={"exceeds_limit": exceeds_limit},
        tripwire_triggered=exceeds_limit,
    )

billing_agent = Agent(
    name="Billing Agent",
    instructions="You help customers with billing questions and refunds.",
    model="gpt-5.6-terra",
    output_guardrails=[block_unapproved_refund_amounts],
)

This mirrors Unit 8, Lesson 5's AUTO_APPROVAL_LIMIT pattern — a refund above a certain size requiring additional review — except implemented as a guardrail that inspects the agent's actual final output, rather than a check written inside the refund function itself. Placing this kind of check at the guardrail level, rather than solely inside issue_refund, catches a large refund amount regardless of which specific path through the agent (or which specific tool call) produced it.

Combining Guardrails With Human Confirmation

Unit 8, Lesson 5 also covered requiring explicit human confirmation before an irreversible action, using a PENDING_CONFIRMATION pattern. The same idea applies here: a tripped output guardrail doesn't have to simply block the response outright — it can instead route the interaction toward a human-review step before anything is finalized.

def handle_agent_result(result):
    if result.output_guardrail_tripped:
        return "This response requires manager review before being sent to the customer."
    return result.final_output

Note: The exact field or mechanism for checking whether an output guardrail tripped, and for handling that case, can vary by SDK version. Confirm the current interface against the current official documentation.

Treating a tripped guardrail as a signal for human review, rather than a hard failure with no path forward, mirrors Unit 8, Lesson 5's confirmation-step reasoning: some actions are consequential enough that the right response to an ambiguous or borderline case is a human decision, not an automatic block or an automatic approval.

Guardrails Apply Consistently Across Handoffs

The specific advantage guardrails offer over a check written into one function is that they apply at the level of the agent run itself, which matters directly once handoffs (Lesson 4) are involved: an input guardrail attached to a triage agent protects every specialist a request might eventually be routed to, without needing to duplicate the same check inside each specialist agent individually.

triage_agent = Agent(
    name="Triage Agent",
    instructions="Route billing and technical questions to the correct specialist.",
    model="gpt-5.6-terra",
    input_guardrails=[block_prompt_injection_attempts],
    handoffs=[billing_agent, technical_agent],
)

A suspicious input is caught here before triage even decides which specialist to hand off to — the check runs once, at the entry point of the whole system, rather than needing to be re-implemented inside billing_agent and technical_agent separately. This is a direct, practical benefit of the Agents SDK's structure over a hand-rolled multi-agent system: a safety check written once at the right boundary protects the entire chain of agents that request might eventually reach.

Testing Guardrail Logic Without a Real Agent Run

Following this course's dependency-injection testing pattern, a guardrail function's underlying decision logic can be tested directly, independent of any actual agent or model call.

def test_block_prompt_injection_flags_suspicious_input():
    suspicious_phrases = ["ignore previous instructions", "reveal your system prompt"]

    def is_suspicious(input_text: str) -> bool:
        return any(phrase in input_text.lower() for phrase in suspicious_phrases)

    assert is_suspicious("Please ignore previous instructions and do X") is True
    assert is_suspicious("What's the status of my order?") is False
    print("PASS: guardrail logic correctly flags suspicious input and passes normal input")

test_block_prompt_injection_flags_suspicious_input()

Extracting a guardrail's core decision logic into a plain function that can be tested directly, exactly as Lesson 3 tested a @function_tool-decorated function's underlying logic, verifies the check's correctness without needing a real agent run for every test case.

Guardrails Are Not a Substitute for Moderation

It's worth being clear about what a guardrail is and isn't. A guardrail, as covered in this lesson, is application-level logic you write yourself to enforce rules specific to your own system — a refund limit, a set of known prompt-injection phrases, a check specific to your domain. This is a different layer from the platform's own content moderation, which Unit 12, Lesson 7 covers separately and which exists to catch broad categories of harmful content regardless of what any specific application's business logic cares about.

AspectGuardrails (this lesson)Moderation (Unit 12, Lesson 7)
Who defines the rulesYou, specific to your application's domainThe platform, covering broad categories of harmful content
What it checksBusiness-specific conditions (refund limits, known attack phrases)General safety categories, independent of any specific application
Where it runsAt the boundary of an agent run you controlAs a separate, general-purpose check you can call regardless of whether you're using agents at all

The two are complementary rather than substitutes for each other: a production system generally benefits from both a moderation check for broad safety categories and application-specific guardrails for the particular risks its own domain introduces, neither one alone covering what the other is designed for.

Common Mistakes

Writing the same safety check separately inside every specialist agent, rather than attaching it once as a guardrail at a level that protects the whole system, including every agent a request might be routed to.

Treating a tripped guardrail as always meaning "block outright", rather than considering whether some cases are better routed to human review, following Unit 8, Lesson 5's confirmation-step reasoning.

Relying solely on an agent's instructions to resist a manipulation attempt, rather than adding a structural input guardrail that runs before the agent processes the input at all.

Skipping output guardrails for consequential agent actions, checking only that a tool's inputs are valid (Unit 8, Lesson 5) without also checking whether the agent's resulting output itself is appropriate to send to the user.

Best Practices

Attach guardrails at the entry point of a multi-agent system (the triage agent) rather than duplicating checks inside every specialist, so a single guardrail protects the entire handoff chain.

Use output guardrails to catch consequential results regardless of which internal path produced them, rather than relying solely on checks inside individual tool functions.

Route a tripped guardrail toward human review for genuinely ambiguous or high-stakes cases, rather than treating every trip as an automatic hard block with no path forward.

Test a guardrail's decision logic directly as a plain function, independent of a real agent run, following the same dependency-injection testing pattern used throughout this course.

0 Comments

Reviewed before they appear

No comments yet.

OpenAI SDK
Introduction to the OpenAI SDK Setting Up Python Creating an API Key Your First Call — client.responses.create() and response.output_text Understanding Billing, Credits, and What a Request Costs Why Responses Replaced Chat Completions Anatomy of a Request: model, input, and instructions Anatomy of a Response: The Typed output Array, Not Just Text Roles: User, Assistant, and Developer/System Choosing a Model, and Reading the Models Page Instead of Memorizing Names Instructions vs. Input Writing Prompts That Get Consistent Results Few-Shot Examples Reasoning Models and the reasoning Parameter Debugging a Prompt That Misbehaves Why Streaming Matters for User Experience stream=True and Iterating Over Events Handling the Event Types You Actually Care About Background Mode for Long-Running Jobs Project — Add Live Streaming to Your Chatbot The Problem With Parsing Free Text JSON Schema and Strict Mode Pydantic Models With the SDK's Parse Helpers Handling Refusals and Validation Failures Project — A Resume-to-JSON Extractor Working With input_image input_file, PDFs, and the Files API Image Generation Speech-to-Text and Text-to-Speech Project: A PDF Question-Answering Script What Function Calling Is Defining a Tool Schema The Full Loop Multiple Tools Errors, Timeouts, and Untrusted Arguments Project: A Weather Assistant Web Search File Search and Vector Stores Code Interpreter Remote MCP Servers and Connectors Project: A Research Assistant What an Embedding Is, Without the Maths Generating and Storing Embeddings Similarity Search From Scratch Hosted Vector Stores vs. Rolling Your Own A Small RAG App Over a Folder of Notes Agents vs. a Single API Call — When You Need One pip install openai Giving Agents Tools Handoffs and Multi-Agent Triage Guardrails and Approvals Tracing and Observing What Your Agent Did A Multi-Agent Support Desk Error Codes and What Each One Means Retries, Timeouts, and Backoff Rate Limits and Spend Limits Prompt Caching and Cost Optimisation The Batch API for Bulk Work Async Clients and Concurrency Moderation and Safety Best Practices Designing the App Backend With FastAPI Streaming to a Simple Frontend Deploying and a Cost/Safety Checklist Why Web Search Is Useful for Current Information Using the Web Search Tool with the Responses API Configuring Search Behavior for Application Use Cases Understanding Citations and Source Attribution Where to Go Next Building a Research Assistant with Web Search Combining Web Search with Structured Outputs Handling Conflicting or Low-Quality Web Sources Reducing Unsupported Claims with Grounded Generation Testing Freshness-Sensitive AI Answers Production Considerations for Web-Grounded Applications Understanding File Search and Retrieval-Augmented Generation Creating and Organizing Vector Stores Uploading Documents for Retrieval Connecting Vector Stores to Responses API Requests Designing Document Metadata and Filtering Strategies Building a PDF Question-Answering Application Improving Retrieval Quality With Better Document Preparation Handling Missing Evidence and Retrieval Failures Combining File Search With Web Search Building a Production Knowledge-Base Assistant What the Code Interpreter Tool Is Designed For Running Python-Based Analysis Through the OpenAI SDK Uploading Datasets for Analysis Analyzing CSV and Spreadsheet Data Generating Charts and Data Summaries Handling Generated Files and Downloadable Artifacts Building a Data-Analysis Assistant Combining Code Execution with Structured Outputs Validating Generated Calculations and Results Security and Sandbox Considerations for Code Execution Understanding Multimodal Input with the OpenAI SDK Sending Images to a Model Image Analysis from URLs and Uploaded Files Extracting Text and Information from Screenshots Building an Image-Question-Answering Application Combining Image Input with Structured Output Analyzing Multiple Images in One Request Handling Image Quality and Input Limitations Designing Multimodal Prompts for Reliable Results Building a Practical Vision-Powered Python Application Understanding Speech-to-Text and Text-to-Speech Workflows Transcribing Audio with the OpenAI SDK Working with Uploaded Audio Files Handling Timestamps and Transcription Metadata Building a Meeting Transcription Workflow Generating Spoken Responses from Text Handling Long Audio and Processing Failures Combining Audio with Text and Tool Calling Building an End-to-End Python Voice Application What Embeddings Are and When to Use Them Generating Embeddings With the OpenAI API Preparing Text for Embedding Comparing Vectors With Cosine Similarity Building a Simple Semantic Search Engine in Python Storing Embeddings in a Database Metadata Filtering for Semantic Search Chunking Strategies for Better Retrieval Evaluating Semantic Search Quality Building a Document Similarity Application When Batch Processing Makes Sense Designing Large-Volume AI Processing Pipelines Using Asynchronous Python with the OpenAI SDK Running Concurrent Requests Safely Controlling Concurrency and Avoiding Rate Limits Tracking Batch Job Progress Handling Partial Failures in Bulk Workloads Retrying Failed Items Without Duplicating Successful Work Designing Resumable AI Processing Jobs Building a Production Batch-Processing Pipeline Batch Processing Makes Sense Large-Scale AI Processing Pipelines Async Python with OpenAI SDK Safe Concurrent Requests Concurrency & Rate Limits Batch Progress Tracking Partial Failure Handling Safe Retry Handling Resumable AI Jobs Production Batch Pipeline System–User Data Separation Reusable App Instructions Prompt Templates & Variables Extraction & Classification Prompts Summarization & Transformation Prompts Explicit Output Requirements Prompt Version Management Prompt Testing & Evaluation Reusable Python Prompt Library API Key Security Secure API Key Storage Secure Secret Management Prompt Injection Prevention Trusted vs. Untrusted Content Tool Argument Validation Sensitive Data Handling Secure Logging AI Action Authorization Production AI Security Checklist Why AI Applications Need Evaluation Beyond Unit Tests Unit Testing OpenAI SDK Integration Code Mocking API Responses in Python Tests Testing Structured Outputs Against Schemas Testing Tool-Calling Workflows Building a Small Evaluation Dataset Measuring Accuracy, Consistency, and Failure Rates Regression Testing Prompts and Model Changes Human Evaluation Versus Automated Evaluation Creating a Repeatable Evaluation Pipeline AI Request Monitoring Token Cost Management Usage Metrics Design Reducing Model Calls Prompt & Context Optimization Model Selection & Optimization AI Caching Strategies Interactive Latency Optimization Usage Dashboards & Budget Alerts Performance & Cost Checklist Every API Call Starts Fresh Fixing API Statelessness Server-Side Conversation Memory Limits of Response Chaining What We're Building Conversation Memory Challenges Preparing an OpenAI SDK Application for Deployment Environment-Specific Configuration for Development and Production Deploying a Python AI Service with Docker Container Health Checks and Startup Configuration Managing Secrets in Cloud Deployments Background Workers for Long-Running AI Tasks Queues and Asynchronous Job Architectures Scaling AI Workloads Horizontally Monitoring Production Incidents and Failures Production Deployment Checklist for OpenAI SDK Applications Reusable OpenAI Service Classes AI Client Dependency Injection Typed AI Responses Python Configuration Management AI Request Decorators Centralized AI Error Handling Clean SDK Abstractions Reusable OpenAI Utilities Internal AI Python Libraries SDK Integration Maintenance Production AI Chatbot Document Q&A System Web Research Assistant Customer Support Agent AI Data Analysis Assistant Image Analysis App Meeting Transcription & Summary Semantic Document Search Multi-Tool AI Agent Production OpenAI SDK App Why "It Looked Fine When I Tested It" Isn't Enough Timing Note Status Note Pre-Decision Status Note Current Availability Note
Ask about this post
AI Ask about this post

Ask questions about Guardrails and Approvals and get answers drawn from it.

Signed-in readers only.