OpenAI Agents SDK Core Concepts
The OpenAI Agents SDK (openai-agents) is a first‑party Python framework that provides a production‑grade runtime for building, orchestrating, and observing AI agents. It gives you ready‑made abstractions for agents, tools, handoffs, guardrails, sessions, and tracing—so you can focus on what the agent should do rather than on low‑level orchestration. This article is your implementation‑focused handbook: you’ll learn the core building blocks, understand the execution model, and see practical code examples for each concept.
What Is the OpenAI Agents SDK
The SDK wraps the raw OpenAI API into an agent‑centric programming model. Instead of manually managing chat completions, tool‑call loops, and context switching, you define an Agent with a name, instructions, and a set of tools. You then hand it to the Runner, which handles the entire execution lifecycle—reasoning, tool invocations, guardrails, handoffs, and multi‑turn conversations.
A minimal example:
from agents import Agent, Runner
agent = Agent(
name="Assistant",
instructions="You are a helpful assistant. Answer concisely.",
model="gpt-4o"
)
result = await Runner.run(agent, "What is the capital of France?")
print(result.final_output) # "Paris"
Key differences from using the raw API directly:
- Built‑in tool orchestration – the Runner automatically loops over tool calls and LLM reasoning until a final answer is produced.
- Handoffs – delegate to another agent mid‑conversation with automatic context transfer.
- Guardrails – input and output validation run before the agent sees input and after it produces output, without you writing wrapping logic.
- Sessions – long‑running, stateful conversations are persisted and can be resumed later.
- Tracing – every step is recorded and can be visualized in the OpenAI dashboard.
The SDK is designed to scale from a single‑agent script to a multi‑agent production service with minimal code changes.
Why the OpenAI Agents SDK Exists
Building robust agents with the raw Chat Completions API requires a lot of boilerplate: you have to maintain conversation history, detect tool calls, execute tools, merge results, handle errors, and manage context across multiple agents. The SDK eliminates this boilerplate with a consistent, opinionated runtime. It was created to:
- Simplify agent development – write agents as declarative objects, not as complex loops.
- Standardize agent workflows – tool calling, handoffs, and guardrails follow a uniform pattern.
- Improve observability – built‑in tracing logs every action, making debugging and monitoring straightforward.
- Support production deployment – sessions, structured outputs, and validation checks help you run reliably at scale.
- Reduce orchestration complexity – the Runner handles the messy state transitions; you just configure the agent.
Core Concepts Overview
| Concept | Purpose |
|---|---|
| Agent | An AI worker that combines a model, instructions, tools, and optional handoffs. |
| Instructions | The system prompt that defines the agent’s role, tone, and behaviour. |
| Tools | External functions (APIs, databases, code) that the agent can invoke. |
| Handoffs | A mechanism to transfer the conversation to another agent. |
| Guardrails | Safety and validation checks that run before or after the agent processes input. |
| Session | A stateful execution context that maintains conversation history across multiple turns. |
| Tracing | Automatic capture of every step—LLM calls, tool runs, handoffs—for debugging and audit. |
These abstractions compose together. In the following sections we’ll examine each one with implementation details and code.
Agent
What Is an Agent
An Agent is the primary execution unit in the SDK. It encapsulates:
- A name (unique identifier within a workflow).
- An instruction (system message) that controls behaviour.
- A model (e.g.,
"gpt-4o","gpt-4o-mini"). - A list of tools the agent can call.
- (Optionally) a list of handoffs to other agents.
- An output type for structured output.
When you create an agent, you’re defining a reusable role. The Runner will instantiate the agent for each request, manage the conversation loop, and enforce guardrails.
Example: a support agent with a tool.
from agents import Agent, function_tool
@function_tool
def lookup_order(order_id: str) -> str:
# Call your order database
return f"Order {order_id}: shipped"
support_agent = Agent(
name="Support Agent",
instructions="You help customers with order inquiries. Use the lookup tool to fetch order status.",
model="gpt-4o",
tools=[lookup_order]
)
Agent Components
| Component | Required | Description |
|---|---|---|
name | Yes | A unique string identifier (used in tracing and handoffs). |
instructions | Yes | The system prompt; can be a string or a callable that receives context. |
model | No | The OpenAI model name; defaults to "gpt-4o" (or latest) if omitted. |
tools | No | List of tools the agent may call. |
handoffs | No | List of agents to which this agent can hand off. |
output_type | No | A Pydantic model for structured output; forces the agent to return JSON matching that schema. |
Every agent is stateless in itself; state is stored in the Session (see below). This makes agents thread‑safe and easy to replicate.
Instructions
Instructions are the agent’s core personality. They are passed as the system message when the model is called. You can provide a static string, or a callable that receives the RunContext and returns a dynamic prompt.
Static instructions:
agent = Agent(
name="Translator",
instructions="You translate English to French. Return only the translated text.",
model="gpt-4o"
)
Dynamic instructions based on runtime context:
def build_instructions(context, agent):
return f"You are a {context['role']}. Provide advice in a {context['tone']} tone."
agent = Agent(
name="Advisor",
instructions=build_instructions,
model="gpt-4o"
)
Instructions define:
- Role – the agent’s job title and domain expertise.
- Constraints – what the agent should not do.
- Output formatting – e.g., “Return JSON only”, “Summarise in 3 sentences”.
Well‑crafted instructions are the single most important factor for reliable agent behaviour.
Tools
What Are Tools
Tools give agents the ability to interact with the outside world. The SDK supports two main tool types:
- Function tools – regular Python functions decorated with
@function_tool. - Host tools – built‑in tools like
WebSearchTool,FileSearchTool, andCodeInterpreterTool(requires a connected runtime environment).
When a tool is called, the Runner:
- Pauses the LLM.
- Executes the tool (locally or remotely).
- Feeds the tool’s output back into the conversation as a new message.
- Resumes the LLM loop until the agent produces a final response.
Defining a Function Tool
from agents import function_tool
from pydantic import BaseModel, Field
class WeatherArgs(BaseModel):
city: str = Field(description="City name")
@function_tool
def get_weather(city: str) -> str:
"""Return current weather for a city."""
# In real code, call a weather API
return f"The weather in {city} is sunny, 22°C"
The function’s docstring becomes the tool’s description, which the model uses to decide when to call it. The args_schema (automatically derived from the function signature or explicitly set) tells the model what parameters to pass.
Tools can also be asynchronous (async def). The Runner handles both sync and async tools seamlessly.
Handoffs
Handoffs allow one agent to transfer control to another agent, along with the full conversation context. This is the SDK’s primary mechanism for building multi‑agent workflows. When a handoff occurs, the receiving agent’s instructions are prepended to the conversation, and the original agent’s name is recorded so the user knows who is now “speaking”.
Example: triage agent handing off to specialists.
billing_agent = Agent(name="Billing", instructions="...", tools=[...])
tech_agent = Agent(name="Tech Support", instructions="...", tools=[...])
triage_agent = Agent(
name="Triage",
instructions="Determine user intent and hand off to the appropriate specialist.",
model="gpt-4o",
handoffs=[billing_agent, tech_agent]
)
The triage_agent will reason about the user’s request and, when appropriate, produce a handoff call to billing_agent or tech_agent. The Runner automatically routes the conversation, and the receiving agent continues from where the first left off.
Handoffs can be chained—an agent can hand off to another agent, which can hand off to yet another. The SDK ensures that context (including tool results and conversation history) flows correctly through the chain.
Guardrails
Guardrails are validation checks that run automatically at the boundaries of an agent’s execution. They help enforce safety, business rules, and input/output quality. The SDK supports two types:
- Input guardrails – run before the agent processes the user message. If the guardrail raises an exception, the input is blocked.
- Output guardrails – run after the agent produces a final output. If the output fails validation, it can be rewritten or rejected.
Guardrails are defined as async functions and attached to an agent.
Input guardrail example: reject toxic input.
from agents import input_guardrail
@input_guardrail
async def no_toxic_input(context, agent, input):
if "offensive_word" in input:
raise ValueError("Toxic content detected")
Output guardrail example: ensure answer contains a specific citation format.
from agents import output_guardrail
@output_guardrail
async def check_citations(context, agent, output):
if "Reference:" not in output:
# Optionally, the SDK allows you to modify the output or raise an error.
raise ValueError("Missing required citation")
Attach guardrails to an agent:
agent = Agent(
name="Support",
instructions="...",
input_guardrails=[no_toxic_input],
output_guardrails=[check_citations],
...
)
The Runner will automatically invoke guardrails at the correct points. Guardrails are crucial for content safety, compliance, and format enforcement in production.
Sessions
A Session represents a stateful conversation thread. It stores the message history, the conversation turn count, and any metadata. Sessions allow you to maintain long‑running interactions where a user can pause, resume later, and have the agent remember previous context.
The SDK’s Session object is created either automatically by the Runner or manually with a unique ID. You can persist sessions to a database (e.g., using the SQLiteSession or a custom adapter).
Basic session usage:
from agents import Agent, Runner, RunConfig
config = RunConfig(session_id="user-123")
result = await Runner.run(agent, "My order hasn’t arrived.", run_config=config)
# Later, same session ID
result2 = await Runner.run(agent, "Check again please.", run_config=config)
The second call will include all previous messages, so the agent understands it’s a follow‑up.
Behind the scenes, the Runner loads the conversation history from the session store, appends the new user message, runs the agent, and then persists the new state. This makes it trivial to build multi‑turn agents without writing any persistence code.
Tracing
Tracing gives you end‑to‑end visibility into every agent run. The SDK automatically generates traces that include:
- The user input and final output.
- Each LLM call with token usage.
- Every tool invocation (name, arguments, result).
- Handoff events and guardrail checks.
- Timing information for each step.
Traces can be viewed in the OpenAI Platform under the “Traces” tab. You can also export them to other observability tools.
Enabling tracing:
from agents import set_tracing_export_api_key
import os
# Set your OpenAI API key (or tracing key) and enable
os.environ["OPENAI_API_KEY"] = "sk-..."
set_tracing_export_api_key(os.environ["OPENAI_API_KEY"])
With that, every Runner.run() call will send a trace to OpenAI. You can add custom spans and attributes using the trace() context manager.
Example: custom trace span.
from agents import trace
with trace("Research flow"):
# ... agent runs are automatically nested
result = await Runner.run(agent, query)
Tracing is essential for debugging, performance analysis, and monitoring in production.
Execution Lifecycle
When you call Runner.run(agent, input), the SDK goes through the following steps:
- Input guardrails run first; if any reject, the run stops immediately.
- The agent loop begins: the LLM generates a response.
- If the response contains a tool call, the tool is executed and the result is fed back to the LLM.
- If the response is a handoff, the Runner transfers control to the new agent.
- If the response is a final text output, output guardrails are applied.
- The session is updated with the new messages, and a trace is recorded.
This lifecycle is entirely managed by the Runner—you don’t need to write any control flow.
Data Flow Inside the SDK
Data passes through a well‑defined pipeline:
Input (user message) → Instructions (system prompt) → LLM → Tool (if needed) → Guardrail (output) → Output
At each stage, the RunContext object carries session state, metadata, and the agent’s configuration. Tools and guardrails can access this context to make informed decisions.
OpenAI Agents SDK vs Raw OpenAI API
| Feature | Raw API | Agents SDK |
|---|---|---|
| Tool orchestration | Manual loop; you must detect tool calls, call functions, merge results | Automatic; Runner handles tool call → execution → continuation |
| Handoffs | You must implement your own agent switching logic | Built‑in handoff mechanism with automatic context transfer |
| Guardrails | You write validation wrappers around API calls | Input/output guardrail decorators that run automatically |
| Tracing | Limited; you integrate your own logging | Built‑in tracing to OpenAI dashboard with full step visibility |
| Sessions | You manage conversation history yourself | First‑class session abstraction with persistent storage adapters |
| Structured output | Manual JSON validation | Agent output_type parameter forces well‑formed JSON responses |
The SDK doesn’t add any new capabilities to the model—it adds production‑grade workflow management that you would otherwise have to build from scratch.
OpenAI Agents SDK vs Other Frameworks
| Framework | Philosophy | Strengths | When to choose |
|---|---|---|---|
| OpenAI Agents SDK | Lightweight, OpenAI‑native agent runtime | Simplicity, built‑in tracing, fast setup, seamless OpenAI integration | Teams heavily using OpenAI models; quick prototyping to production |
| LangGraph | State‑machine graph execution | Full control over flow, checkpoints, human‑in‑the‑loop | Complex, long‑running workflows needing explicit state management |
| CrewAI | Role‑based task delegation | Easy mental model, built‑in memory & tools, fast collaborative agents | Rapid development of multi‑agent teams with less overhead |
| AutoGen | Event‑driven, conversation‑centric | Asynchronous, scalable, rich event model, supports non‑OpenAI models | Highly interactive, multi‑party conversations and flexible tool use |
The OpenAI Agents SDK is the most lightweight and fastest to production when you are already invested in the OpenAI ecosystem. It doesn’t try to be a universal framework; instead, it excels at providing a clean, well‑integrated experience for OpenAI‑based agents.
Common Beginner Mistakes
- Overloading instructions – cramming too many rules makes the agent unreliable. Keep instructions focused.
- Excessive tool usage – giving an agent 20 tools with overlapping descriptions confuses the model. Provide only what’s necessary.
- Ignoring guardrails – skipping input validation can allow harmful or out‑of‑policy content.
- Poor handoff design – creating circular handoffs or not handling escalation leads to infinite loops.
- Missing tracing – without observability, debugging production agents is nearly impossible.
- Not using output types – relying on free‑text parsing for structured data increases fragility.
Consequences: unpredictable agent behaviour, security holes, difficult debugging, and frustrated users.
Best Practices
- Keep instructions focused – one clear role, a few constraints. Use dynamic instructions only when needed.
- Use structured outputs – define
output_typewith a Pydantic model to guarantee consistent JSON. - Limit tool complexity – each tool should do exactly one thing. Use descriptive names and docstrings.
- Enable tracing – turn on tracing in all environments, even development. It pays for itself in debugging time.
- Validate outputs – apply output guardrails to enforce format and content policies.
- Design clean handoffs – have a clear triage → specialist pattern. Avoid deep chains.
- Persist sessions – for multi‑turn applications, always use a session store (SQLite, Postgres) rather than in‑memory.
- Test guardrails and tools in isolation before integrating them into the agent.
Practical Example: Research Assistant Agent
Let’s build a full example that demonstrates all core concepts: an agent that answers user questions by first searching the web, then summarises the findings, with input/output guardrails and tracing.
1. Define the tools
from agents import function_tool
import requests # for the actual API call
@function_tool
def web_search(query: str) -> str:
"""Search the web for a given query and return the top snippets."""
# In production, use a search API like Tavily or SerpAPI
response = requests.get(f"https://api.search.example?q={query}")
return response.text[:500] # truncate for context window
2. Create agents (using handoff for summarization)
We’ll create two agents: a Researcher that searches, and a Summarizer that condenses the results. The researcher will hand off to the summarizer when it has collected data.
from agents import Agent
researcher = Agent(
name="Researcher",
instructions="Search the web for the user's query. When you have enough information, hand off to the Summarizer to create a concise summary.",
model="gpt-4o",
tools=[web_search]
)
summarizer = Agent(
name="Summarizer",
instructions="Summarize the research findings into a single paragraph.",
model="gpt-4o-mini"
)
# Researcher can hand off to Summarizer
researcher.handoffs = [summarizer]
3. Add guardrails
from agents import input_guardrail, output_guardrail
@input_guardrail
async def reject_empty_input(context, agent, input):
if not input or len(input.strip()) == 0:
raise ValueError("Input cannot be empty")
@output_guardrail
async def ensure_summary_length(context, agent, output):
if len(output) > 2000:
raise ValueError("Output too long")
Attach guardrails to the researcher agent (they’ll apply across the chain):
researcher.input_guardrails = [reject_empty_input]
researcher.output_guardrails = [ensure_summary_length]
# Note: In practice, output guardrails run on the final agent that produces the final answer. In a handoff, the final answer comes from the last agent. So you'd attach the output guardrail to the summarizer as well, or set `output_guardrails` on the agent that will actually return the final output.
We’ll attach the output guardrail to the summarizer instead, because the final answer comes from it:
summarizer.output_guardrails = [ensure_summary_length]
4. Run with session and tracing
from agents import Runner, RunConfig
import asyncio, os
# Enable tracing
os.environ["OPENAI_API_KEY"] = "sk-..."
from agents import set_tracing_export_api_key
set_tracing_export_api_key(os.environ["OPENAI_API_KEY"])
async def main():
config = RunConfig(session_id="research-session-1")
query = "Latest advances in AI agent frameworks"
result = await Runner.run(researcher, query, run_config=config)
print("Final answer:", result.final_output)
asyncio.run(main())
Execution flow:
- The Runner checks input guardrails (non‑empty). Passes.
- The
Researcherreceives the query, reasons, and callsweb_search. - The search result is returned; the researcher may search again or decide it has enough data.
- The researcher outputs a handoff to the
Summarizerwith the collected information as context. - The
Summarizerreceives the handoff, reads the research snippets, and produces a final summary. - The output guardrail
ensure_summary_lengthruns; if the summary is too long, it’s rejected (and the Runner may retry or return an error). - The final output is returned, the session is saved, and a trace is recorded in the OpenAI dashboard.
This example illustrates how Agents, Tools, Handoffs, Guardrails, Sessions, and Tracing work together to create a robust, observable workflow—all in less than 50 lines of code.
OpenAI Agents SDK and Modern Agent Development
The SDK integrates naturally with the broader agent ecosystem:
- Tool calling – Beyond OpenAI’s built‑in tools, you can wrap any API or function, including MCP tools (via a custom tool adapter). See the MCP Guide.
- Agent workflows – Handoffs and sessions enable complex, multi‑step processes. Combined with guardrails, you can create safe, compliant pipelines.
- Human‑in‑the‑Loop – You can implement HITL by using an
input_guardrailthat pauses and awaits external approval, or by using aHostToolthat simulates a “wait for human” tool. - Production operations – Tracing feeds into dashboards; sessions enable crash recovery; structured output ensures downstream processing consistency.
For a deeper dive into tool calling and handoffs, continue to:
- Tool Calling with OpenAI Agents SDK
- Handoffs & Multi‑Agent Patterns
- Guardrails & Safety
- Observability & Tracing
FAQ
1. What is the OpenAI Agents SDK?
It’s a Python framework that provides high‑level abstractions (Agent, Runner, Tools, Handoffs, Guardrails) to build and deploy AI agents on top of OpenAI models.
2. What is an Agent in the SDK?
An Agent is a configuration object that binds a model, instructions, tools, handoffs, and guardrails. The Runner uses it to execute a conversation.
3. What are Handoffs?
Handoffs allow an agent to delegate the conversation to another agent while preserving full context. They enable multi‑agent collaboration.
4. What are Guardrails?
Guardrails are validation functions that run before the agent processes input (input guardrails) or after it produces output (output guardrails). They enforce safety and policy rules.
5. How does Tracing work?
Tracing automatically records every step of an agent run—LLM calls, tool executions, handoffs, guardrails—and sends them to the OpenAI dashboard for visualization.
6. How are Sessions managed?
A Session stores the conversation history for a specific thread. You provide a session_id in the RunConfig; the SDK persists and loads messages automatically.
7. Is the SDK production‑ready?
Yes. It includes built‑in persistence, tracing, guardrails, and is used in production by many teams. It is actively maintained by OpenAI.
8. Can I use non‑OpenAI models with the SDK?
Not directly. The SDK is designed for OpenAI models (GPT‑4o, GPT‑4o‑mini, etc.). For multi‑model support, consider AutoGen or LangChain.
9. How do I add a custom tool?
Use the @function_tool decorator on any Python function (sync or async). Provide a clear description via the docstring.
10. What’s the difference between a tool and a handoff?
A tool performs an external action (search, API call) and returns data. A handoff transfers the entire conversation to a different agent.
11. Can I have dynamic instructions?
Yes, you can pass a callable that receives the RunContext and returns a string. This allows per‑request customization.
12. How do I debug an agent run?
Enable tracing and view the trace in the OpenAI dashboard. Add console.log statements in tools/guardrails, or use the trace context manager for custom spans.
13. Does the SDK support streaming?
Yes, the Runner supports streaming responses. You can set stream=True in Runner.run() to receive tokens as they are generated.
14. Can I run the agent without the Runner?
It’s possible to call agent.run() directly, but you lose session handling, guardrails, and tracing. Using the Runner is recommended.
15. How do I persist sessions across server restarts?
Use a persistent session store like SQLiteSession or PostgresSession. The SDK provides adapters; you just configure the storage backend.
16. Is there a way to limit the number of tool calls?
You can set max_turns in the RunConfig to prevent infinite loops. The Runner will stop after that many conversational turns.
Conclusion
The OpenAI Agents SDK brings a clean, production‑ready abstraction layer to agent development. You’ve learned how the core concepts—Agents, Instructions, Tools, Handoffs, Guardrails, Sessions, and Tracing—fit together to form a complete execution model. The Runner orchestrates the entire lifecycle, so you can concentrate on designing intelligent behaviour.
Key takeaways:
- Agents are declarative and easy to configure.
- Tools and handoffs provide real‑world action and multi‑agent collaboration.
- Guardrails enforce safety and compliance at the runtime level.
- Sessions and tracing give you state persistence and full observability.
- The SDK is simple yet powerful, especially when you’re building on OpenAI models.
Continue exploring the SDK with these deep‑dive articles:
- Tool Calling – write effective tools and integrate external APIs.
- Handoffs & Multi‑Agent Patterns – design complex agent delegation.
- Guardrails & Safety – build robust validation layers.
- Observability & Tracing – monitor and debug your agents.
For comparisons with other frameworks, see the LangGraph, CrewAI, and the full framework comparison. To learn about standardised tool ecosystems, visit the MCP Guide.
Now, go build agents that are reliable, safe, and observable—right out of the box.