Claude Agent SDK Deep Dive
Building custom agents with the Claude Agent SDK — the agent loop, tools, and hooks.
Learning Objectives
- Explain the agent loop pattern
- Build agents with custom tools and sub-agents
- Use lifecycle hooks for monitoring and control
Claude Agent SDK Deep Dive
The Claude Agent SDK (also called the Anthropic Agent SDK) provides a structured framework for building agentic applications. Rather than manually implementing the agent loop with raw API calls, the SDK provides high-level abstractions — the Agent class, Runner, tool definitions, sub-agent delegation, and lifecycle hooks — that handle the boilerplate while giving you control over the important decisions.
The Agent Loop in Detail
At its core, the Agent SDK implements the same loop described in Lesson 1.1, but with structure and guardrails:
- Receive input: The user (or a parent agent) sends a message to the agent.
- Generate response: The agent sends the conversation state (system instructions + message history + available tools) to the Claude model.
- Process response: If the model returns text only (no tool calls), the agent returns the final answer. If the model returns one or more
tool_useblocks, the agent executes each tool call. - Feed results: Tool results are appended to the conversation as
tool_resultblocks in a new user message. - Repeat: Go to step 2. The loop continues until the model produces a response with no tool calls (indicating it has finished), or a maximum turn limit is hit.
The SDK manages this loop internally via Runner.run(), so you don't need to write the while-loop yourself. You define the agent's configuration, tools, and constraints — the SDK handles execution.
Building Agents with the Agent Class
The Agent class is the primary building block. An agent is defined by:
- name: A human-readable identifier used in logs and handoffs.
- model: Which Claude model to use (e.g.,
"claude-sonnet-4-5-20250514"). - instructions: The system prompt — what this agent does and how it should behave. This is equivalent to the system prompt in a raw API call.
- tools: A list of tool functions the agent can call. These are Python functions decorated or registered with the SDK.
- handoffs: Other agents this agent can delegate to (sub-agents).
from agents import Agent, Runner, function_tool
# Define tools as decorated Python functions
@function_tool
def search_database(query: str, limit: int = 10) -> str:
"""Search the customer database for records matching the query.
Args:
query: Search terms to match against customer records.
limit: Maximum number of results to return (default: 10).
"""
# In production, this would query a real database
return f"Found 3 records matching '{query}' (limit: {limit})"
@function_tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email to a customer.
Args:
to: Email address of the recipient.
subject: Email subject line.
body: Email body text.
"""
return f"Email sent to {to} with subject '{subject}'"
# Create the agent
support_agent = Agent(
name="Customer Support Agent",
model="claude-sonnet-4-5-20250514",
instructions="""You are a customer support agent for Acme Corp.
Your job is to help customers resolve issues by searching our database
and communicating solutions via email. Always verify account information
before making changes. Be professional and empathetic.""",
tools=[search_database, send_email],
)
# Run the agent
import asyncio
async def main():
result = await Runner.run(
support_agent,
messages=[{
"role": "user",
"content": "I need to update my shipping address for order #12345"
}]
)
print(result.final_output)
asyncio.run(main())Defining Tools
Tools in the Agent SDK are Python functions with type annotations and docstrings. The SDK automatically generates the JSON schema that Claude needs from the function signature:
- Function name becomes the tool name.
- Docstring becomes the tool description — this is what Claude reads to decide whether and how to use the tool.
- Parameter type annotations define the input schema —
str,int,bool,list, etc. - Default values make parameters optional in the schema.
Writing clear docstrings is essential — Claude uses them to understand what the tool does and when to call it. A vague description leads to misuse or underuse of the tool.
Sub-Agent Delegation
One of the most powerful features of the Agent SDK is the ability to define agents as handoffs for other agents. This enables the orchestrator-workers pattern natively:
from agents import Agent, Runner, function_tool
# Specialist agents
billing_agent = Agent(
name="Billing Specialist",
model="claude-sonnet-4-5-20250514",
instructions="""You are a billing specialist. Help resolve payment issues,
process refunds, and explain charges. You have access to the billing system.""",
tools=[search_database],
)
technical_agent = Agent(
name="Technical Support",
model="claude-sonnet-4-5-20250514",
instructions="""You are a technical support engineer. Help diagnose and
resolve technical issues. Ask for error messages and reproduction steps.""",
tools=[search_database],
)
# Orchestrator agent with handoffs to specialists
triage_agent = Agent(
name="Triage Agent",
model="claude-sonnet-4-5-20250514",
instructions="""You are a customer support triage agent. Determine the
nature of the customer's issue and hand off to the appropriate specialist:
- For billing/payment issues, hand off to the Billing Specialist.
- For technical/product issues, hand off to Technical Support.
If the issue is simple, handle it directly without handoff.""",
handoffs=[billing_agent, technical_agent],
)When the triage agent determines a handoff is needed, it transfers control to the specialist agent. The specialist receives the conversation context and continues the interaction. This is the SDK's native implementation of agent-to-agent communication.
Lifecycle Hooks
Hooks allow you to inject custom logic at key points in the agent loop without modifying the agent's core behavior:
- on_tool_start / on_tool_end: Called before and after each tool execution. Use these for logging, metrics, access control, or auditing.
- on_handoff: Called when an agent transfers control to another agent. Use for logging handoff patterns or enforcing handoff policies.
Hooks are essential for production observability. Without them, debugging multi-turn, multi-tool agent interactions is extremely difficult.
Guardrails in the SDK
The Agent SDK supports guardrails — validation checks that run on inputs and outputs:
- Input guardrails: Validate or filter user input before it reaches the agent. Use for content policy enforcement, prompt injection detection, or input sanitization.
- Output guardrails: Validate agent output before it's returned to the user. Use for PII detection, response policy compliance, or format validation.
Exam Tip: Know the agent loop sequence cold: message → model response → if tool_use, execute tools → feed tool_result back → repeat. The exam may ask about the format of tool results (they are sent as a user message with tool_result content blocks, keyed by tool_use_id). Also know that the loop terminates when the model responds with text and no tool calls — this signals the agent considers the task complete.
Key Takeaways
The Agent SDK provides structured abstractions for building agents: the Agent class (configuration), Runner (execution), function_tool (tool definition), and handoffs (sub-agent delegation).
Tools are Python functions with type annotations and docstrings. The SDK generates JSON schemas automatically. Clear docstrings are critical — they're what Claude reads to decide when to use a tool.
Handoffs enable multi-agent patterns natively. A triage agent can hand off to specialist agents, implementing orchestrator-workers without manual coordination code.
Lifecycle hooks and guardrails are essential for production: hooks provide observability (logging, metrics), and guardrails enforce input/output policies.