

When OpenAI quietly replaced the Assistants API with the Agents SDK in early 2025, I will admit I was sceptical. I had spent eighteen months building around Assistants, threads and runs, and the prospect of yet another paradigm shift felt exhausting. Then I sat down with the SDK over a weekend, ported one of my production agents, and came away genuinely impressed. The Agents SDK is the first OpenAI primitive that treats agent-building as a serious engineering discipline rather than a hosted black box.
In this guide I want to walk you through everything I wish I had known on day one. We will cover the core primitives - Agent, Tool, Handoff and Tracing - in enough depth that you can build something real by the end. I will share the production patterns I have settled on after migrating three systems off Assistants, and I will be honest about where the SDK is still rough.
This is not a documentation regurgitation. It is the guide I would hand to a colleague joining my team to build their first production agent on OpenAI’s stack.
The Assistants API was a noble experiment. It gave us threads, runs and file search out of the box, but it pushed too much state into OpenAI’s infrastructure and made it impossibly hard to debug. I lost count of the times I stared at a requires_action run with no idea why the model had decided to call a tool.
OpenAI listened. The Agents SDK is, in their words, “an unopinionated, lightweight package with very few abstractions” that gives you agents you can run yourself, trace yourself and reason about. The headline differences I noticed within an hour:
OpenAI deprecated the Assistants API for new development. Existing assistants still work, but the migration path is explicit and supported. If you are starting in 2026, the Agents SDK is the only sensible choice on the OpenAI stack.
The SDK is small enough that I can list its primitives on a single page. That economy is part of its charm.
| Primitive | What it does | Mental model |
| Agent | Wraps a model, instructions, tools and handoffs | A configured persona |
| Tool | A Python function the agent can call | A capability |
| Handoff | Delegation from one agent to another | A baton pass |
| Guardrail | Input or output validator | A bouncer |
| Session | Conversation state persistence | A thread |
| Runner | Executes the agent loop | The conductor |
| Trace | Structured run history | The flight recorder |
That is essentially it. Everything else - retrieval, evaluation, deployment - composes from these primitives plus your own Python.
For a comparison with the Anthropic equivalent, see my Claude Agent SDK vs OpenAI Agents SDK post.
I find the best way to learn a framework is to build a trivially small thing and then scale it up. Here is the canonical hello-world I use in my workshops.
from agents import Agent, Runner
agent = Agent(
name="Triage",
instructions="You triage customer questions. Keep responses under 80 words.",
model="gpt-4o-mini",
)
result = Runner.run_sync(agent, "My order is late, what should I do?")
print(result.final_output)
Three lines of substance: define the agent, run it, read the output. No threads, no runs, no polling. The first time I ran this I laughed at how much complexity OpenAI had quietly removed.
A few subtleties worth flagging immediately:
I will build on this example throughout the guide.
Tools are how an agent does anything beyond chatting. The SDK takes a Pythonic approach: decorate a function, type-hint the inputs, write a docstring, done.
from agents import function_tool
@function_tool
def get_order_status(order_id: str) -> str:
"""Return the current status of an order given its ID."""
return lookup(order_id)
The SDK extracts the schema from the type hints and docstring. You attach the tool to an agent via tools=[get_order_status] and it becomes callable.
Beyond plain Python functions, you get:
A pattern I use constantly is to combine a hosted WebSearchTool with a few custom function tools and one MCP server. It gives me search, structured actions and integrations without leaving the SDK.
The handoff is, in my opinion, the single most original idea in the SDK. Instead of one agent calling another as a tool and waiting for a return value, a handoff transfers control entirely. The receiving agent inherits the conversation and continues.
from agents import Agent, handoff
billing = Agent(name="Billing", instructions="Resolve billing questions.")
shipping = Agent(name="Shipping", instructions="Resolve shipping questions.")
triage = Agent(
name="Triage",
instructions="Route the user to the right specialist.",
handoffs=[billing, shipping],
)
When triage decides to hand off to shipping, the user effectively starts talking to a new agent. The SDK exposes pre and post hooks so you can mutate state, redact PII or log the transition.
I have found handoffs work best when:
For more on this pattern, see my multi-agent systems collaboration deep dive.
If you do not configure anything, every Runner.run starts fresh. For a chatbot that is wrong. The SDK ships Sessions for conversational memory.
from agents import Agent, Runner, SQLiteSession
session = SQLiteSession("user-123", "agent.db")
Runner.run_sync(agent, "Hi, my name is Alex.", session=session)
Runner.run_sync(agent, "What is my name?", session=session)
Behind the scenes the session persists the message list and replays it. SQLite is the default. There are connectors for Postgres and Redis, and you can implement your own by subclassing Session.
Three patterns I use in production:
For richer memory - summarisation, entity extraction, knowledge graphs - you compose on top of Sessions rather than expecting them to handle it.
This is where the SDK quietly outshines its predecessor. Every Runner.run automatically produces a trace, visible in the OpenAI dashboard or exportable via OpenTelemetry.
What I see in a typical trace:
| Span type | What it captures |
| agent.run | Inputs, outputs, model used, total tokens |
| tool.call | Tool name, arguments, return value, latency |
| handoff | Source agent, target agent, reason |
| guardrail | Input or output validator result |
| custom | Anything you log via tracing.custom_span |
The traces are sampled at 100% by default but you can downsample with the trace_sampler config in production.
I have integrated traces with LangSmith, Arize and Datadog via OTel exporters. The SDK does not lock you in. For teams that already invest in observability, the OTel hooks are the killer feature.
A warning from experience: do not log sensitive data into tool arguments. The trace captures everything. Use the redact hooks if you handle PII.
Guardrails are validators that run before or after the model call. They can fail fast, modify the input or output, or trigger a tripwire that halts the run.
from agents import input_guardrail, GuardrailFunctionOutput
@input_guardrail
async def block_prompt_injection(ctx, agent, user_input):
score = injection_classifier(user_input)
return GuardrailFunctionOutput(
output_info={"score": score},
tripwire_triggered=score > 0.9,
)
I attach guardrails for:
Guardrails are deliberately Pythonic. You can call OpenAI Moderation, a custom classifier, or even a small LLM judge. The tripwire pattern means a violation halts the run with a structured error you can handle in your application.
Anyone who has built a chat UI knows that streaming is non-negotiable. The SDK supports it cleanly.
async for event in Runner.run_streamed(agent, "Plan a trip to Lisbon"):
if event.type == "raw_response_event":
print(event.data.delta, end="")
elif event.type == "run_item_stream_event":
handle_tool_or_handoff(event.item)
You get three event categories: raw token deltas, run items (tool calls, handoffs, messages) and lifecycle events. In production I pipe raw deltas to the UI for low-latency token rendering and use the run items to render structured UI elements like tool call cards.
For voice applications, the SDK pairs naturally with the Realtime API. The Agents SDK takes care of tool orchestration while Realtime handles speech-to-speech.
I am almost evangelical about evals. The SDK does not bundle an eval harness, but it makes evaluation easy because runs are deterministic given fixed seeds and trace-rich.
My standard eval setup:
I usually wire this into the OpenAI Evals product when the test set is small and proprietary, and into LangSmith Evals or Braintrust when it scales.
“If you cannot answer the question ‘has this change made the agent better?’ with a number, you are not ready to ship.” This is the rule I drill into every team I work with.
For inspiration on what to test, see my AI agents portfolio projects and AI agents interview questions posts.
The SDK is just a Python package. Deployment is therefore conventional. The architectures I have shipped:
Operational essentials I never skip:
For more on this discipline, see LLM routing and orchestration patterns.
Agents are token-hungry. A single multi-turn run can consume tens of thousands of input tokens. I have learned to manage this aggressively.
Tactics that work:
A useful baseline: for a customer support agent with three tools, expect 4-8k tokens per turn average. If you see 20k+ consistently, something is wrong.
If you are coming from Assistants, here is the mental mapping that helped me.
| Assistants API | Agents SDK |
| Assistant | Agent |
| Thread | Session |
| Run | Runner.run invocation |
| Tools (function, code, retrieval) | Function tools, hosted tools, FileSearchTool |
| requires_action | Tool calls handled inline by the loop |
| OpenAI-hosted state | Your own database |
The migration steps I followed for a production assistant:
Allow a week per agent including testing. Most of that week is QA, not code.
I would not be doing my job if I painted the SDK as perfect. The honest gaps:
OpenAI has been shipping at a steady pace. Expect more first-party connectors, deeper MCP support and improved eval tooling through 2026.
Brian Jagger is an AI Architect and Software Engineer with over 15+ years of experience in generative AI, AI-first software development, and digital accessibility. As the Co-founder & CTO of TechA11y and Founder of GuardRailz, he has built innovative AI solutions for businesses, education, and enterprise clients. Brian combines deep technical expertise with a creative background in film and media, helping professionals leverage AI to build impactful, scalable solutions.
QUICK FACTS
Yes. I have shipped three systems on it and several Fortune 500 companies have publicly committed. Treat it as you would any rapidly evolving SDK and pin versions.