← the writing notes 10 min

The Claude Agent SDK in Practice: Building Agents That Work

Most "AI agent" demos are theatrical. They look great in a Loom recording and collapse the moment a real user touches them. Here's how I actually build agents that hold up under production load using the Claude Agent SDK.

Dimly lit developer desk at night with amber lamp, mechanical keyboard, and monitor glow, 35mm film grain editorial style

Last November I handed a client a Claude-powered agent that was supposed to triage inbound support tickets, route them to the right department, and draft first-pass replies. Took me three weeks to build. Looked brilliant in staging. First day in production it hallucinated a refund policy that doesn't exist, routed seventeen tickets to the wrong queue, and confidently told a customer their order would arrive "by Thursday" with zero access to shipping data.

So. I learned some things.

This post is about what I know now, after rebuilding that agent properly and shipping several more since. Not theory. Actual decisions I made, tools I reached for, and mistakes I won't repeat. If you're an agency owner or a freelancer trying to move past the demo stage with the Claude Agent SDK, this is written for you.

---

What the Claude Agent SDK Actually Is (and Isn't)

Before anything else: the SDK is not magic. It's a structured way to give Claude access to tools, manage conversation context across multiple turns, and orchestrate what amounts to a decision loop. Claude reasons about a task, decides whether to call a tool, gets a result back, reasons again, and either calls another tool or produces a final answer.

That loop sounds simple. It is simple. The complexity lives entirely in what you put around it.

The SDK gives you the plumbing. You're still responsible for the water pressure, the pipe diameter, and whether you remembered to shut off the mains before you started drilling. I've seen agency owners hand the SDK to a junior dev, expect a finished product in a sprint, and get back something that technically runs but falls apart on any input that wasn't in the happy path.

What the Loop Looks Like in Practice

You define tools as JSON schemas. Claude reads those schemas, decides when to use them, passes structured arguments, and your code executes the actual logic. Claude never runs code directly. It asks. Your system does the work. Then Claude gets the result and continues.

That separation matters more than most people realise. It means Claude is always an orchestrator, not an executor. And that framing should shape every architectural decision you make.

---

Designing Tools That Claude Can Actually Use

This is where most builds fall down. I've reviewed maybe fifteen agent codebases from other developers over the past year, and the single most common problem isn't prompt engineering or model choice. It's badly designed tools.

Here's what "badly designed" means in practice:

  • A tool called process_data that does five unrelated things depending on which parameters you pass
  • Tool descriptions that read like internal code comments ("calls the v2 endpoint with auth headers")
  • Parameters named type or mode that accept arbitrary strings instead of enums
  • No error information in the return value, so Claude has no idea whether the call succeeded

Back in early 2023, Seahawk had a content pipeline project where we'd built a manage_content tool that accepted a action parameter: create, update, delete, publish, unpublish, archive. Claude kept choosing the wrong action because the distinctions weren't obvious from the schema alone. We split it into six separate tools. Accuracy on that specific decision went from about 60% to 94% in our internal evals. One change.

The Rules I Follow Now

  1. One tool, one job. If you can't describe the tool's purpose in a single sentence without "and", split it.
  2. Use enums wherever possible. Don't let Claude guess strings.
  3. Write the description for Claude, not for a human developer. Claude doesn't know your codebase. It knows what you tell it.
  4. Always return structured data with an explicit success/failure field. Never make Claude infer from silence.
  5. Keep tool names verb-first. search_orders , create_draft , fetch_customer_record . Not orders , draft , customer.

The Anthropic tool use documentation goes deeper on schema structure and is worth reading carefully, not skimming.

---

Context Management Is the Hidden Cost

Here's something nobody talks about enough. Tokens are not free, and agents are hungry.

Each turn in the loop includes the full conversation history, all tool schemas, system prompt, and tool results. A moderately complex agent with ten tools and a detailed system prompt might start each user session at 3,000-4,000 tokens before the user has typed a single character. Add five or six tool calls with results, and you're looking at 15,000-20,000 tokens per resolved task. At Claude's current API pricing, that adds up fast on any volume.

I track this obsessively now. For every agent I ship, I run a cost-per-resolution number during QA. If it's above a threshold I've agreed with the client upfront, I go back and tighten the system prompt, reduce tool schemas, or look at whether I can cache static context using prompt caching, which Anthropic added and which I genuinely use on every project now. Cache-eligible tokens cost about 10% of the standard input rate on a cache hit. On a busy agent that reruns the same system prompt thousands of times a day, this is not a rounding error.

Trimming Without Breaking Things

The temptation is to write a rich, detailed system prompt covering every edge case. Resist it. Every line you add costs tokens on every turn. Write for the common case. Handle edge cases in tool return values or in shorter in-context instructions injected at the right moment.

I also ruthlessly cut tool descriptions once an agent is working. If a description says "This tool searches the order database and returns a list of orders matching the query, including order ID, customer name, line items, shipping status, and timestamps" I'll trim it to "Search orders by query string. Returns matching order records." Claude is smart enough. It doesn't need the field list in the tool description if the return schema documents those fields properly.

---

Multi-Agent Orchestration: When One Agent Isn't Enough

Single-agent systems break at a certain complexity ceiling. I hit that ceiling on a project for a property management company last spring. The agent needed to handle maintenance requests, communicate with contractors, update a Notion database, send templated emails via SendGrid, and pull availability data from a custom-built calendar API. Seven tools, several of which had sub-workflows.

One agent trying to coordinate all of that became unreliable. The context got messy. Claude would occasionally lose track of which sub-task it was working on mid-loop.

The fix was obvious in retrospect: orchestrator plus specialists. One top-level Claude agent handles intent classification and routing. Specialist sub-agents handle specific domains (communication, scheduling, data updates) and report back structured results. The orchestrator never sees the internals of what each specialist did. It just sees the output.

This pattern is described in Anthropic's own multi-agent guidance and it maps closely to how you'd design a human team. A project manager doesn't personally write every email and update every spreadsheet. They delegate, wait for confirmation, and move on.

Practical Notes on Sub-Agent Design

  • Give each sub-agent a tight, specific system prompt. No cross-domain instructions.
  • Sub-agents should never have more tools than they need for their domain. Tool bloat is as dangerous in sub-agents as it is in the main orchestrator.
  • Pass context explicitly. Don't assume a sub-agent "knows" what happened upstream. Send it exactly what it needs, nothing more.

---

Handling Failures Gracefully (Because They Will Happen)

Production agents fail. They time out. External APIs return 500s. Users send inputs you never anticipated. Claude occasionally misreads a tool schema and passes a malformed argument.

The question isn't whether your agent will fail. It's whether it fails safely.

I build three things into every agent now, without exception:

  1. Retry logic with backoff on all external tool calls. Not just on rate limit errors. On anything non-200.
  2. A fallback path when the agent has made more than N tool calls without resolving the task. N varies, but I rarely let it go above eight. At that point, something is wrong and a human should be involved.
  3. Explicit uncertainty handling in the system prompt. I tell Claude: if you don't have enough information to act confidently, ask a clarifying question rather than proceeding on assumptions.

That third one saved the ticket-triage agent I mentioned at the start. The rebuilt version now asks one clarifying question when it's uncertain about routing. Users don't mind. They'd rather answer a question than have their ticket land in the wrong queue.

---

Evals: You Cannot Ship Without Them

I didn't run proper evals on the first version of the support ticket agent. That was the mistake, really. Everything else was a symptom of that.

Evals don't have to be fancy. What I do now is build a set of 40-60 representative inputs before I start building, covering normal cases, edge cases, and adversarial inputs. I run the agent against all of them after every significant change. I track three numbers: task completion rate, tool call accuracy (did it call the right tool with the right arguments), and hallucination rate (did it assert something not grounded in tool results).

For a production agent, I won't ship below 88% task completion and zero tolerance on hallucination in high-stakes outputs like customer-facing messages with specific claims (dates, prices, policies).

The HELM benchmarking framework from Stanford is worth looking at for inspiration on eval design, even if you're not running at academic scale. The categories they test map well to real production requirements.

---

The System Prompt Is Load-Bearing

I've changed my mind on this over the past year. I used to treat the system prompt as setup text, something you write once and forget. Now I treat it as the most important file in the project.

A well-written system prompt does four things:

  • Defines the agent's identity and scope clearly (what it does and, critically, what it explicitly does not do)
  • Sets tone and output format expectations
  • Handles the most common failure modes proactively ("If you cannot find an order, say so explicitly rather than guessing")
  • Establishes escalation criteria

The scope definition is the one most developers skip. Without it, Claude will try to be helpful in ways you didn't intend. On the property management agent, the first system prompt didn't explicitly exclude financial advice. A tenant asked the agent whether they should dispute a charge. Claude helpfully weighed in. That's not what the client paid for, and it's not what the agent was built to do.

One sentence fixed it: "You are not authorised to provide advice on financial disputes, legal matters, or lease interpretation. Direct users to contact the office directly for these topics."

Write that sentence for every domain that's out of scope. Don't assume Claude will infer the boundaries.

---

FAQ

How is the Claude Agent SDK different from just using the Claude API directly?

The API gives you a single request-response. The Agent SDK (and the agent patterns Anthropic documents around it) gives you a structured loop where Claude can make multiple decisions, call tools, receive results, and continue reasoning across turns. It's less about a distinct software package and more about a pattern: tool definitions, multi-turn context management, and orchestration logic. You're building the scaffolding around the API to enable that loop.

What's a realistic timeline for shipping a production-ready agent?

Honestly, four to six weeks for anything non-trivial. Two weeks of that is building and wiring tools. One week is prompt engineering and iteration. One to two weeks is evals, edge case handling, and QA. Anyone promising you a production agent in a week either hasn't shipped one before or is handing you a demo dressed up as a product.

Should I use Claude for all sub-agents in a multi-agent system, or mix models?

I use Claude for anything that requires nuanced reasoning or where output quality matters to the end user. For simple classification tasks or high-volume low-stakes routing, a smaller and cheaper model can work. But mixing models adds integration overhead and makes debugging harder. Start with Claude for everything, then optimise once you have real production data showing where a lighter model is sufficient.

How do I prevent an agent from going off-script?

Three things working together: tight system prompt with explicit out-of-scope statements, tool design that physically prevents certain actions (don't give the agent a tool it shouldn't use), and output validation on anything customer-facing. You can't rely solely on the system prompt. Defence in depth.

What's the biggest mistake developers make with agent memory?

Treating the context window as infinite. It isn't. Most failures I see in poorly built agents come from context that's grown bloated with irrelevant history, forcing Claude to reason through noise. Prune aggressively. Summarise where you can. Only carry forward what the agent genuinely needs to complete the current task.

---

The honest summary is this: the SDK isn't the hard part. The hard part is the same thing it's always been in software, thinking clearly about scope, designing for failure, and testing before you ship. Claude is a remarkably capable reasoning layer, but it won't compensate for a badly designed system around it. Get the plumbing right first.

Need this done, not just read?

start a project book 30 minutes