The Claude Agent SDK at its sweet spot: 5 tools, unbounded tool-call order, ~120 lines of Python.
Agents Module 2 Claude Agent SDK Tool-Heavy
Hits every condition on L03 Slide 11's “Pick it when” list.
Given a research question, return a short report (intro + 3 bullet findings + sources) backed by web-search and document-fetch tools.
Real example question: “What changed in Linux kernel 6.14's networking stack?”
The agent decides on its own which sources to consult, in what order, and when it has enough material to write the report.
| Tool | What it does (signature) |
|---|---|
pkb.search(query) | Search the personal knowledge base (mocked dict of pre-seeded docs). |
pkb.fetch(doc_id) | Fetch the full text of a single doc. |
web_search(query) | Search the web (mocked — returns a list of {title, url, snippet}). |
web_fetch(url) | Fetch a URL's text (mocked — returns the snippet). |
write_report(intro, bullets, sources) | Final answer tool: structures the report. |
You are a research assistant. To answer a question:
1. Check `pkb.search` first; fetch any promising doc with `pkb.fetch`.
2. If you need more, use `web_search`; fetch with `web_fetch`.
3. When you have enough material, call `write_report(intro, bullets, sources)`.
Always cite sources by URL or doc_id. Be concise.
The prompt does not prescribe an exact tool order — it gives the LLM a strategy. The LLM picks the actual sequence per question.
write_report final-answer tool patternwrite_report is just a normal @tool-decorated function that returns "saved".end_turn.@tool definitions@tool
def pkb_search(query: str) -> str:
"""Search the personal knowledge base by substring. Returns
a list of '{doc_id}: {title} - {one-line snippet}' lines."""
hits = [f"{k}: {v['title']} - {v['snippet']}"
for k, v in PKB.items() if query.lower() in v['text'].lower()]
return "\n".join(hits) if hits else "no matches"
@tool
def pkb_fetch(doc_id: str) -> str:
"""Fetch the full text of a PKB doc by id."""
return PKB.get(doc_id, {}).get("text", f"unknown doc: {doc_id}")
Two of the five shown here; the others (web_search, web_fetch, write_report) follow the same pattern — one function, one decorator, one docstring.
from anthropic import Anthropic
from anthropic.agents import Agent
agent = Agent(
client=Anthropic(),
model="claude-3-5-sonnet-latest",
system=SYSTEM_PROMPT,
tools=[pkb_search, pkb_fetch, web_search, web_fetch, write_report],
max_turns=15,
max_cost_usd=0.50,
on_step=log_step,
)
report = agent.run(question)
print(report.final_text)
~12 lines of glue around the tool definitions. The agent loop, message-dance, and stop conditions are all inside Agent.run().
[step 1] tools=[pkb_search] cost=$0.003
[step 2] tools=[pkb_fetch] cost=$0.007
[step 3] tools=[web_search] cost=$0.011
[step 4] tools=[web_fetch, web_fetch] cost=$0.018
[step 5] tools=[write_report] cost=$0.024
[step 6] tools=[] cost=$0.026 (end_turn)
Note: turn 4 has parallel tool calls — the SDK runs both web_fetch calls concurrently, and the LLM gets both results back together.
tool_use blocks in one turn, the SDK runs them all (asyncio) and returns their results together.For a research assistant that gets called interactively a few times an hour, this is a comfortable budget.
Plan F (Module 6) wires this exact agent.py into agents/_eval/run_all.py as the Claude SDK row of the cross-framework scorecard.
The same shape of agent gets rebuilt in LangGraph (M3), OpenAI SDK (M4), and CrewAI (M5) so you can compare cost / latency / LoC apples-to-apples.
Today's agent.py is the canonical Claude entry.
Agent(...) was built for.write_report tool pattern gives you structured final answers without an extra schema layer.