SDK Anatomy: Agent, Runner, Tool, Guardrail
On this page
SDK Anatomy: Agent, Runner, Tool, Guardrail
The Agents SDK rests on four core concepts: Agent, Runner, Tool, and Guardrail. Each has a single responsibility. Their interplay—who decides, who executes, who acts, who controls—is what makes the SDK readable once you internalize it.
Agent — The brain
Defines instructions, model, tools, and guardrails. This is your agent's configuration.
Runner — The execution engine
Orchestrates the reasoning loop: sends requests, executes tools, and handles handoffs.
Tool — Capabilities
Functions the agent can call: web search, code execution, API calls, and more.
Guardrail — Safety
Validates agent inputs and outputs. Blocks dangerous or off-topic requests.
Agent: Configuration
The Agent class is purely declarative: it doesn’t run anything; it describes. Everything that defines your agent’s behavior lives in one place.
from agents import Agent, function_tool, WebSearchTool
@function_tool
def get_price(product: str) -> str:
"""Retrieves the price of a product."""
prices = {"widget": "€29.99", "gadget": "€49.99"}
return prices.get(product, "Unknown product")
agent = Agent(
name="Sales Agent",
instructions="""You are a sales agent for an online store.
Help customers find products and learn their prices.
Always respond in French with a professional tone.""",
model="gpt-5.6-terra",
tools=[get_price, WebSearchTool()],
handoff_description="Agent specialized in selling products",
output_type=None, # or a Pydantic type to enforce an output format
)
Key Agent parameters:
name: Human-readable identifier for the agentinstructions: The system prompt that defines behaviormodel: The model to use (gpt-5.6-sol,gpt-5.6-terra,gpt-5.6-luna)tools: The list of available toolshandoff_description: Description used for handoffs between agentsoutput_type: Pydantic type to structure the output
Runner: Execution
The Runner is the engine that runs your agent. It manages the full loop and comes in three modes depending on your calling context.
from agents import Runner
# Synchronous mode (development)
result = Runner.run_sync(agent, "What is the price of the widget?")
# Asynchronous mode (production)
result = await Runner.run(agent, "What is the price of the widget?")
# Streaming mode (real time)
result = Runner.run_streamed(agent, "What is the price of the widget?")
Regardless of mode, you get back a RunResult, which contains far more than the displayable response. The full history lets you resume a conversation, and last_agent becomes essential as soon as multiple agents hand off to one another.
result = Runner.run_sync(agent, "What is the price of the widget?")
# The agent's final response
print(result.final_output)
# The complete conversation history
print(result.to_input_list())
# The last active agent (useful with handoffs)
print(result.last_agent.name)
Tool: Capabilities
The SDK supports three ways to declare tools, from most common to most architectural.
1. Function tools (most common)
A decorated Python function becomes a tool; the SDK infers the schema from the signature and docstring.
from agents import function_tool
@function_tool
def send_email(recipient: str, subject: str, body: str) -> str:
"""Sends an email to a recipient."""
# Your sending logic here
return f"Email sent to {recipient}"
2. Tools hosted by OpenAI
These tools run on OpenAI’s side: you don’t write the search logic or the Python sandbox.
from agents import WebSearchTool, FileSearchTool, CodeInterpreterTool
tools = [
WebSearchTool(), # Web search
FileSearchTool(vector_store_ids=["vs_123"]), # File search
CodeInterpreterTool(tool_config={"type": "code_interpreter", "container": {"type": "auto"}}), # Code execution
]
3. Agents as tools
One agent can use another as a tool. The coordinator below delegates research without ever ceding control of the conversation, which distinguishes this from a handoff.
search_agent = Agent(
name="Researcher",
instructions="You look up precise information.",
tools=[WebSearchTool()],
)
primary_agent = Agent(
name="Coordinator",
instructions="You coordinate research.",
tools=[search_agent.as_tool(
tool_name="search",
tool_description="Performs in-depth research"
)],
)
Guardrail: Safety
A guardrail is a control agent placed in front of your business agent. Here, a verifier checks whether the request falls within sales before the sales agent responds.
from agents import Agent, InputGuardrail, GuardrailFunctionOutput, Runner
guardrail_agent = Agent(
name="Verifier",
instructions="Determine whether the request concerns sales. Respond true or false.",
output_type=bool,
)
async def verify_topic(ctx, agent, input_data):
result = await Runner.run(guardrail_agent, input_data, context=ctx)
return GuardrailFunctionOutput(
output_info=result.final_output,
tripwire_triggered=not result.final_output,
)
protected_agent = Agent(
name="Protected Sales Agent",
instructions="You are a sales agent.",
input_guardrails=[
InputGuardrail(guardrail_function=verify_topic),
],
)
If the guardrail trips (tripwire_triggered=True), execution stops and an InputGuardrailTripwireTriggered exception is raised. Plan to catch this in your application; otherwise, you’ll return a 500 error to a user who simply asked an off-topic question.
How these pieces interact
The full flow runs as follows:
- You call
Runner.run(agent, message) - The Runner checks input guardrails
- The Runner sends the message to the Agent’s model
- If the model requests a tool, the Runner executes it
- The Runner returns the result to the model (back to step 3)
- When the model produces a final response, the Runner checks output guardrails
- The
RunResultis returned
Key takeaways
Agentdefines behavior (instructions, model, tools)Runnerorchestrates the agent loop execution- Declare tools with
@function_tool, built-in classes, or.as_tool() - Guardrails validate inputs and outputs for safety
RunResultholds the final response and the complete history