Before you start
Install the SDK with pip install openai-agents on Python 3.10 or newer, or with npm install @openai/agents zod for TypeScript. The TypeScript SDK runs on Node.js 22 or later, Deno, Bun and Cloudflare Workers with nodejs_compat enabled. Both read your key from OPENAI_API_KEY, as in lesson 1. The Python import name is agents, not openai_agents.
An agent here is a model with instructions and tools that keeps working until a task is done. The SDK is OpenAI's library for building them, and its docs call it a production-ready upgrade of Swarm, the earlier experiment. It has few parts: agents, handoffs and agents as tools for delegation, guardrails for checks, and built-in tracing. Everything else is ordinary Python or TypeScript.
It is one of three ways OpenAI offers to build an agent, and the agents overview compares them. With the Responses API you write the loop yourself, as lessons 2 and 3 did. With the Agents SDK the loop runs inside your application, and your server keeps control of tools, storage and approvals. With the Agents API, OpenAI hosts the agent itself on a managed Codex setup for long tasks and saves its progress.
If you have used Anthropic's tools, the Claude Agent SDK lesson covers a library with a similar goal and a different design, and the agent loop lesson writes the loop by hand. The OpenAI SDK sits between the two: you define agents in code, and the runner owns the loop.
Agents SDK released
An orchestration framework for designing, building and deploying agents.
TypeScript version
The Agents SDK arrives for TypeScript.
Sandboxes and memory controls
Agents can run in controlled sandboxes, and you decide when memories are created and where they are stored.
The update reaches TypeScript
With sandbox agents and the open-source agent runtime.
Agents API in public beta
OpenAI hosts the agent on a managed Codex setup and handles sessions, compaction and recovery.
Source: OpenAI Docs: Changelog (read November 5, 2026)
Create and run your first agent
Give an agent named "History tutor" one instruction and the question "When did the Roman Empire fall?", and the quickstart says you should see a concise answer in the terminal. That is one run. The code below is the quickstart's Python version, copied as published.
Three names carry it. Agent holds the configuration: a name that shows up in traces and handoffs, the instructions, and the model. Runner.run takes a starting agent and an input, runs the loop and returns a result. result.final_output is the answer. In TypeScript the same program is new Agent({ ... }), then await run(agent, "When did the Roman Empire fall?"), then result.finalOutput.
The runner has three entry points. Runner.run is async, Runner.run_sync wraps it for plain scripts, and Runner.run_streamed hands you events while the run is still going. Streaming uses the same loop, so wait for the stream to finish before you treat the run as settled. An agent that leaves out model falls back to OPENAI_DEFAULT_MODEL when that variable is set, so a whole process can share one default. When the instructions depend on the current user or tenant, the definitions guide suggests a dynamic instructions callback instead of stitching strings together where you call the agent.
import asyncio
from agents import Agent, Runner
agent = Agent(
name="History tutor",
instructions="You answer history questions clearly and concisely.",
model="gpt-6-astra",
)
async def main() -> None:
result = await Runner.run(agent, "When did the Roman Empire fall?")
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())- Install the SDK with
pip install openai-agentsand exportOPENAI_API_KEY. - Save the code as
agent.py. - Run
python agent.pyand read the answer the agent prints. - Change the instructions to ask for one sentence only, and run it again.
- Find the run you just made among the traces on the OpenAI platform.
Give the agent tools and understand the loop
Tools are usually the first thing an agent needs. In Python, the @function_tool decorator turns a plain function into one. The SDK reads the function signature to build the JSON Schema and uses the docstring for the tool description and each argument's description. In the quickstart sample, "Return a short history fact." becomes the description the model sees.
Hosted tools work too, such as the web search and file search tools from lesson 2. In TypeScript a function tool is tool({ name, description, parameters, execute }), with the parameters written as a Zod object. A large tool set can stay hidden until needed: @function_tool(defer_loading=True) keeps a tool out of view until ToolSearchTool() loads it.
MCP servers plug in two ways. Hosted MCP tools suit public remote servers, and the model reaches them through OpenAI. Local or private servers connect from your own runtime over stdio or streamable HTTP, so your code owns the connection, filtering and approvals.
The runner then repeats one loop. It calls the current agent's model with the prepared input and looks at what came back. Tool calls get run, their results appended, and the loop goes around again. A handoff switches the current agent and goes around again. A final answer ends the run.
A final answer means text output of the expected type and no tool calls. With an output_type set to a Pydantic model or Zod schema, the expected type is that structure, built on the Structured Outputs feature from lesson 3. Without one, it is plain text.
Every pass counts as a turn, and the Python SDK caps a run at 10 turns by default. Past the cap, the runner raises MaxTurnsExceeded. Pass max_turns to raise or lower it, or max_turns=None to remove it. For a gentler stop, error_handlers accepts "max_turns", "model_refusal" and "invalid_final_output" and lets you return a controlled final output instead of an exception.
Some tools should not run without a person saying yes. Mark one with @function_tool(needs_approval=True) and the run pauses instead of calling it. The result then holds interruptions and a resumable state from result.to_state(). Your code calls state.approve(interruption) or rejects it, then resumes with Runner.run(agent, state). The guide treats that as the same run, not a new user turn, and the state can be stored while a person reviews it.
import asyncio
from agents import Agent, Runner, function_tool
@function_tool
def history_fun_fact() -> str:
"""Return a short history fact."""
return "Sharks are older than trees."
agent = Agent(
name="History tutor",
instructions="Answer history questions clearly. Use history_fun_fact when it helps.",
tools=[history_fun_fact],
)
async def main() -> None:
result = await Runner.run(
agent,
"Tell me something surprising about ancient life on Earth.",
)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())| What the model produced | What the runner does next |
|---|---|
| Text of the expected type, with no tool calls | Ends the loop and returns the result |
| One or more tool calls | Runs them, appends the results and calls the model again |
| A handoff to another agent | Makes that agent current and calls the model again |
A call to a tool marked needs_approval | Pauses and returns interruptions with a resumable state |
| Anything, once the turn limit is passed | Raises MaxTurnsExceeded |
Hand off to specialists and guard the run
Ask a "Homework triage" agent "Who was the first president of the United States?" with a history tutor and a math tutor listed as handoffs. The run ends with the history tutor's answer, and result.last_agent.name tells you which specialist gave it. The sample is the quickstart's own.
Behind it, each handoff is a tool. An agent named "Refund Agent" becomes a tool called transfer_to_refund_agent, and the triage agent calls it like any other tool. The handoff_description on each specialist tells the router when to pick it, so the guide asks for a short, concrete line. After a handoff, pass last_agent into the next turn when that specialist should stay in charge. Handoffs can also carry structured metadata or a filtered history, and the SDK docs for each language cover that wiring.
The orchestration guide frames the choice as a question of ownership. A handoff gives the conversation to the specialist. With agents as tools, agent.as_tool() in Python or agent.asTool() in TypeScript, a manager agent calls the specialist for a bounded job such as a summary and writes the final reply itself. The guide's advice is to start with one agent and split only when a branch needs different instructions, tools, policy or model.
Guardrails are automatic checks. An input guardrail validates what the user sent, an output guardrail checks the final answer, and a tool guardrail wraps a function tool's arguments or result. When a check fails, it trips a tripwire, and the runner raises InputGuardrailTripwireTriggered or its output twin and stops.
Input guardrails run in parallel with the agent by default, run_in_parallel=True, which saves time. If one trips, the agent may already have used tokens and run tools. Set run_in_parallel=False to make the check finish first when starting the agent is costly or risky. The guide's example does this with a homework check placed ahead of a support agent. Asked "Can you solve 2x + 3 = 11 for me?", the check trips, the runner raises the exception, and the sample prints "Guardrail blocked the request." The exception's guardrail_result names the guardrail that fired.
Scope matters for both kinds. Input guardrails run only for the first agent in the chain, and output guardrails only for the agent that produces the final output. Tool guardrails run on the function tools they are attached to. In a manager workflow, put the check next to the tool that causes the side effect.
import asyncio
from agents import Agent, Runner
history_tutor = Agent(
name="History tutor",
handoff_description="Specialist for history questions.",
instructions="Answer history questions clearly and concisely.",
)
math_tutor = Agent(
name="Math tutor",
handoff_description="Specialist for math questions.",
instructions="Explain math step by step and include worked examples.",
)
triage_agent = Agent(
name="Homework triage",
instructions="Route each homework question to the right specialist.",
handoffs=[history_tutor, math_tutor],
)
async def main() -> None:
result = await Runner.run(
triage_agent,
"Who was the first president of the United States?",
)
print(result.final_output)
print(result.last_agent.name)
if __name__ == "__main__":
asyncio.run(main())| Pattern | Use it when | Who writes the reply |
|---|---|---|
| Handoffs | A specialist should take over this branch of the conversation | The specialist |
| Agents as tools | A manager should stay in charge and call specialists for bounded tasks | The manager |
Remember conversations and read the traces
Ask "What city is the Golden Gate Bridge in?", then "What state is it in?", and the second question only makes sense with the first in memory. In the sessions page's example below, the comments give the docs' expected answers, "San Francisco" and then "California". The same SQLiteSession object is passed to every Runner.run call, and the SDK loads and saves the history around each run.
A session is the SDK's memory layer. It stores the items of one conversation under an id, so you stop replaying result.to_input_list() by hand. SQLiteSession("conversation_123") keeps it in memory, and a second argument, a file path such as "conversations.db", keeps it on disk. For long conversations, OpenAIResponsesCompactionSession wraps another session and compacts the stored history after each turn. It must not wrap OpenAIConversationsSession, since the two manage history in different ways.
Sessions are one of four ways to carry state, and the running guide asks you to pick one per conversation. The others are replaying the history yourself, a conversation_id from the Conversations API, and previous_response_id, all from lesson 1. A session cannot be combined with those server-side options in the same run, because mixing them can duplicate context.
Tracing is on by default. Each run records model calls, tool calls, handoffs, guardrails and any custom spans you add, and you read them in the Traces dashboard on the OpenAI platform. The observability guide suggests opening a trace as soon as the first run works, before you tune prompts.
There are three ways to turn tracing off. Set the environment variable OPENAI_AGENTS_DISABLE_TRACING=1, call set_tracing_disabled(True) in code, or set tracing_disabled on the RunConfig for one run. Organizations under a Zero Data Retention policy cannot use tracing at all. If you only want less of it, the observability guide prefers those SDK-level or per-run controls to removing all visibility. Traces also become the raw material for agent evaluations once you start scoring behavior.
from agents import Agent, Runner, SQLiteSession
# Create agent
agent = Agent(
name="Assistant",
instructions="Reply very concisely.",
)
# Create a session instance with a session ID
session = SQLiteSession("conversation_123")
# First turn
result = await Runner.run(agent, "What city is the Golden Gate Bridge in?", session=session)
print(result.final_output) # "San Francisco"
# Second turn - agent automatically remembers previous context
result = await Runner.run(agent, "What state is it in?", session=session)
print(result.final_output) # "California"
# Also works with synchronous runner
result = Runner.run_sync(agent, "What's the population?", session=session)
print(result.final_output) # "Approximately 39 million"| Session type | Suits |
|---|---|
SQLiteSession | Local development and simple apps, in memory or in a file |
RedisSession | Memory shared across workers and services |
SQLAlchemySession | Production apps with an existing database |
OpenAIConversationsSession | History stored by OpenAI through the Conversations API |
EncryptedSession | Encryption and expiry wrapped around another session |
What OpenAI Developers and Steve Coffey posted about the Agents SDK
OpenAI Developers introduced the Agents SDK on X on 11 March 2025, as an open-source SDK for orchestrating multi-agent workflows that improved on Swarm. The post named the parts this lesson uses: built-in tools, handoffs, guardrails and traces for debugging. Those four are still the core of the SDK, so learn them before you reach for anything newer.
Steve Coffey, who works on the API at OpenAI, posted on 15 April 2026 about what he called a Rather Large update. With it, he wrote, you can scale Codex-style agents in production without building all the scaffolding yourself, and computer use, skills, memory and compaction came along. If your agent needs long runs or a sandbox, read what that update added before you build your own agent runtime.
Configure agents with built-in tools, hand off tasks, add safety guardrails, and visualize execution traces for debugging and optimizing performance.

