๐Ÿ“š Study Notes / Home / GenAI / Session 7
Session 07 ยท Agent SDK โ€” Fundamentals

Building agents the easy way: the Agent SDK

In Session 6 you wired up agents by hand with LangChain and LangGraph โ€” drawing every node and edge yourself. It worked, but it was a lot of plumbing. Today we meet a friendlier tool: an Agent SDK (we'll use the popular OpenAI Agents SDK as our example). It hands you agents, tools, and hand-offs ready-made, so you can focus on what your agent should do instead of how to glue it together. As always, we start each idea with a tiny story, then go deep, then show real code.

โฑ 22 min read๐Ÿ“– 7 topics

1 Recap & motivation: why we want an Agent SDK


Explain like I'm 5

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 pointWhat it means in practice
Lower-level by designYou think in nodes, edges, and a graph. That's the right altitude for complex flows, but overkill for "an agent that uses 3 tools."
BoilerplateYou 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 loopThe "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 manualRouting between specialised agents (a "triage" agent sending work to others) means more nodes, more edges, more state plumbing.
Easy to get subtly wrongForget an edge or mishandle state and your agent loops forever, drops a tool result, or never stops. These bugs are fiddly.
Not a criticism of LangGraph

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.

The motivation in one line

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.

Recap You already know agents (Session 3) and one way to build them with LangChain/LangGraph (Session 6). LangGraph is powerful but low-level: lots of state, nodes, edges, and a hand-built loop. An Agent SDK exists to take that repetitive plumbing off your hands for the common case.

2 What is an Agent SDK? (the OpenAI Agents SDK)


Explain like I'm 5

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

PrimitiveWhat it isMaps to (from earlier sessions)
AgentAn 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.
ToolA 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.
HandoffA 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.
Two bonus primitives you'll hear about

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.

Concrete example: your first agent in ~5 lines

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.

Key takeaway

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.

Recap An Agent SDK is a focused library for building agents. The OpenAI Agents SDK gives you four primitives: Agents (LLM + instructions + tools), Tools (functions the agent can call), Handoffs (delegating to another agent), and the Runner (the engine that runs the loop). Plus guardrails and sessions as extras.

3 The agent lifecycle: how a single run executes


Explain like I'm 5

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

๐Ÿ“จ
1. Call the model
Send instructions + history + tool list
โ†’
โ“
2. Final or tool?
Did the model answer, or ask for a tool/handoff?
โ†’
๐Ÿ”ง
3. Run tools
Execute the requested tool calls
โ†’
๐Ÿ‘€
4. Observe
Append tool results to the conversation
โ†’
๐Ÿ”
5. Repeat
Loop back to step 1 until a final answer

More precisely, each iteration of the loop does this:

  1. Call the LLM for the current agent, passing its instructions, the running conversation, and the schemas of all available tools and handoffs.
  2. 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.
  3. 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).
Worked example: watching a run unfold

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.

Async, sync, and streaming

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.

Key takeaway

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.

Recap The run loop = call model โ†’ is it a final answer, a handoff, or tool calls? โ†’ run any tools and feed results back โ†’ repeat, bounded by a max-turns limit. The Runner drives it; you just call run / run_sync / run_streamed.

4 Tool patterns in the SDK


Explain like I'm 5

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.
Defining a function tool
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.

Other kinds of tools

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).

Good tools need good descriptions

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.

Recap The headline tool pattern is the function tool: decorate a typed, documented Python function and the SDK auto-generates its schema, validates arguments with Pydantic, and exposes it to the model each turn. You skip the hand-written JSON schema from Session 3 โ€” but clear names and docstrings still matter, because that's all the model sees.

5 Orchestration: coordinating many specialised agents


Explain like I'm 5

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

StyleWho decides the flowWhen to use
Orchestrate via the LLMThe 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 codeYour 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.

Worked example: a triage agent routing to specialists

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.

Link back to LangGraph

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).

Recap Orchestration = coordinating multiple specialist agents. A common pattern is a triage agent that routes work to the right specialist. You can orchestrate via the LLM (flexible, model-driven) or via code (predictable, you control the order) โ€” or mix both. In the SDK, model-driven routing is expressed mostly through handoffs.

6 Handoffs: one agent delegating to another


Explain like I'm 5

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 callHandoff
What happensRun a function, get a result back.Transfer the conversation to another agent.
Who's in charge afterThe 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 forFetching data / doing an action (weather, math, DB lookup).Routing to a specialist with different instructions/tools.
The mental model

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.)

Handoffs in action (with a customised 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.

What gets passed along

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.

Recap A handoff transfers control of the run to another agent โ€” the new agent takes over and drives. The model picks handoffs like tools, but the effect is different: a tool call returns a value to the same agent, while a handoff passes the baton to a different agent. Handoffs are how the SDK does specialist routing.

7 SDK vs framework: when to pick which


Explain like I'm 5

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.

DimensionAgent SDK (e.g. OpenAI Agents SDK)Framework (LangChain / LangGraph)
Abstraction levelHigh โ€” think in Agents, Tools, Handoffs, Runs.Lower โ€” think in nodes, edges, state, and an explicit graph.
BoilerplateMinimal โ€” loop, schemas, and routing provided.More โ€” you build the loop, state schema, and wiring.
Control over flowGood for standard agent + handoff patterns; less granular for unusual flows.Maximum โ€” arbitrary cycles, branching, pauses, human-in-the-loop.
Learning curveGentle โ€” a handful of concepts.Steeper โ€” more concepts and moving parts.
Flexibility / customisationCovers the common cases cleanly; custom logic means dropping lower.Build essentially any control flow you can imagine.
Lock-inLighter (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 forGetting a real agent or small multi-agent app running quickly.Complex, bespoke workflows needing precise, custom orchestration.
A simple rule of thumb

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.

Don't over-engineer

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.

Recap An Agent SDK is high-level, low-boilerplate, and fast for standard agents; a framework like LangGraph is lower-level and maximally flexible for custom flows. Default to the SDK for the common case, and step down to the framework only when you genuinely need its extra control โ€” or combine them.

โ˜… 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

Papers, docs & deep dives