Lesson 04 A Tool-Heavy Research Assistant
Theme:

A Tool-Heavy Research Assistant

Lesson 04 · Building Your Own Agents · ~12 min read

What makes an agent “tool-heavy”

Working definition: a tool-heavy agent is one with four or more tools, where at decision time the LLM doesn’t have a fixed order to call them in — the next tool depends on what the previous tool returned. The model is doing real planning between turns, not following a recipe you wrote in code. It’s worth contrasting that shape with three things it isn’t:

The Claude Agent SDK is at its sweet spot when (a) tools are many, (b) order is data-driven, and (c) one model handles the whole flow. This lesson’s research assistant ticks all three boxes by construction.

Why this is the Claude Agent SDK’s sweet spot

Lesson 03’s closing section listed five criteria for picking the Claude Agent SDK over LangGraph, OpenAI Agents SDK, or CrewAI. Let’s replay that list against today’s lesson:

What would push you off the SDK? If the tools had hard ordering constraints — e.g., “the writer step must always come after exactly one approval step, and approval can loop back to writer” — that’s a graph shape and LangGraph will model it more cleanly. If you wanted separate sub-agents with distinct personalities — a “search agent” that hands off to a “writer agent” — that’s a multi-agent shape and CrewAI is the better fit. None of that applies here. This lesson is the canonical Claude-Agent-SDK demo.

The toolset

In the lab you’ll build agent.py with five tools wired into an Anthropic Agent. Each one is mocked in-process so the lab is self-contained, reproducible, and free to run repeatedly. Real integrations come later in the course (and in the companion MCP Servers course). Here’s the full surface:

Real PKB-style integration belongs in the MCP Servers course, where the pkb server actually runs over MCP and can be plugged into any client. Here we mock it inline so you can read every byte of behaviour in one file.

The write_report “final-answer tool” pattern

This is the most novel idea in the lesson, so it’s worth dwelling on. Without write_report, the agent finishes a task by producing a free-text final answer — the LLM’s last assistant message is just prose. That’s fine for a chat UI, but it’s unparseable for downstream code. You can’t reliably grep an LLM’s prose for “the three bullet points” or “the source list,” especially across many runs and across model upgrades.

With write_report, the agent’s final action is a tool call with three explicit fields: intro, bullets, and sources. The Pydantic-validated parameters mean three concrete benefits:

  1. Guaranteed structure. The schema requires all three fields. There is no “the model forgot to include sources” failure mode — the tool call won’t even validate without them.
  2. Parseable output. Your caller can grab tool_calls[-1].input["bullets"] and feed it to a templating engine, a CMS, a Slack post, a JSON API response — anything that expects structured data.
  3. Composability. If you later want a second agent to review the report, the input shape is already JSON. No re-parsing prose, no fragility around Markdown headings.

This pattern generalises

Any agent with a structured final output should use a final-answer tool rather than text parsing. This is also how LangGraph’s “structured-output node” and OpenAI Agents SDK’s output_type parameter work under the hood — they’re just dressing up the final-answer tool pattern with framework sugar. Knowing the bare pattern means you can recreate it in any SDK.

The cost: one extra tool call per task (cheap — small input, tiny output), plus one extra constraint in the system prompt (“when you have enough information, call write_report — do not produce the report as free text”). Both are good investments.

Parallel tool calls

When the LLM emits multiple tool_use blocks in a single turn, the Claude Agent SDK runs them concurrently (under the hood, with asyncio) and returns all results together in the next user message. Two practical wins:

When doesn’t the LLM emit parallel calls? When the second call’s arguments depend on the first call’s results. The model has to see the first result before it knows what to ask second. Parallelism therefore happens organically when the work is independent (e.g., “fetch these three URLs I found earlier”), not when it’s sequential (e.g., “search, then fetch the best hit”).

A concrete SDK win over hand-rolling

Hand-rolled L02 didn’t do this — tool calls ran serially in the order they came out of the response. The SDK does it automatically: spot multiple tool_use blocks, schedule them concurrently, gather, return. It’s one of the most concrete wins for “use the SDK over hand-rolling” and it happens with zero extra code in agent.py.

Stop-condition tuning for this agent

L03 enumerated the four stop conditions (natural exit, turn cap, cost cap, caller interrupt) and how they’re wired in the SDK. Picking good numbers for a specific agent is the harder skill. Here are realistic settings for the research assistant:

Why these numbers? Because you don’t want the cap to normally fire — that means your typical task is on the cliff edge of failure. Caps should sit at roughly 5–10× the median task’s actual cost (and roughly 2× the worst observed). If the cap fires often, raise the cap and investigate why the agent is using more turns than expected. The usual culprits: a vague system prompt, or a tool returning unhelpful output that the LLM keeps re-querying.

What the lab gives you

By the end of the lab you’ll have run python agent.py "What changed in Linux kernel 6.14’s networking stack?" against the mocked tools and watched a multi-turn trace end in a structured report. You’ll have deliberately constructed a prompt that triggers parallel tool calls, and you’ll have used the on_step hook to log per-turn cost and identify which tool was the most expensive in your run.

Most importantly: you’ll have one self-contained agent.py (~120 lines) committed at agents/lesson04-tool-heavy-agent/agent.py in the course repo. This is the same file Lesson 11’s cross-framework scorecard imports. When you later see the LangGraph, OpenAI Agents SDK, and CrewAI versions of “the same agent” in Modules 3–5, you’ll know exactly what each framework is rewriting and what it’s preserving. The shape of the agent stays constant; what changes is the framework around it.

References