Screenshots of X's own embeds of the posts, taken on 5 November 2026, with the time in UTC. Select one to open the post on X. X cuts Steve Coffey's post short with Show more.
Common mistakes
- Designing five agents before one agent answers one question.
- Splitting into specialists that share the same tools and instructions.
- Using a handoff when the manager should keep writing the reply.
- Leaving a costly input guardrail in parallel mode and paying for work it later blocks.
- Expecting an input guardrail on the first agent to check calls made after a handoff.
- Treating an approval pause as a new user turn instead of resuming from state.
- Passing a session and a
previous_response_idin the same run. - Raising
max_turnsto hide a loop that never reaches a final answer.
Try it yourself
- Run the history tutor, then open its trace and find the model call.
- Add the
history_fun_facttool and ask a question that should use it. Does the trace show the call? - Give the triage agent a math question and print
result.last_agent.name. - Rewrite the math tutor as a tool with
as_tool()and compare who writes the final answer. - Set
max_turns=1on a run that needs a tool and catchMaxTurnsExceeded. - Add a
SQLiteSessionwith a file path, stop the script, start it again and ask a follow-up. - Mark a tool with
needs_approval=True, approve the interruption in code and check that the run resumes from its state.
Next
This is the last lesson of the OpenAI API course. The RAG tutorial opens the next course, which goes deeper into giving a model your own documents to search, beyond the hosted file search from lesson 2.
Agents become more useful once they can look things up in your own data. The tools, sessions and traces from this lesson carry straight over to that work.
Check yourself
A Python run goes past its turn limit. What happens by default?
Show answer
D. The runner raises MaxTurnsExceeded The default limit is 10 turns, and passing it raises MaxTurnsExceeded unless you set an error handler.
Which pattern keeps the manager agent in charge of the final reply?
Show answer
B. Agents as tools, with as_tool() With agents as tools, the manager calls the specialist for a bounded task and writes the reply itself.
An input guardrail keeps its default run_in_parallel setting and trips. What may already have happened?
Show answer
C. The agent used tokens and ran tools Parallel guardrails start with the agent, so work can happen before the tripwire stops it.
What keeps conversation history across Runner.run calls without manual replay?
Show answer
A. Passing the same SQLiteSession as session A session loads and saves the conversation items around each run.
Frequently asked questions
Can the Agents SDK use models from other providers?
Yes. The Python SDK README calls it provider-agnostic: it supports the OpenAI Responses and Chat Completions APIs and more than 100 other models, with integrations such as any-llm and LiteLLM. Provider setup differs by language, and the models guide points to each SDK's own docs for it.
Is there a TypeScript version of the Agents SDK?
Yes, @openai/agents, in the openai-agents-js repository. The names follow TypeScript style: run() instead of Runner.run, finalOutput, lastAgent, handoffDescription, outputType and asTool(). Function tools use tool() with Zod parameters, and the quickstart builds a router with Agent.create.
Does tracing record my users' data?
It can. Generation spans store the model's inputs and outputs, and function spans store tool inputs and outputs. If that data is sensitive, set trace_include_sensitive_data to false on the RunConfig to keep the spans without their content.
How do I give a tool my database client without showing it to the model?
Pass it as run context. Runner.run takes a context argument, and tools and guardrails receive it through a RunContextWrapper, so a database client, a logger or the signed-in user reaches your code and never enters the conversation. The definitions guide draws the line plainly: if the model needs a fact, put it in instructions, input or a tool, and if only your runtime needs it, keep it in local context.
Sources
- OpenAI Docs: Agents https://developers.openai.com/api/docs/guides/agents
- OpenAI Docs: Agents SDK https://developers.openai.com/api/docs/guides/agents/sdk
- OpenAI Docs: Agents SDK quickstart https://developers.openai.com/api/docs/guides/agents/quickstart
- OpenAI Docs: Agent definitions https://developers.openai.com/api/docs/guides/agents/define-agents
- OpenAI Docs: Agents SDK models and providers https://developers.openai.com/api/docs/guides/agents/models
- OpenAI Docs: Running agents https://developers.openai.com/api/docs/guides/agents/running-agents
- OpenAI Docs: Orchestration and handoffs https://developers.openai.com/api/docs/guides/agents/orchestration
- OpenAI Docs: Guardrails and human review https://developers.openai.com/api/docs/guides/agents/guardrails-approvals
- OpenAI Docs: Integrations and observability https://developers.openai.com/api/docs/guides/agents/integrations-observability
- OpenAI Agents SDK (Python): Running agents https://openai.github.io/openai-agents-python/running_agents/
- OpenAI Agents SDK (Python): Sessions https://openai.github.io/openai-agents-python/sessions/
- OpenAI Agents SDK (Python): Tracing https://openai.github.io/openai-agents-python/tracing/
- OpenAI Agents SDK (Python): Guardrails https://openai.github.io/openai-agents-python/guardrails/
- OpenAI Agents SDK (Python): Handoffs https://openai.github.io/openai-agents-python/handoffs/
- OpenAI Agents SDK (Python): Tools https://openai.github.io/openai-agents-python/tools/
- openai/openai-agents-python on GitHub https://github.com/openai/openai-agents-python
- openai/openai-agents-js on GitHub https://github.com/openai/openai-agents-js
- OpenAI Docs: Changelog https://developers.openai.com/api/docs/changelog
- X: OpenAI Developers introduce the Agents SDK https://x.com/OpenAIDevs/status/1899531857143972051
- X: Steve Coffey on the April 2026 Agents SDK update https://x.com/stevendcoffey/status/2044465818239701041
- YouTube (OpenAI): Build Hour: Agents SDK https://www.youtube.com/watch?v=tK32trvj_b4


