Lesson 03 Claude Agent SDK Intro
Theme:

Lab 03 — First agent with the Claude Agent SDK

Lesson 03 · Building Your Own Agents · ~25 min hands-on

Goal: at the end of this lab you will have (a) verified that the SDK is importable from anthropic, (b) written a 12-line hello-world agent that runs successfully, and (c) ported the L02 weather agent to use @tool decorators and the Agent class — deleting roughly 70 lines of boilerplate in the process.

Prereqs

Step 1 — Confirm the SDK is importable

Before writing any code, sanity-check that the right anthropic version is on your path and that the agent primitives can be imported from the anthropic.agents namespace.

python -c "from anthropic.agents import Agent, tool; print('ok')"

Expected: ok.

If you get ModuleNotFoundError: No module named 'anthropic.agents', your installed anthropic is older than 0.39 — re-run pip install -r agents/requirements.txt and confirm the version matches the pin in agents/SDK-VERSIONS.md.

Step 2 — The 12-line hello world

Create hello_agent.py in a scratch directory of your choosing:

from anthropic import Anthropic
from anthropic.agents import Agent, tool

@tool
def get_weather(city: str) -> str:
    """Get current weather for a city as 'temp°C, conditions'."""
    return f"{city}: 4°C, light rain"

agent = Agent(
    client=Anthropic(),
    model="claude-3-5-sonnet-latest",
    system="You are a helpful assistant. Use the get_weather tool to answer.",
    tools=[get_weather],
    max_turns=5,
    max_cost_usd=0.05,
)

result = agent.run("What's the weather in Kyiv?")
print(result.final_text)
print(f"[cost: ${result.usage.total_cost_usd:.4f}]")

Run it:

python hello_agent.py

Expected: a one-sentence weather answer (something like “The weather in Kyiv is 4°C with light rain.”) plus a cost line in the range of ~$0.001–$0.005.

✓ Self-check: the script printed both a natural-language answer and a cost figure, and exited cleanly with no traceback.

Step 3 — Trigger the cost cap

Edit hello_agent.py and change max_cost_usd=0.05 to max_cost_usd=0.0001. Re-run.

python hello_agent.py

Expected: the SDK raises MaxCostExceeded. Note the difference from L02 — you get a typed exception, not a generic RuntimeError. That matters because in real code you can except MaxCostExceeded specifically and handle it differently from a turn-cap breach or a transport error.

Restore max_cost_usd=0.05 before moving on.

Step 4 — Port the L02 agent to the SDK

Open your L02 agent.py (the hand-rolled weather agent from Lesson 02). With hello_agent.py open side-by-side as a reference, replace each piece of boilerplate:

Save the result as agent_sdk.py. Run the same question you ran in L02:

python agent_sdk.py "What's the weather in Kyiv? Should I bring a jacket?"

Expected: the same final answer as the L02 hand-rolled version, a similar cost figure, and ~70 lines deleted from your file. Count them with wc -l agent.py agent_sdk.py to make the delta concrete.

✓ Self-check: agent_sdk.py answers the same question as agent.py with roughly the same cost, and is dramatically shorter (typically ~12 lines vs ~80).

Step 5 — Add an on_step hook

The SDK exposes a per-turn observability hook. Add this above the agent = Agent(...) line in agent_sdk.py:

def log_step(step):
    print(f"[step {step.turn}] stop_reason={step.stop_reason} "
          f"tools={[c.name for c in step.tool_calls]} "
          f"cost_so_far=${step.cost_so_far_usd:.4f}")

Then pass on_step=log_step to the Agent(...) constructor and re-run the script.

Expected: one log line per turn, printed before the final answer. This is the SDK’s built-in observability hook — the same hook you’ll wire to a logging library or an OTEL span later in the course.

Step 6 — Verify MaxTurnsExceeded

To see the turn-cap path, temporarily edit get_weather in agent_sdk.py to always return a perpetually-incomplete reply:

@tool
def get_weather(city: str) -> str:
    """Get current weather for a city as 'temp°C, conditions'."""
    return "need more details, call again"

Re-run the script. Expected: the SDK raises MaxTurnsExceeded after max_turns iterations — again, a typed exception rather than a hand-written raise RuntimeError.

Restore the original get_weather body once you’ve seen the exception.

What you should leave with

The next lesson (L04) uses exactly these primitives to build a multi-tool research assistant — the canonical “tool-heavy” shape that’s the SDK’s sweet spot.

Troubleshooting

Common stumbles