Questions? Call 1.800.716.8955

Chapter 8

Building blocks in code

This chapter assembles the patterns from Chapters 2 to 6 into working code. The exam will not ask you to write code, but it will show you configurations and ask what they do, and building these pieces is the fastest way to make the patterns stick. The examples use Python and the Claude Agent SDK; the TypeScript SDK has equivalent options with camelCase names. A note on accuracy. These snippets follow Anthropic's documentation as of October 2026 and are illustrative. SDK option names change between versions; for instance, the exam guide calls the subagent-spawning tool "Task", and some recent SDK and Claude Code versions label it "Agent". Check the docs for your installed version before copying.

8.1 A bare agentic loop on the Messages API

import anthropic

client = anthropic.Anthropic()

MAX_SAFETY_TURNS = 50 # backstop with an alert, not the stopping rule def run_agent(task, tools, system):

messages = [{"role": "user", "content": task}]

for turn in range(MAX_SAFETY_TURNS):

resp = client.messages.create(model=MODEL, max_tokens=4096, system=system, tools=tools, messages=messages)

  • messages.append({"role": "assistant", "content": resp.content})
  • if resp.stop_reason == "end_turn":
  • return final_text(resp)
  • if resp.stop_reason == "max_tokens":
  • messages.append({"role": "user", "content": "Continue."})
  • continue
  • if resp.stop_reason == "tool_use":
  • results = []
  • for block in resp.content:
  • if block.type == "tool_use":
  • output, is_error = execute(block.name, block.input)
  • results.append({"type": "tool_result", "tool_use_id": block.id,
  • "content": output, "is_error": is_error})
  • messages.append({"role": "user", "content": results})
  • alert("agent hit safety cap", messages)

Every tool_use block gets a matching tool_result with the same ID, all in one user message. Errors go back as results with is_error set, so Claude can decide what to do, rather than as exceptions that end the loop.

8.2 A support agent on the Agent SDK

The Agent SDK runs the loop for you and adds built-in tools, hooks, subagents and sessions. from claude_agent_sdk import (query, ClaudeAgentOptions, HookMatcher, tool, create_sdk_mcp_server)

@tool("get_customer", "Find and verify a customer by email or phone. Returns customer_id, " "name and account status. Call this before any order or refund tool.", {"email": str}) async def get_customer(args):

  • matches = crm.find(args["email"])
  • if len(matches) > 1:
  • return {"content": [{"type": "text", "text":

'{"matches": %d, "action": "ask the customer for an order number"}' % len(matches)}]}

...

support = create_sdk_mcp_server(name="support", version="1.0.0", tools=[get_customer, lookup_order, process_refund, escalate_to_human])

  • verified = set()
  • async def require_verified(input_data, tool_use_id, context):
  • if input_data["tool_name"] == "mcp__support__get_customer":
  • return {}
  • if context_session_customer(input_data) not in verified:
  • return {"hookSpecificOutput": {"hookEventName": "PreToolUse",
  • "permissionDecision": "deny",

"permissionDecisionReason": "Verify the customer with get_customer first."}} return {}

async def record_verification(input_data, tool_use_id, context):

  • if input_data["tool_name"] == "mcp__support__get_customer":
  • mark_verified_if_single_match(input_data, verified)
  • return {}
  • options = ClaudeAgentOptions(

system_prompt=SUPPORT_PROMPT, # policies, escalation criteria, few-shot examples mcp_servers={"support": support},

allowed_tools=["mcp__support__get_customer", "mcp__support__lookup_order", "mcp__support__process_refund", "mcp__support__escalate_to_human"], hooks={

"PreToolUse": [HookMatcher(matcher="mcp__support__.*", hooks=[require_verified]), HookMatcher(matcher="mcp__support__process_refund", hooks=[refund_limit])],

"PostToolUse": [HookMatcher(matcher="mcp__support__get_customer", hooks=[record_verification])],

},

)

Building blocks in code

async for message in query(prompt=customer_message, options=options):

handle(message)

This one file shows four exam ideas: descriptive tool text, a programmatic prerequisite (no order or refund tool before verification), a hard business limit (refund_limit from worked example 2.D) and multiple-match handling that asks rather than guesses. MCP tools appear to Claude under the name mcp__<server>__<tool>.

