1 Recap & motivation: why we want an Agent SDK
Last time, building an agent was like making a toy robot from a box of loose wires, motors, and screws. Powerful โ you control everything โ but slow and easy to get wrong. An Agent SDK is like a LEGO Technic kit instead: the wheels, the motor, and the steering already come as ready-made pieces that snap together. You build the same robot, but much faster and with far fewer wires sticking out.
A quick recap of where we've been
From Session 3 you already know the heart of an agent:
- An agent is an LLM that can act โ it can call tools (functions, APIs, searches) to get things done, not just chat.
- It runs in a loop sometimes called the agent loop (or "ReAct" style): think โ call a tool โ read the result โ think again โ โฆ โ answer.
- It keeps going until it decides it has enough to give a final answer.
From Session 6 you know one concrete way to build this: LangChain (building blocks for prompts, models, and tools) and LangGraph (a way to express the agent as an explicit graph of nodes and edges, with a shared state object flowing through it).
What's hard about building agents directly in LangGraph
LangGraph is brilliant and very flexible โ but that flexibility has a cost. When all you want is a "normal" agent that calls a few tools, LangGraph asks you to spell out a lot of low-level machinery yourself.
| Pain point | What it means in practice |
|---|---|
| Lower-level by design | You think in nodes, edges, and a graph. That's the right altitude for complex flows, but overkill for "an agent that uses 3 tools." |
| Boilerplate | You define a state schema, write the model-calling node, write the tool-executing node, add conditional edges to decide "loop again or stop," and wire it all up โ before your agent does anything useful. |
| You build the loop | The "call model โ run tools โ feed results back โ repeat" loop isn't free; you assemble it from nodes and edges (or use a prebuilt helper and then customise). |
| Multi-agent is manual | Routing between specialised agents (a "triage" agent sending work to others) means more nodes, more edges, more state plumbing. |
| Easy to get subtly wrong | Forget an edge or mishandle state and your agent loops forever, drops a tool result, or never stops. These bugs are fiddly. |
LangGraph's low level is the point โ when you genuinely need custom control flow, cycles, branching, human-in-the-loop pauses, that explicit graph is exactly what you want. The issue is only that for the common "agent + tools (+ maybe a few sub-agents)" case, it makes you write a lot that could be handed to you for free.
Most agents share the same shape (an LLM, some tools, a loop, maybe a few sub-agents). An Agent SDK packages that shape as ready-made parts, so you write your logic โ not the plumbing โ and only drop to a lower-level framework when you truly need custom control.
2 What is an Agent SDK? (the OpenAI Agents SDK)
Imagine a kitchen where the four most-used gadgets โ a chef, a recipe card, a set of knives, and a way to call another chef for help โ already sit on the counter, ready to use. An Agent SDK is that pre-stocked kitchen. The four gadgets have names: Agents, Tools, Handoffs, and a Runner that actually does the cooking.
An Agent SDK is a purpose-built software library whose only job is to help you build LLM agents. Instead of generic building blocks, it gives you a small set of primitives (ready-made core concepts) that map directly onto how agents actually work. Our running example is the OpenAI Agents SDK, a lightweight, open-source Python library (the successor to OpenAI's earlier "Swarm" experiment). It's deliberately tiny โ few concepts, fast to learn โ yet enough to build real multi-agent apps. (It's also provider-flexible: although it's from OpenAI, it can drive many different LLMs, not only OpenAI's.)
The core primitives
| Primitive | What it is | Maps to (from earlier sessions) |
|---|---|---|
| Agent | An LLM bundled with instructions (its system prompt / personality), a list of tools it may call, and optional extras like which sub-agents it can hand off to and its output type. | The "agent" idea from Session 3, now a single configured object. |
| Tool | A capability the agent can invoke โ most commonly a plain Python function the SDK auto-wraps so the model can call it. | Tools from Session 3 โ but you skip writing the schema by hand. |
| Handoff | A special action that lets one agent delegate the conversation to another, more specialised agent. | New here โ like routing between LangGraph nodes, but as a first-class feature. |
| Run (Runner) | The engine that actually executes an agent: it runs the loop, performs the tool calls, follows handoffs, and returns the result. | The agent loop from Session 3 โ now run for you. |
Guardrails are checks that run alongside your agent to validate inputs/outputs and "fail fast" if something looks wrong (e.g. a user asking something off-policy). Sessions automatically keep conversation history across turns so you don't re-thread it by hand. Both build on the four core primitives โ we'll meet tracing and threads properly in Session 8.
Here's the whole "hello world" of the OpenAI Agents SDK โ an agent with instructions, and a runner that executes it:
from agents import Agent, Runner agent = Agent( name="Assistant", instructions="You are a friendly helper. Answer concisely.", ) result = Runner.run_sync(agent, "Write a haiku about recursion.") # print the agent's final answer print(result.final_output)
No graph, no state schema, no loop wiring. The Agent holds the
config; Runner.run_sync does the work and hands back a result object
whose final_output is the answer.
An Agent SDK trades flexibility for speed and clarity on the common path. Learn four small nouns โ Agent, Tool, Handoff, Run โ and you can describe almost any agent system. The SDK fills in the loop, the tool plumbing, and the routing.
3 The agent lifecycle: how a single run executes
Picture a smart helper doing your homework. They read the question, maybe grab a calculator (a tool), look at the calculator's answer, decide if they're done, and if not, grab another tool โ over and over โ until they can finally write the answer at the bottom of the page. The run loop is exactly that "do I need a tool, or am I done?" cycle, repeated until the helper writes the final answer.
When you call Runner.run(...), the SDK executes an
agent loop on your behalf. This is the same think-act-observe loop from
Session 3 โ but you don't build it; the Runner does. Here's what happens on each turn.
The run loop, step by step
More precisely, each iteration of the loop does this:
- Call the LLM for the current agent, passing its instructions, the running conversation, and the schemas of all available tools and handoffs.
- Inspect the model's reply and branch on what it asked for:
- A final output (plain text, or a structured object if you set an output type)
โ the loop ends and that becomes
result.final_output. - A handoff โ switch the "current agent" to the target agent and loop again (covered in Topic 6).
- One or more tool calls โ run them, append each result back into the conversation as an observation, and loop again.
- A final output (plain text, or a structured object if you set an output type)
โ the loop ends and that becomes
- Safety brake: the loop is bounded by a max turns limit so a misbehaving agent can't loop forever (it raises an error if exceeded).
Agent has a get_weather(city) tool. User asks:
"What should I wear in Paris today?"
- Turn 1 โ model call: the LLM decides it needs the weather and emits a
tool call
get_weather("Paris")(not a final answer). - Tool runs: the SDK executes your Python function โ returns
"18ยฐC, light rain". That string is appended as an observation. - Turn 2 โ model call: now the LLM has the weather in its context. This time it produces a final answer: "It's 18ยฐC with light rain in Paris โ wear a jacket and bring an umbrella." Loop ends.
Two model calls, one tool call, no plumbing written by you. The Runner orchestrated all of it.
The same loop is exposed three ways: Runner.run(...) (async,
returns when done), Runner.run_sync(...) (a blocking convenience
wrapper), and Runner.run_streamed(...) (emits events as they happen,
so you can show progress live โ like ChatGPT typing out). Same loop underneath; different ways
to consume it.
A "run" is one full pass of the loop โ possibly many model calls and tool calls โ that ends when the agent produces a final output (or hits the max-turns brake). The loop logic that you hand-built as nodes and edges in LangGraph is provided for you here.
run / run_sync / run_streamed.
4 Tool patterns in the SDK
A tool is a gadget you give your helper โ a calculator, a weather-checker, a search box. The tricky part used to be writing a little instruction card telling the helper exactly what the gadget does and what buttons it has. The SDK reads your gadget (your function) and writes that instruction card for you, automatically.
Recall from Session 3 that for an LLM to call a tool, the model needs a description of it: the tool's name, what it does, and what arguments it takes (its schema). The model reads these descriptions to decide which tool to call and with what arguments. Writing that schema by hand (often as JSON) is exactly the boilerplate the SDK removes.
Function tools โ turn any function into a tool
The most common pattern is the function tool: you write a normal Python function and add a small decorator. The SDK then:
- uses the function name as the tool name;
- reads the docstring to describe what the tool does (and even each parameter);
- reads your type hints to build the argument schema automatically;
- uses Pydantic (a Python data-validation library) to validate the model's arguments before your code ever runs.
from agents import Agent, Runner, function_tool # The decorator turns this plain function into a tool. # Name, description, and arg schema are all derived automatically. @function_tool def get_weather(city: str) -> str: """Return the current weather for a given city. Args: city: The name of the city, e.g. "Paris". """ # (a real version would call a weather API) return f"18ยฐC, light rain in {city}" agent = Agent( name="Weather bot", instructions="Help users with weather. Use tools when needed.", tools=[get_weather], # just hand it the function ) result = Runner.run_sync(agent, "What's it like in Paris?") print(result.final_output)
Notice: no JSON schema, no manual registration. The type hint city: str
and the docstring become the tool's contract that the model sees.
How the SDK exposes tools to the model
Under the hood, on every model call (step 1 of the loop), the SDK includes the generated schema
of each tool in the request. The model replies with a structured "I want to call
get_weather with {"city": "Paris"}" message. The
SDK then validates those arguments against your type hints, runs your function, captures the
return value, and feeds it back as an observation. If validation fails (say the model sends a
number where a string is required), the SDK can surface a clear error back to the model so it can
correct itself โ far safer than your raw code crashing on bad input.
Beyond your own functions, SDKs typically also offer hosted tools the model provider runs for you (e.g. web search, code execution, file search) and the ability to expose another agent as a tool (so a "main" agent can call a specialist and get just its answer back, without fully handing over the conversation โ contrast this with handoffs in Topic 6).
The model only knows what your name, docstring, and type hints tell it. Vague names
(do_thing) or missing docstrings make the model misuse or skip your
tool. Treat the docstring like a tiny prompt โ clear, specific, with units and examples where
helpful.
5 Orchestration: coordinating many specialised agents
Think of a hospital front desk. You walk in and tell the receptionist your problem; they don't fix you themselves โ they send you to the right specialist (a dentist, an eye doctor, a bone doctor). The receptionist is a triage agent: their whole job is to listen, figure out what you need, and route you to the agent who's best at it.
Orchestration means: when you have several agents, how do they work together? A single do-everything agent often performs worse than a team of focused specialists โ each with a tight, simple set of instructions and tools. The classic pattern is a triage agent (also called a router or coordinator) that decides which specialist should handle the request.
Two flavours of orchestration
| Style | Who decides the flow | When to use |
|---|---|---|
| Orchestrate via the LLM | The model decides โ using handoffs and tools โ which agent acts next. Flexible and adaptive. | Open-ended tasks where you can't predict the path; you trust the model to route. |
| Orchestrate via code | Your Python code decides the order (if/else, loops, running agents in sequence or parallel). Predictable and cheap. | Known workflows where you want deterministic, repeatable steps. |
You can mix both: code for the overall pipeline, the LLM for the fuzzy routing decisions.
A help desk has two specialists โ a billing agent and a tech-support agent โ and a triage agent that decides who should answer:
from agents import Agent, Runner billing_agent = Agent( name="Billing", instructions="You handle invoices, refunds, and payment questions.", ) tech_agent = Agent( name="Tech support", instructions="You troubleshoot login, setup, and bug issues.", ) triage_agent = Agent( name="Triage", instructions=( "Decide which specialist should help, then hand off. " "Use Billing for money questions, Tech support for technical ones." ), handoffs=[billing_agent, tech_agent], # the specialists it can route to ) result = Runner.run_sync(triage_agent, "My card was charged twice!") print(result.final_output) # answered by the Billing agent
The user talks to one entry point (triage). The triage agent reads the message, decides "this is billing," and hands off. The Billing agent produces the final answer.
In Session 6, this same idea was an explicit graph: a router node with
conditional edges pointing to specialist nodes, all sharing one state object. The SDK
expresses the same orchestration with one list โ handoffs=[...] โ and
lets the model do the routing. Less wiring, but also less fine-grained control over the exact
path (the trade-off we'll formalise in Topic 7).
6 Handoffs: one agent delegating to another
Imagine you call a phone helpline. The first person can't fix your problem, so they say "let me transfer you to someone who can" โ and now you're talking to a different, more expert person who takes over the whole conversation. A handoff is that transfer: one agent passes the baton (the whole conversation) to another agent.
A handoff lets one agent delegate control of the run to
another agent. After a handoff, the new agent becomes the "current agent" in the loop (Topic 3):
it sees the conversation so far and takes over producing the answer. Cleverly, the SDK exposes
handoffs to the model as a special kind of tool โ so from the model's point of view,
"transfer to the Billing agent" looks like just another action it can choose. You can declare
them simply by listing target agents, or use the handoff() helper to
customise the name, description, or filter what context gets passed along.
Handoff vs a plain tool call โ the crucial difference
This is the part beginners mix up. Both are "actions the model can take," but they do very different things:
| Plain tool call | Handoff | |
|---|---|---|
| What happens | Run a function, get a result back. | Transfer the conversation to another agent. |
| Who's in charge after | The same agent โ it keeps control and reads the tool's result. | A different agent โ it takes over the run. |
| Control returns? | Yes โ back to the calling agent to continue reasoning. | Not by default โ the new agent now drives (it may answer or hand off again). |
| Good for | Fetching data / doing an action (weather, math, DB lookup). | Routing to a specialist with different instructions/tools. |
A tool call is "go get me something and bring it back." A handoff is "you take this from here." One returns a value to the same agent; the other passes the baton to a new agent. (Contrast also with agent-as-a-tool from Topic 4: that calls a sub-agent and brings its answer back โ so it behaves like a tool call, not a handoff.)
from agents import Agent, Runner, handoff refund_agent = Agent( name="Refunds", instructions="Process refund requests politely and confirm the amount.", ) support_agent = Agent( name="Support", instructions="Help users. If they want a refund, hand off to Refunds.", handoffs=[ refund_agent, # simple: just list the agent handoff( # or customise the handoff refund_agent, tool_name_override="escalate_to_refunds", ), ], ) result = Runner.run_sync(support_agent, "I want my money back for order 42.") # The Refunds agent now owns the conversation and produces the answer. print(result.final_output)
The support agent recognises a refund request and hands off. From that point, the Refunds agent โ with its own instructions โ runs the loop and produces the final output. Control did not bounce back to support.
By default the receiving agent sees the whole conversation so far, so it has context. You
can attach an input filter to trim or reshape that history (e.g. drop
earlier tool noise), and add an on_handoff callback to run code at the
moment of transfer (like logging or fetching data). This is the SDK's first-class version of
the routing edges you drew by hand in LangGraph.
7 SDK vs framework: when to pick which
An Agent SDK is like a pre-built toy kit: fast, neat, and great for the usual toys. A framework like LangGraph is like a full LEGO box: more work, but you can build anything, exactly how you want. Neither is "better" โ it depends on what you're making and how much control you need.
You now know both worlds: the lower-level framework (LangChain/LangGraph, Session 6) where you assemble the graph yourself, and the higher-level Agent SDK (OpenAI Agents SDK) where the common shape is handed to you. Here's how they compare.
| Dimension | Agent SDK (e.g. OpenAI Agents SDK) | Framework (LangChain / LangGraph) |
|---|---|---|
| Abstraction level | High โ think in Agents, Tools, Handoffs, Runs. | Lower โ think in nodes, edges, state, and an explicit graph. |
| Boilerplate | Minimal โ loop, schemas, and routing provided. | More โ you build the loop, state schema, and wiring. |
| Control over flow | Good for standard agent + handoff patterns; less granular for unusual flows. | Maximum โ arbitrary cycles, branching, pauses, human-in-the-loop. |
| Learning curve | Gentle โ a handful of concepts. | Steeper โ more concepts and moving parts. |
| Flexibility / customisation | Covers the common cases cleanly; custom logic means dropping lower. | Build essentially any control flow you can imagine. |
| Lock-in | Lighter (the OpenAI SDK is small, open-source, and works across many model providers), but you adopt its agent model. | Framework-shaped code and concepts; portable across many models, but tied to the framework's patterns. |
| Best for | Getting a real agent or small multi-agent app running quickly. | Complex, bespoke workflows needing precise, custom orchestration. |
Start with the SDK. If your app is "an agent (or a few) with some tools and handoffs," the SDK gets you there fast and readably. Reach for the framework when you hit something the SDK can't express cleanly โ custom control flow, intricate branching, long-running stateful graphs, fine-grained human-in-the-loop. Many teams even use both: the framework for a complex backbone, an SDK-style agent inside a node.
The most common beginner mistake is reaching for the most powerful tool first. If a simple SDK agent solves the problem, using a full graph framework just adds complexity, bugs, and maintenance cost. Pick the lowest amount of machinery that does the job.
โ Putting it all together
You just learned how to build agents the easy way. Here's the one-paragraph story that ties all seven topics together:
Building agents directly in LangGraph (Session 6) is powerful but low-level โ lots of state, nodes, edges, and a hand-built loop. An Agent SDK like the OpenAI Agents SDK packages the common shape for you with four primitives: an Agent (an LLM plus instructions, tools, and handoffs), Tools (typically typed Python function tools whose schema the SDK auto-generates and validates), Handoffs (delegating the whole conversation to a specialist โ unlike a tool call, control doesn't come back), and a Runner that executes the run loop (call model โ tool/handoff/final? โ run tools โ observe โ repeat). With these you can orchestrate a team of agents โ e.g. a triage agent routing to specialists โ either model-driven or code-driven. Choose the SDK for the common case and speed, and drop to a framework only when you need custom, fine-grained control.
Quick self-check
What are the four core primitives of the OpenAI Agents SDK?
Agents (LLM + instructions + tools/handoffs), Tools (capabilities the agent can call), Handoffs (delegating to another agent), and the Run/Runner (the engine that executes the loop). Guardrails and sessions are useful extras on top.
In the run loop, what three things can the model's reply be, and what happens for each?
A final output โ the loop ends and that's the answer. A handoff โ switch to the target agent and continue. One or more tool calls โ run them, append the results as observations, and loop again (bounded by a max-turns limit).
How is a handoff different from a plain tool call?
A tool call runs a function and returns a result to the same agent, which stays in control. A handoff transfers the whole conversation to a different agent, which then takes over driving the run โ control doesn't return by default.
Why don't you have to write a JSON schema for a function tool?
The SDK auto-generates it: the function name becomes the tool name, the docstring becomes the description, and the type hints define the argument schema (validated with Pydantic). You still need clear names and docstrings, since that's all the model sees.
When should you reach for LangGraph instead of an Agent SDK?
When you need custom, fine-grained control flow the SDK can't express cleanly โ arbitrary cycles, intricate branching, long-running stateful graphs, or detailed human-in-the-loop. For a standard "agent + tools + handoffs," the SDK is faster and simpler.
๐ References & Further Reading
Class material
- ๐ Original course notes / handout (source sheet) โ open the shared GenAI class material for this session.
- Class handout: "Agent SDK โ Fundamentals".
- ๐ Assignment 04 โ Website Automation Agent โ the assignment shared for this session.
Papers, docs & deep dives
- ๐ OpenAI Agents SDK documentation โ agents, tools, handoffs, and the runner used throughout this session.
- ๐ OpenAI โ A practical guide to building agents (PDF) โ when to use agents, orchestration patterns, and guardrails.
- ๐ Anthropic โ Building effective agents โ workflow vs agent patterns and how to keep agents simple.