Agent SDKs: hosted tools, function tools, agents as tools
Build a team of agents with the OpenAI Agents SDK using hosted tools, function tools, agents as tools, handoffs and an MCP server, and compare it with the Claude Agent SDK.
Words in this lessonAgent SDK · Hosted tool · Function tool · Agent as a tool · Handoff · Claude Agent SDK · Agent
- Agent SDK
- A library that packages the agent loop with building blocks like tools, handoffs, guardrails and tracing. Like a fitted kitchen with stations and equipment, instead of building every cupboard yourself.
- Hosted tool
- A tool that runs on the model provider's own servers, such as built-in web search or file search. Like the bank's card machine on the shop counter: it works, but it belongs to someone else.
- Function tool
- One of your own functions made available for an agent's model to call. Like the shop's own order book, which only your staff keep and read.
- Agent as a tool
- Wrapping a whole agent so another agent can call it like a function and get its answer back. Like the counter assistant shouting a question to the head baker and relaying the answer.
- Handoff
- Passing a conversation from one agent to another, which then takes over completely. Like a receptionist transferring your call to the right department and hanging up.
- Claude Agent SDK
- Anthropic's SDK that gives you the agent harness behind Claude Code to build your own agents. Like hiring an experienced assistant who already knows the office tools, then giving them your house rules.
- Agent
- A model running in a loop that decides its own next step, uses tools, checks results and keeps going until a task is done. Like a personal assistant you hand a goal, who makes the calls and checks back, rather than one you direct step by step.
On this page
- How the real bakery does it
- What an agent SDK actually gives you
- Following one message through a team of agents
- Opening the hood: the four building blocks
- Hosted tools
- Function tools
- Agents as tools
- Handoffs
- The real code, built up step by step
- Step 1: one agent, one function tool
- Step 2: a specialist, used as a tool
- Step 3: a handoff target with a tight policy
- Step 4: run it
- Step 5: plug in the lab's MCP server
- Step 6 (optional): a hosted tool
- The Claude Agent SDK, conceptually
- Where it breaks
- Try it yourself
- Common mistakes
- Recap
- Go deeper
Meera forwarded a WhatsApp transcript to Arjun with a single word: “Explain.”
A customer had written that her Classic Diwali Hamper arrived with a cracked jar of pickle. Crumb’s support bot apologised and promised a full refund of ₹1,499, and a replacement hamper. Crumb’s policy for one damaged item is a replacement of that item, or a ₹200 credit. The hamper itself had been fine. The bot had given away about ₹1,700 of hamper on a ₹150 jar.
Arjun knew why before he finished reading. The bot had grown one tool at a time since Module 06. It now had 14 tools (order lookup, stock, allergens, delivery tracking, refunds, store hours and more) and a 4,800-token system prompt that tried to cover everything: refund rules, allergy rules, delivery rules, tone. His last attempt to fix refunds had been to add a line in capitals: NEVER PROMISE A FULL REFUND FOR A PARTIAL DAMAGE. It had not worked. In his test log, the bot picked the wrong tool about one time in twelve.
“You’ve built one employee who has to know everything and do everything,” Priya said. “How does the actual bakery handle this?”
Important
The idea in one sentence: An agent SDK gives you the agent loop plus four kinds of building blocks: hosted tools the model provider runs for you, function tools that run your own code, other agents used as tools (they answer and hand control back), and handoffs (they take over the conversation), so you can build a team of small, focused agents instead of one overloaded one.
How the real bakery does it
“Walk me through what happens at the Baner counter,” Priya said, “when a customer comes in with a broken jar and a question about nuts.”
Arjun thought about it. “Lata at the counter listens first. She can check the order book herself, and she can swipe a card on the payment machine. But she can’t approve refunds. For that she calls Mr Deshpande, the store manager, over, and he takes it from there.”
“And the nut question?”
“If it’s tricky, she shouts across to Anjali, the head baker, ‘Does the replacement hamper have cashews?’ Anjali answers, and Lata tells the customer.”
“Who is talking to the customer at that point?”
“Still Lata. Anjali never comes to the counter.”
“And after Mr Deshpande comes over?”
“He is. Lata goes back to the queue.”
Priya drew it as four boxes, and those four boxes are the whole lesson:
| At the Baner store | In an agent SDK | What it means |
|---|---|---|
| The card machine, provided by the bank | Hosted tool | A tool the model provider runs on its own servers, like web search |
| The store’s order book | Function tool | A tool that runs your Python function |
| Asking Anjali a question across the room | Agent as a tool | One agent calls another like a tool, gets an answer, and stays in charge |
| Calling Mr Deshpande over to take the customer | Handoff | One agent passes the whole conversation to another, who takes over |
“Your bot,” Priya said, “is Lata trying to be the bank, the manager and the baker at the same time.”
What an agent SDK actually gives you
You have built agent loops by hand since Module 06: call the model, check for tool calls, run them, send the results back, repeat. An agent SDK is a library that packages that loop and adds the pieces every real agent ends up needing. The OpenAI Agents SDK, which this lesson uses, gives you:
- an
Agent: a name, instructions (its system prompt), a model, and its tools and handoffs; - a
Runner: the loop that runs an agent until it produces a final answer; - tools of several kinds, including tools from MCP servers like the one from the lab;
- handoffs between agents;
- guardrails (checks that run on inputs or outputs), sessions (conversation memory), and tracing (a record of every step, viewable in a dashboard).
The model providers have converged on these ideas, so what you learn here carries over. Anthropic’s Claude Agent SDK, which we compare at the end, shares the same concepts with a different emphasis.
Following one message through a team of agents
Arjun rebuilt the support bot as three small agents:
- Triage talks to the customer first. It can look up orders. It hands off refund and damage cases.
- Refunds handles refunds, replacements and damaged items, with Crumb’s exact policy in its instructions. It can look up orders and ask the allergy expert.
- Allergy expert answers ingredient and allergen questions, using the
check_allergenstool from the lab’s MCP server. It never talks to customers directly.
Then he and Priya traced the same kind of message that had caused the disaster:
“My hamper (order A1001) arrived with a cracked pickle jar. I want this sorted. Also, does the replacement have any nuts? My son is allergic to peanuts.”
| Step | Who is in charge | What happens | Approx. tokens |
|---|---|---|---|
| 1 | Triage | Reads the message. Sees “cracked jar”, “sorted”. Calls the handoff tool transfer_to_refunds. |
1,100 |
| 2 | Refunds | Now owns the conversation and sees the whole history. Calls function tool lookup_order("A1001") → “1 × Classic Diwali Hamper, delivered 28 Oct”. |
1,400 |
| 3 | Refunds | Policy says: damaged item → replace that item or ₹200 credit. Needs the nut answer, so calls tool allergy_expert("Does the Classic hamper's pickle replacement contain peanuts or other nuts?"). |
1,600 |
| 3a | Allergy expert (inside the tool) | Runs as a separate, short agent run. Calls the MCP tool check_allergens("mango pickle") on Crumb’s full-menu version of the lab server → “Mango Pickle contains: mustard.” Answers: “The pickle contains no nuts. Note: the hamper’s kaju katli contains cashews and may contain peanuts.” |
900 + 700 |
| 4 | Refunds | Writes the final reply: a replacement pickle jar today or a ₹200 credit, the customer’s choice; the pickle is nut-free; and a clear warning about the kaju katli. | 1,900 |
Five model calls, about 7,600 tokens, about 7 seconds end to end: roughly three times the tokens of the old single-call bot. But the refund went from about ₹1,700 given away to a ₹200 credit or a jar of pickle, and the peanut warning appeared because a specialist whose only job is allergies looked at the question.
Two details in that trace are the heart of the lesson.
After step 1, Triage is gone. It made one decision and handed over. The final answer comes from Refunds, and if the customer replies, Refunds is still the one listening. That is a handoff.
In step 3, the allergy expert never speaks to the customer. Refunds asked it a question, got an answer back as a tool result, and carried on. That is an agent used as a tool.
Note
Pause and think. Why did Arjun make the allergy expert a tool of the Refunds agent instead of another handoff target?
Show the answer
Because the customer’s main problem is the refund, and Refunds should stay in charge of it. The nut question is a sub-question whose answer Refunds needs to fold into its reply. With a handoff, the allergy expert would take over the whole conversation, answer the nut question, and then nobody would finish the refund. Use a handoff when someone else should own the conversation from now on; use an agent as a tool when you need an answer and want to stay in charge.
Opening the hood: the four building blocks
Hosted tools
A hosted tool runs on the model provider’s servers, not yours. With the OpenAI Agents SDK these include WebSearchTool (search the web), FileSearchTool (search documents you uploaded into an OpenAI vector store), a code interpreter and more. You add them to an agent’s tool list, and when the model uses one, the search or code execution happens inside OpenAI’s platform. Your code never sees the individual steps, only the result folded into the model’s answer.
The trade-off: zero code to write, but you are tied to that provider (these tools only work with OpenAI’s models through its Responses API), you pay per use on top of tokens, and your data passes through their service. They are the bank’s card machine: convenient, reliable, and not yours.
Function tools
A function tool is one of your Python functions, made available to the model. The SDK reads the function’s name, type hints and docstring to build the tool’s description and input schema, exactly as FastMCP did in the lab. When the model asks for it, the Runner calls your function in your process and sends back the return value. This is where Crumb’s own systems, policies and databases come in.
Tools from MCP servers behave the same way from the agent’s point of view. You attach a server to an agent, the SDK lists its tools, and they sit in the tool list next to your function tools. The difference is where the code runs: in the MCP server, which any other MCP host can also use.
Agents as tools
An agent as a tool wraps a whole agent so another agent can call it like a function: input in, answer out. Inside, it is a full agent run with its own instructions, tools and model calls. The outer agent only sees the final answer. Use it when you want a specialist’s judgement on a sub-question, or want to split a big job into focused parts, while one agent stays responsible for the conversation. You met this pattern as orchestrator and workers in Module 13, and as subagents in the deep agents lesson of Module 15.
Handoffs
A handoff transfers control. Under the hood, the SDK gives the current agent a special tool, named like transfer_to_refunds. When the model calls it, the Runner switches the current agent to Refunds and continues the loop with Refunds’ instructions and tools. By default the new agent sees the conversation so far, so the customer does not have to repeat themselves. Use handoffs to route a conversation to the right specialist, especially when that specialist needs different instructions, tools or permissions.
flowchart LR
U["Customer message"] --> T["Triage agent"]
T -->|"handoff: transfer_to_refunds"| R["Refunds agent"]
R -->|"function tool"| O["lookup_order"]
R -->|"agent as tool"| A["Allergy expert"]
A -->|"MCP tool"| M["check_allergens"]
R --> F["Final reply"]
| Agent as a tool | Handoff | |
|---|---|---|
| Who talks to the user afterwards | The calling agent | The new agent |
| What comes back | An answer, as a tool result | Nothing: control moves |
| Good for | Sub-questions, expert opinions, parallel subtasks | Routing a conversation to a specialist |
| At the bakery | Asking the baker across the room | Calling the manager to the counter |
Note
Pause and think. Triage itself has a lookup_order tool, and so does Refunds. Isn’t that duplication? Why not give every tool to every agent?
Show the answer
A little overlap is fine when several agents genuinely need the same information. But giving every agent every tool recreates the original problem: long tool lists confuse the model and raise the chance of the wrong choice. It also matters for safety. Only Refunds should hold anything that can issue credits; if Triage cannot reach that tool, no confusing message can make Triage give money away. Small agents with small toolsets are easier to test, cheaper to run per call and safer.
The real code, built up step by step
Install the SDK with uv add openai-agents python-dotenv, and put OPENAI_API_KEY in your .env. Model names change often, so we read one from the environment; check OpenAI’s models page for current names.
Step 1: one agent, one function tool
import os
from agents import Agent, Runner, function_tool
from dotenv import load_dotenv
load_dotenv()
MODEL = os.getenv("OPENAI_MODEL", "gpt-5-mini")
ORDERS = {"A1001": "1 x Classic Diwali Hamper (Rs 1,499), delivered 28 Oct."}
@function_tool
def lookup_order(order_id: str) -> str:
"""Look up what was in a Crumb order and when it was delivered.
Args:
order_id: The order ID, like "A1001".
"""
return ORDERS.get(order_id.upper(), f"No order {order_id}.")
triage = Agent(
name="Triage",
instructions="You are Crumb's front-desk assistant. Be brief and kind.",
tools=[lookup_order],
model=MODEL,
)
result = Runner.run_sync(triage, "What was in order A1001?")
print(result.final_output)
@function_tool does what @mcp.tool() did in the lab: name, type hints and docstring become the tool definition. Runner.run_sync runs the loop (model, tool, model) until the agent produces a final answer, available as result.final_output. You should see something like: “Order A1001 was one Classic Diwali Hamper, delivered on 28 October.”
Step 2: a specialist, used as a tool
allergy_expert = Agent(
name="Allergy expert",
instructions=(
"You answer questions about allergens in Crumb products. "
"Be precise. If you are not sure, say so. Never guess."
),
model=MODEL,
)
allergy_tool = allergy_expert.as_tool(
tool_name="allergy_expert",
tool_description="Ask Crumb's allergy expert a question about ingredients or allergens.",
)
as_tool wraps the whole agent as a tool. When another agent calls allergy_expert, the SDK runs the expert’s own loop and returns its final answer as the tool result.
Step 3: a handoff target with a tight policy
refunds = Agent(
name="Refunds",
handoff_description="Handles refunds, replacements and damaged or missing items.",
instructions=(
"You handle refunds for Crumb & Co.\n"
"Policy: one damaged or missing item -> replace that item or a Rs 200 credit, "
"customer's choice. Whole order damaged -> full refund. Never promise more.\n"
"Always look up the order first. For any ingredient question, ask allergy_expert."
),
tools=[lookup_order, allergy_tool],
model=MODEL,
)
triage = Agent(
name="Triage",
instructions=(
"You are Crumb's front-desk assistant. Answer simple questions yourself. "
"For refunds, replacements or damaged items, hand off to Refunds."
),
tools=[lookup_order],
handoffs=[refunds],
model=MODEL,
)
handoff_description is what Triage reads when deciding whether to hand off, so write it like a tool description. Notice the refund policy now lives in one short prompt, read only by the agent that needs it, instead of buried in a 4,800-token prompt shared by everything.
Step 4: run it
message = (
"My hamper (order A1001) arrived with a cracked pickle jar. I want this sorted. "
"Also, does the replacement have any nuts? My son is allergic to peanuts."
)
result = Runner.run_sync(triage, message)
print("Answered by:", result.last_agent.name)
print(result.final_output)
You should see Answered by: Refunds, then a reply offering a replacement jar or a ₹200 credit, plus an answer about nuts. result.last_agent tells you who ended up owning the conversation. By default, the SDK also records a trace of every model call, tool call and handoff, which you can open in the OpenAI dashboard, the fastest way to see why the agents did what they did.
Step 5: plug in the lab’s MCP server
The allergy expert should not be guessing ingredients from its training data. Give it the real check_allergens tool from your lab server:
import asyncio
from agents.mcp import MCPServerStdio
CRUMB_MCP = "/absolute/path/to/crumb-mcp" # the lab's project folder
async def main() -> None:
async with MCPServerStdio(
params={"command": "uv", "args": ["--directory", CRUMB_MCP, "run", "server.py"]},
name="crumb",
) as crumb:
allergy_expert = Agent(
name="Allergy expert",
instructions="Answer allergen questions using check_allergens. Never guess.",
mcp_servers=[crumb],
model=MODEL,
)
result = await Runner.run(allergy_expert, "Does the walnut brownie contain peanuts?")
print(result.final_output)
asyncio.run(main())
The SDK starts your server, performs the MCP handshake, lists its tools and offers them to the model, all from mcp_servers=[crumb]. Running the server from its own project folder with uv --directory keeps its dependencies separate from the agent’s, which matters because the two projects may pin different versions of the mcp package. MCP connections are asynchronous, so this step uses await Runner.run(...) inside async code instead of run_sync.
Step 6 (optional): a hosted tool
from agents import FileSearchTool, WebSearchTool
researcher = Agent(
name="Market researcher",
instructions="Answer questions about bakery trends in Pune, citing sources.",
tools=[WebSearchTool()],
model=MODEL,
)
One line, and the agent can search the web; OpenAI runs the search. FileSearchTool(vector_store_ids=["vs_..."], max_num_results=3) works the same way over documents you have uploaded to an OpenAI vector store, a hosted version of the RAG you built in Module 09.
Library APIs change quickly. If a name has moved, check the docs linked below.
The Claude Agent SDK, conceptually
Anthropic offers its own agent SDK, and it is worth understanding how it differs.
The Claude Agent SDK (Python package claude-agent-sdk) gives you the same agent harness that powers Claude Code. Where the OpenAI Agents SDK starts nearly empty and you add tools, the Claude Agent SDK starts with an agent that can already use a computer: built-in tools to read, write and edit files, run shell commands, search files and fetch web pages. You shape it with a system prompt, decide which built-in tools it may use, attach your own tools and MCP servers, define subagents (its version of agents as tools, each with its own instructions and context), add hooks (your code that runs before or after tool calls, for example to block a dangerous command), and choose a permission mode that decides what it may do without asking.
| OpenAI Agents SDK | Claude Agent SDK | |
|---|---|---|
| Starting point | A light toolkit: agents, tools, handoffs, guardrails | A ready-made, computer-using agent: the Claude Code harness |
| Built-in tools | Hosted tools on OpenAI’s platform (web search, file search, code interpreter) | Local tools: files, shell, search, web fetch |
| Splitting work | Handoffs and agents as tools | Subagents |
| Your own tools | Function tools and MCP servers | Custom tools and MCP servers |
| Best fit | Customer-facing assistants, routing, multi-agent workflows | Coding agents, research and document work, automation over files |
Both connect to MCP servers, which is the practical payoff of the last three lessons: the crumb server works with either. Other frameworks, including LangGraph from Module 14, express the same ideas in their own vocabulary. Learn the concepts once, and switching frameworks becomes a matter of syntax.
Where it breaks
Handoff ping-pong. Arjun’s first version let Refunds hand back to Triage “for anything that isn’t a refund”. A customer asked about both a refund and store hours; the agents passed the conversation back and forth until the Runner stopped them with a MaxTurnsExceeded error (the default limit is 10 turns). Make handoffs mostly one-way, and give the specialist enough tools to finish the job.
More agents, more cost and delay. Each agent hop is another model call. Five calls for the damaged-jar message was right; five calls for “what time do you open?” would be waste. Let Triage answer simple things itself.
Context on handoff. By default the new agent sees the whole conversation. That is usually right, but it can mean passing irrelevant or sensitive history along. Handoffs accept an input filter that trims what the next agent sees.
Money-moving tools need a human. When Arjun added a real issue_credit tool to Refunds, he marked it to require approval (@function_tool(needs_approval=True)), so the run pauses until a staff member confirms. A short policy prompt makes mistakes rarer. It does not make them impossible.
Tracing sends data to the provider. The SDK’s built-in tracing uploads a record of each run to OpenAI by default. For customer conversations, check that this fits your privacy rules; you can switch it off with set_tracing_disabled(True) or the OPENAI_AGENTS_DISABLE_TRACING=1 environment variable, or send traces to your own system (Module 22 covers observability).
Version drift. In Step 5 the server’s log may show a long warning about an unrecognised server/discover request: the SDK’s newer MCP client trying the stateless 2026-07-28 revision first, then falling back to the lab server’s older handshake. Harmless, but both sides of MCP evolve.
Try it yourself
Add a third specialist, Delivery, which handles “where is my order?” questions using a new function tool track_delivery(order_id) that returns a made-up status. Make it a handoff target of Triage. Then run three messages through Triage and print result.last_agent.name for each:
- “What time does the Baner store close?”
- “Where is order A1001? It was meant to arrive at 6.”
- “Order A1001 arrived but the brownies were missing.”
Show the answer
You should see roughly:
Triage: a simple question it can answer itself (or say it does not know, if you have not given it store hours).Delivery: a tracking question, handed off.Refunds: a missing item is a refund or replacement case.
If message 2 ends at Refunds or message 3 at Delivery, the handoff_description values are not distinct enough. Rewrite them to say exactly when each agent should be chosen (“Late, missing or in-transit deliveries: tracking only, no refunds”). Handoff descriptions are prompts, and they need the same care.
Common mistakes
- One agent with every tool. Split by responsibility; give each agent only the tools it needs.
- Handoff when you meant agent-as-tool. If you need an answer and want to stay in charge, use
as_tool. - Two-way handoffs everywhere. They create loops. Prefer one-way routing to specialists who can finish the job.
- Using hosted tools without noticing the lock-in. They are convenient, tied to one provider, and billed separately.
- Letting an agent move money unsupervised. Require human approval for anything irreversible.
Recap
- An agent SDK packages the agent loop with agents, a runner, tools, handoffs, guardrails, sessions and tracing.
- Hosted tools run on the provider’s platform; function tools run your code; MCP tools come from MCP servers and behave like function tools.
- An agent as a tool answers a question and hands control back; a handoff passes the whole conversation to another agent.
- Small agents with focused instructions and tools beat one overloaded agent, at the cost of more model calls.
- The Claude Agent SDK offers the same ideas starting from a computer-using agent (the Claude Code harness), with subagents, hooks and permission modes.
Arjun explained the new bot to Meera using the Baner store. “Lata at the counter, Mr Deshpande for refunds, Anjali in the kitchen for ingredients. The front desk hands refund cases to the manager, who owns them from then on. The manager can shout a question to the baker without the baker ever coming to the counter. And the refund policy lives with the manager, nowhere else.”
Meera read the new transcript: a ₹200 credit, a nut-free pickle, and an unprompted warning about the kaju katli. “That’s what Mr Deshpande would have said.” Then she sighed. “I wish we had one of these for our code. Priya spends half her week reviewing pull requests, and last month a bug in the coupon code cost us real money.” Arjun smiled. That was the project.
Go deeper
- OpenAI Agents SDK documentation, covering agents, tools, handoffs, MCP, guardrails and tracing.
- Anthropic documentation, including the Claude Agent SDK and Claude Code.
- Building effective agents, on when to split work across agents and when not to.
- Model Context Protocol documentation, for the servers your agents connect to.