8.3 A research coordinator with subagents

from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition agents = {

"web-search": AgentDefinition(

description="Searches the web for one focused sub-question and returns claims with URLs and dates.",

prompt="Return JSON: findings[{claim, excerpt, url, published}], gaps[], errors[]. " "Retry transient failures once; report anything still failing in errors[].", tools=["WebSearch", "WebFetch"]),

"doc-analysis": AgentDefinition(

description="Analyses supplied documents and returns data points with page numbers.", prompt="Return JSON: findings[{claim, excerpt, document, page, published, method}].", tools=["Read", "mcp__docs__load_document"]),

"synthesis": AgentDefinition(

description="Combines findings into a report section, preserving every source.", prompt="Keep claim-source mappings. Mark contested findings. Note coverage gaps.", tools=["mcp__facts__verify_fact"]),

}

options = ClaudeAgentOptions(

system_prompt=COORDINATOR_PROMPT, # goals and quality bar, see Chapter 2 deep dive agents=agents,

allowed_tools=["Task"], # required to spawn subagents

)

The coordinator's prompt tells it to list the question's dimensions, launch independent searches together (several Task calls in one response), pass full findings to synthesis, check coverage and re-delegate for gaps. Each subagent has only its own tools: synthesis cannot search the web, but it has a scoped verify_fact for quick checks.

8.4 Sessions: resume and fork

  • # Continue yesterday's analysis
  • options = ClaudeAgentOptions(resume=saved_session_id)
  • # Branch two independent explorations from the same baseline

opt_a = ClaudeAgentOptions(resume=baseline_id, fork_session=True)

opt_b = ClaudeAgentOptions(resume=baseline_id, fork_session=True)

In the Claude Code CLI, claude --resume <name-or-id> continues a session. If files changed since, say so in the first message: "Since this session, ledger.ts, refund.ts and audit.ts changed; re-read those before continuing."

8.5 Structured extraction with tool use

resp = client.messages.create(

model=MODEL, max_tokens=2048,

system="Extract invoice data. Use null for anything not stated. Dates as ISO 8601.", tools=[record_invoice_tool],

tool_choice={"type": "tool", "name": "record_invoice"},

messages=[{"role": "user", "content": FEW_SHOT + f"<document>{text}</document>"}], )

data = next(b.input for b in resp.content if b.type == "tool_use")

  • errors = validate(data) # semantic checks (worked example 5.C)
  • if errors:
  • data = retry_with_errors(text, data, errors)

When several document types are possible, provide one tool per type and use tool_choice={"type": "any"} so Claude must pick one.

8.6 Message Batches

batch = client.messages.batches.create(requests=[

{"custom_id": doc.id,

"params": {"model": MODEL, "max_tokens": 2048, "tools": [record_invoice_tool], "tool_choice": {"type": "tool", "name": "record_invoice"},

  • "messages": [{"role": "user", "content": doc.text}]}}
  • for doc in tonight_docs])
  • # later: poll until processing has ended

if client.messages.batches.retrieve(batch.id).processing_status == "ended":

  • retry = []
  • for r in client.messages.batches.results(batch.id):
  • if r.result.type == "succeeded":
  • store(r.custom_id, extract_tool_input(r.result.message))
  • else:

retry.append(r.custom_id) # resubmit only these, chunked if too long A single-turn forced tool call works in a batch because no tool has to run mid-request; Claude only fills the schema. An agent that needs tool results back cannot run inside a batch.

8.7 Claude Code project layout

Building blocks in code

  • api.md # paths: ["services/api/**/*"]
  • commands/
  • review.md # /review for everyone
  • skills/
  • dependency-audit/SKILL.md # context: fork, allowed-tools
  • agents/
  • security-reviewer.md # a project subagent definition
  • ~/.claude/
  • CLAUDE.md # personal preferences only
  • commands/ skills/ # personal variants, distinct names
  • ~/.claude.json # personal MCP servers

If you can draw this tree from memory and say what each file is for, you can answer most Domain 3 questions.

Use ← → to move between sections