LLM Agents Academy
Module 8: Agent Frameworks & SDKs

OpenAI Agents SDK

Building agents with the OpenAI Agents SDK: tools, handoffs, and guardrails

~12 min readIncludes code

Overview

In the previous lesson, we surveyed the framework landscape and saw how agent SDKs sit on the spectrum between raw API calls and high-level orchestrators. Now we dive into our first framework in depth: the OpenAI Agents SDK.

Originally released as an experimental project called Swarm, the OpenAI Agents SDK has evolved into a production-grade framework for building single-agent and multi-agent systems. Its design philosophy is opinionated but minimal -- it gives you a small set of powerful primitives (agents, tools, handoffs, guardrails) and a runtime loop (the Runner) that orchestrates them. If you have been building agents with raw chat.completions.create() calls as we did in Modules 1 through 3, this SDK eliminates the boilerplate while keeping you close to the metal.

This lesson covers the SDK's architecture, its core abstractions, and how to build a realistic multi-agent customer support system using handoffs -- one of the SDK's most distinctive features.

SDK Architecture

The OpenAI Agents SDK is built around five core components that work together to form a complete agent runtime:

graph TD
  subgraph SDK["OpenAI Agents SDK"]
      AGENT["Agent
─────────────
• name
• instructions
• model
• tools
• handoffs
• guardrails"]
      TOOL["Tool
─────────────
• function_tool
• hosted_tool
• agent-as-tool"]
      HANDOFF["Handoff
─────────────
• target agent
• filter callback
• input type"]
      GUARDRAIL["Guardrail
─────────────
• input guardrail
• output guardrail
• tripwire"]
      RUNNER["Runner
─────────────
• run()
• run_sync()
• run_streamed()"]
  end

  USER([User Input]) --> RUNNER
  RUNNER --> AGENT
  AGENT --> TOOL
  AGENT --> HANDOFF
  AGENT --> GUARDRAIL
  HANDOFF --> AGENT2([Another Agent])
  RUNNER --> OUTPUT([Final Output])

  style SDK fill:#ebf8ff,stroke:#3182ce
  style AGENT fill:#c6f6d5,stroke:#38a169,color:#1a202c
  style TOOL fill:#fefcbf,stroke:#d69e2e,color:#1a202c
  style HANDOFF fill:#fed7d7,stroke:#e53e3e,color:#1a202c
  style GUARDRAIL fill:#e9d8fd,stroke:#805ad5,color:#1a202c
  style RUNNER fill:#bee3f8,stroke:#3182ce,color:#1a202c
  style USER fill:#f7fafc,stroke:#a0aec0,color:#1a202c
  style AGENT2 fill:#c6f6d5,stroke:#38a169,color:#1a202c
  style OUTPUT fill:#f7fafc,stroke:#a0aec0,color:#1a202c
  • Agent -- The central primitive. An agent wraps a model with a system prompt (called instructions), a set of tools, optional handoffs to other agents, and optional guardrails. Think of it as a configured persona with capabilities.
  • Tool -- A function the agent can call. The SDK uses Python type hints and docstrings to auto-generate the JSON schema that the model needs for tool calling.
  • Handoff -- A mechanism for one agent to transfer the entire conversation to a different agent. This is the SDK's answer to multi-agent orchestration.
  • Guardrail -- A validation check that runs in parallel with the agent. Guardrails can inspect input or output and tripwire (halt execution) if a policy is violated.
  • Runner -- The runtime engine that manages the agent loop. It sends messages to the model, dispatches tool calls, processes handoffs, checks guardrails, and repeats until the agent produces a final text response.

Defining an Agent

An Agent in the SDK is a declarative object. You specify what the agent is (its instructions) and what it can do (its tools and handoffs), and the Runner handles the how.

basic_agent.py

The instructions field is the system prompt. The model field selects which OpenAI model powers the agent. With just these two fields, you already have a functioning agent -- though one that can only respond from its training data.

Registering Tools

To give an agent the ability to act in the world, you register tools. The SDK provides a function_tool decorator that inspects your Python function's signature and docstring to build the tool schema automatically.

tools_example.py

Notice the pattern: you write a normal Python function with type hints and a docstring, decorate it with @function_tool, and pass it to the agent's tools list. The SDK handles schema generation, argument parsing, and result serialization. Compare this to the raw API approach from Module 2, where you had to manually define JSON schemas, parse tool call arguments, and manage the conversation history yourself.

Key insight: The SDK's tool registration is essentially a declarative wrapper around the same function-calling mechanism you built by hand in earlier modules. The underlying protocol is identical -- the SDK just eliminates the boilerplate.

Handoff Patterns

Handoffs are the SDK's most distinctive feature. A handoff lets one agent transfer the conversation to another agent mid-stream. This is different from calling another agent as a tool -- with a handoff, the new agent takes over completely, receiving the full conversation history.

The following sequence diagram shows how a triage agent routes a customer to specialized agents:

sequenceDiagram
  participant U as User
  participant R as Runner
  participant T as Triage Agent
  participant S as Sales Agent
  participant X as Support Agent

  U->>R: "I want to upgrade my plan"
  R->>T: Run with user message
  Note over T: Think: This is a sales inquiry
  T-->>R: Handoff → Sales Agent
  R->>S: Transfer conversation
  Note over S: Think: I can help with upgrades
  S->>R: tool call: get_pricing("enterprise")
  R-->>S: Pricing details
  Note over S: Think: I have the info
  S-->>R: Final response
  R->>U: "Great news! The Enterprise plan is..."

The triage agent does not try to handle sales questions itself. It recognizes the user's intent and hands the conversation to the sales agent, which has its own specialized instructions and tools. The Runner manages the entire transition seamlessly.

Here is how you define this multi-agent system with handoffs:

handoffs_example.py

The key details to notice:

  • handoffs=[sales_agent, support_agent] on the triage agent tells the SDK to present these agents as handoff targets to the model. Under the hood, the SDK generates a tool-call-like mechanism for each handoff.
  • result.last_agent tells you which agent ultimately handled the request. This is essential for logging and debugging in production.
  • Handoffs carry the full conversation history. The receiving agent sees everything the user said and everything the triage agent observed.

Guardrails

Guardrails are safety checks that run alongside the agent. Unlike prompt-based safety (where you tell the model "don't do X" in the system prompt), guardrails are programmatic validators that can inspect input or output and halt execution if a violation is detected.

There are two types:

  • Input guardrails -- Run before the agent processes the user's message. They can detect prompt injection, off-topic queries, or policy violations.
  • Output guardrails -- Run on the agent's final response. They can catch hallucinations, PII leakage, or inappropriate content.
guardrails_example.py

Notice that the guardrail itself uses an agent (the injection_detector) to perform classification. This is a common pattern -- you use a smaller, faster model (gpt-4o-mini) as the guardrail classifier so it does not add significant latency to the main agent's execution. The tripwire_triggered flag is the critical mechanism: when set to True, the Runner halts execution and raises an exception instead of returning a response.

The Runner: Putting It All Together

The Runner is the engine that drives everything. When you call Runner.run(), it executes a loop that is conceptually identical to the agent loop from Module 1, Lesson 4 -- but with built-in support for tool dispatch, handoffs, guardrails, and conversation management.

full_system.py

Here is what happens inside Runner.run() for the sales inquiry:

  1. Input guardrails run (if any). If a tripwire fires, execution halts.
  2. The triage agent receives the message and reasons about intent.
  3. The triage agent decides to hand off to the sales agent.
  4. The Runner transfers the conversation to the sales agent.
  5. The sales agent calls get_pricing("enterprise").
  6. The Runner executes the tool and feeds the result back.
  7. The sales agent produces a final text response with the pricing.
  8. Output guardrails run (if any). If a tripwire fires, execution halts.
  9. The Runner returns the RunResult with final_output and metadata.

All of this -- the loop, the tool dispatch, the handoff routing, the guardrail checks -- is managed by the Runner. You declare the what; it handles the how.

Comparing SDK to Raw API

If you have been following along since Module 1, you built agents from scratch using raw API calls. The following table highlights what the SDK abstracts away:

| Concern | Raw API (Modules 1-3) | OpenAI Agents SDK | |---------|----------------------|-------------------| | Agent loop | You write the while-loop | Runner.run() handles it | | Tool schemas | Manual JSON schema definition | Auto-generated from type hints | | Tool dispatch | You parse and route tool calls | SDK dispatches automatically | | Conversation history | You manage the messages list | SDK manages internally | | Multi-agent routing | You build custom routing logic | Declarative handoffs | | Safety checks | Ad-hoc prompt engineering | Structured Guardrail objects | | Streaming | You handle SSE parsing | Runner.run_streamed() |

The SDK does not change what is possible -- everything it does, you could build yourself. What it provides is a standardized, tested, and maintained implementation of the patterns you already understand. This is the value proposition of any good framework: it lets you focus on the problem domain (customer support, data analysis, code generation) instead of re-implementing infrastructure.

When to use the raw API instead: If you need fine-grained control over every message in the conversation, want to use non-OpenAI models, or have a highly custom execution flow that does not fit the SDK's opinion about how agents should work, the raw API remains the right choice.

Structured Outputs

The SDK integrates with Pydantic for structured output. Instead of parsing free-text responses, you can define a Pydantic model and the agent will return a validated, typed object:

structured_output.py

The output_type parameter tells the Runner to use OpenAI's structured output mode, ensuring the response conforms to the Pydantic schema. This eliminates the fragile regex-and-JSON parsing that plagues many agent implementations.

Tracing and Observability

Production agents need observability. The SDK includes built-in tracing that records every step of the agent's execution -- model calls, tool invocations, handoffs, and guardrail checks. Traces are automatically sent to the OpenAI dashboard, but you can also export them to custom backends.

tracing_example.py

Tracing is not optional in production systems. When an agent gives a wrong answer or takes an unexpected path, the trace is how you diagnose the problem. The SDK makes this a first-class concern rather than an afterthought.

Summary

The OpenAI Agents SDK provides a declarative, production-ready framework for building agents on top of OpenAI models. Its five core primitives -- Agent, Tool, Handoff, Guardrail, and Runner -- cover the full lifecycle of agent execution. The Agent defines the persona and capabilities. Tools give the agent the ability to act. Handoffs enable multi-agent routing without custom orchestration code. Guardrails provide programmatic safety that goes beyond prompt engineering. And the Runner manages the agent loop, freeing you from the boilerplate of tool dispatch, conversation history, and termination logic.

The SDK's sweet spot is applications where you want the power of multi-agent systems with minimal framework overhead -- and where you are committed to the OpenAI model ecosystem. In the next lesson, we will explore the Anthropic Claude Agent SDK, which takes a different philosophical approach to many of the same problems.

Check Your Understanding

Question 1 of 4

What is the primary role of the Runner in the OpenAI Agents SDK?