Multi-agent workflows can be built in two different ways in OpenAI's Agents SDK. The first is agents-as-tools which follows an orchestrator-subagent pattern. The second is using handoffs which allow agents to pass control over to other agents. In this example, we'll build both types of multi-agent systems exploring agents-as-tools and handoffs.
First let's set our OPENAI_API_KEY
which we'll be using throughout the example. You can get a key from the OpenAI Platform.
Orchestrator-Subagent
We will build a multi-agent system structured with a orchestrator-subagent pattern. The orchestrator in such a system refers to an agent that controls which subagents are used and in which order, this orchestrator also handles all in / out communication with the users of a the system. The subagent is an agent that is built to handle a particular scenario or task. The subagent is triggered by the orchestrator and responds to the orchestrator when it is finished.

Sub Agents
We'll begin by defining our subagents. We will create three subagents, those are:
-
Web Search Subagent will have access to the Linkup web search tool.
-
Internal Docs Subagent will have access to some "internal" company documents.
-
Code Execution Subagent will be able to write and execute simple Python code for us.
Lets start with our first subagent!
Web Search Subagent
The web search subagent will take a user's query and use it to search the web. The agent will collect information from various sources and then merge that into a single text response that will be passed back to our orchestrator.
OpenAI's built-in web search is not great, so we'll use another web search API called LinkUp. This service does require an account, but you will receive more than enough free credits to follow the course.
We initialize our Linkup client using an API key like so:
We perform an async search like so:
We can parse out the results like so:
Let's put together a @function_tool
using Linkup:
Now we define our Web Search Subagent:
We can talk directly to our subagent to confirm it works:
Great! Now let's move onto our next subagent.
Internal Docs Subagent
In many corporate environments, we will find that our agents will need access to internal information that cannot be found on the web. To do this we would typically build a Retrieval Augmented Generation (RAG) pipeline, which can often be as simple as adding a vector search tool to our agents.
To support a full vector search tool over internal docs we would need to work through various data processing and indexing steps. Now, that would add a lot of complexity to this example so we will create a "dummy" search tool for some fake internal docs.
Our docs will discuss revenue figures for our wildly successful AI and robotics company called Skynet - you can find the revenue report here.
Now we define our Internal Docs Subagent:
Let's confirm it works:
Perfect! Now onto our final subagent.
Code Execution Subagent
Our code execution subagent will be able to execute code for us. We'll focus on executing code for simple calculations but it's entirely feasible for State-of-the-Art (SotA) LLMs to write far more complex code as many of us will be aware with the AI code editors becoming increasingly prominent.
To run generated code, we will use Python's exec
method, making sure to run our code in an isolated environment by setting no global variables with namespace={}
.
Now lets define our Code Execution Subagent. We will use gpt-4.1
rather than gpt-4.1-mini
to maximize performance during code writing tasks.
We can test our subagent with a simple math question:
We now have all three subagents - it's time to create our orchestrator.
Orchestrator
Our orchestrator will control the input and output of information to our subagents in the same why that our subagents control the input and output of information to our tools. In reality, our subagents become tools in the orchestrator-subagent pattern. To turn agents into tools we call the as_tool
method and provide a name and description for our agents-as-tools.
We will first define our instructions for the orchestrator, explaining it's role in our multi-agent system.
Now we define the orchestrator
, including our subagents using the as_tool
method — note that
we can also add normal tools to our orchestrator.
Let's test our agent with a few queries. Our first query will require our orchestrator to call multiple tools.
We should see in our traces dashboard on the OpenAI Platform that our agent used both internal_docs_agent
and get_current_date
tools to answer the question.
Let's ask another question:
Our orchestrator-subagent workflow is working well. Now we can move on to handoffs.
Handoffs
When we use handoffs in Agents SDK the agent is handing over control of the entire workflow to another agent. Handoffs differ to the orchestrator-subagent pattern, with orchestrator-subagent the orchestrator retains control as each subagent must ultimately respond to the orchestrator and the orchestrator decides the flow of information and generates the final response to the user. With handoffs, once a "subagent" gains control of the workflow the flow of information and final answer generation is under their control.

Using the handoff structure, any one of our agents may answer the user directly and our subagents get to see the entire chat history with the steps taken so far.
A significant positive here is latency. To answer a query that requires a single web-search with the orchestrator-subagent, we would need three generations:
The same query with the handoff structure requires just two generations (note, the orchestrator
and main_agent
are essentially the same):
Because we are using less LLM generations to produce our answer, we can generate an answer much more quickly as we skip the return trip through the orchestrator
.
The handoff speed improvement is great but also results in a negative — our workflow can no longer handle queries that require multiple agents to answer. When deciding what structure to use for a particular use-case, the pros and cons of each structure will need to be considered.
Let's jump into implementing our handoff agents workflow.
Using Handoffs
There are three key things that we need to use when defining our main_agent
(equivalent to our earlier orchestrator
agent), those are:
- Update our
instructions
prompt to make it clear what the handoffs are and how they should be used. OpenAI provides a default prompt prefix that we can use. - Set the
handoffs
parameter, which is a list of agents that we can use as handoffs. - Set the
handoff_description
parameter, this is an additional prompt where we should describe to themain_agent
when it should use thehandoffs
.
First, let's check the preset prompt prefix:
Now let's define our main_agent
:
We'll run the same queries as before and see how the response time differs.
That's correct, we also got a 6.4s
runtime vs. the orchestrator-subagent runtime of 7.5s
for the same query. Let's try another:
The answer is correct again, and we get a runtime of 7.6s
vs. the orchestrator-subagent runtime of 8.6s
, another notable improvement to latency.
Other Handoff Features
There are a few other handoff-specific features that we can use, these can be used for various things but are particularly useful during development and debugging of multi-agent workflows. These features are:
on_handoff
is a callback executed whenever a handoff occurs. It could be used in a production setting to maintain a record of handoffs in a DB or used in telemetry. In development this can be a handy place to addprint
orlogger.debug
statements.input_type
allows us to define a specific structured input format for generated information that will be passed to our handoff agents.input_filter
allows us to restrict the information being passed through to our handoff agents.
We can set all of these via a handoff
object, which wraps around our handoff agents and which we then provide via the Agent(handoffs=...)
parameter. Let's start with the on_handoff
parameter:
Now let's see what happens when querying the main_agent
:
Now we can see if and when the handoff occurs. However, we don't get much information other than that the handoff occured. Fortunately, we can use the input_type
parameter to provide more information. We will define a pydantic BaseModel
with the information that we'd like to include.
Now call the main_agent
:
We're now seeing much more information. The final handoff feature we will test is the handoff_filters
. The filters work by removing information sent to the handoff agents. By default, all information seen by the previous agent will be seen by the new handoff agent. That includes all chat history message and all tool calls made so far.
In some cases we may want to filter this information. For example, with a weaker LLM, too much information can reduce it's performance so it is often a good idea to only share the information that is absolutely necessary.
If we have various tool calls in our chat history, these may confuse a smaller LLM. In this scenario, we can filter all tool calls from the history using the handoff_filters.remove_all_tools
method:
Now when asking for the time difference we will see that our handoff agent is unable to give us an accurate answer. This is because the current time is first found by our main_agent
via the get_current_date
tool and that information is stored in the chat history. When we filter tool calls out of the chat history this information is lost.
We should see an incorrect date above.
That's it for this deep dive into multi-agent systems with OpenAI's Agents SDK. We've covered a broad range of multi-agent features in the SDK and how we can use them to build orchestrator-subagent workflows, or handoff workflows. Both having their own pros and cons.