Module 0B of 30
Hello World
Three Approaches

Build the same toy agent three ways: as a hand-rolled tool-use loop on the raw Messages API, as a declarative claude-agent-sdk project, and as a markdown spec that Claude Code turns into code for you. See the abstraction ladder before you climb it.

Foundation Track ⏱ ~18 min read 0B / 30
Concept 1 of 6

Three rungs on the same ladder

There is exactly one HTTP call at the bottom of every Claude agent: messages.create. Everything else — the loop, the tool dispatch, the JSON schemas, the retries — is scaffolding that you can either write yourself, get from a library, or generate from a spec. Three rungs, same call underneath.

This module shows you all three rungs at once with the same toy agent (a time-lookup helper). You'll see the raw loop you'll learn to write in M01–M11, the SDK pattern you'll use from M12 onward, and the spec-driven workflow that powers M15B and every Tier 3 capstone. By the end you'll know why the course climbs the ladder in that order — not just what each rung looks like.

Concept 1 of 6

Three ways to make coffee

BEFORE: Picture three ways to make a latte. You can grind the beans by hand, heat water on the stove, weigh the dose, and pour over — every variable yours. You can press one button on a bean-to-cup espresso machine — it handles grind, dose, pressure, temperature. Or you can tell your smart kitchen, "make my usual before 7am," and an appliance follows the recipe you wrote last week.

PAIN: If you only ever press the button, you panic the day the machine breaks. If you only ever grind by hand, you can't make coffee at scale. If you only ever write recipes, you're lost when someone hands you raw beans and a stove.

MAPPING: Raw API is grinding by hand. The SDK is the espresso machine — one button, every detail handled. Spec-driven is the recipe — you describe the latte, the appliance assembles it. Same drink, three abstractions. Knowing all three is what makes you fluent.

Concept 1 of 6

The abstraction ladder

  1. Rung 1 — Raw Messages APIYou write the tool-use loop, dispatch tools by name, accumulate messages, check stop_reason. ~50 lines of which exactly one is the API call. Mental load: high. Visibility: total.
  2. Rung 2 — claude-agent-sdkYou declare tools with a decorator and options with one config object, then call query() and iterate. ~25 lines. Loop is gone. Hooks, sessions, permissions are built in.
  3. Rung 3 — Spec-DrivenYou write a markdown spec describing the agent. Claude Code reads it, generates agent.py, tests, and a README. Zero lines of agent code from you — the spec is the source of truth.
  4. What stays the sameEvery rung still bottoms out at messages.create. The same Claude model. The same prompt-and-response protocol. Only the scaffolding above it changes.
Concept 1 of 6

How the course uses each rung

TierModulesApproach
Tier 1M01–M11, M15, M18, M20–M22Raw API — see every moving part
Tier 2M12–M14, M16, M17, M19Dual: raw + SDK side by side
Tier 3M15B, M22B, M25–M27B, capstones 1–5/7SDK + spec-driven

The ladder is intentional. Tier 1 builds the protocol intuition. Tier 2 reveals the SDK by contrast. Tier 3 lets you stop writing agent code and start designing agents in markdown.

Concept 1 of 6

Misconceptions

"The SDK is the right answer. Just skip raw API."
If you never write the loop, the day a tool call hangs or a tool_result mismatches you'll have no mental model to debug from. The SDK is fast precisely because you know what it's abstracting.
"Spec-driven is just fancy prompting."
A spec is a contract, not a prompt. You edit the spec and regenerate — you don't patch the generated code. That discipline is what keeps design and implementation in lockstep, and it's what the cert exam tests.

Three rungs, one call. Raw API teaches you the protocol. The SDK gives you production scaffolding for free. Spec-driven inverts code and design so the markdown becomes the source of truth.

You will use all three across the course — on purpose, in this order.

Concept 2 of 6

Write the loop yourself, once

The raw Messages API is the lowest-level path. You write a while loop that sends messages to Claude, inspects stop_reason, runs any tools Claude asked for, appends the tool_result, and sends again. Nothing is hidden — every line of the agent loop lives in your file.

About 50 lines total. Roughly one of them is the actual API call; the other 49 are loop control, tool dispatch, and bookkeeping. That ratio is the whole point: once you see it, the SDK stops feeling like magic and starts looking like a useful abstraction.

Concept 2 of 6

The home cook with a chef's knife

BEFORE: Picture a home cook who sharpens their own knife, butchers the chicken themselves, chops every herb by hand, and watches the pan because they don't trust a timer. Every step they own.

PAIN: It takes them an hour to plate one dish. They can't scale to twenty diners. But the day the gas oven misfires they know exactly which part is broken — they've touched every component of the meal.

MAPPING: Raw API is the home cook with the chef's knife. Slower per dish, but every part of the agent — the loop, the tool dispatch, the message accumulation — is in your hand. When something goes wrong later (in the SDK, in production), you'll instantly know which layer to blame.

Concept 2 of 6

The five-step loop

  1. SendPost the conversation so far to Claude with your list of tool definitions attached.
  2. Check stop_reasonIf it's end_turn, Claude produced the final answer — return the text and exit. If it's tool_use, Claude wants you to run a tool.
  3. Run the toolFind each tool_use block in the response, look up the named tool, call it with the supplied arguments.
  4. Append both sidesAppend the assistant's response, then a user message containing one tool_result for every tool call. tool_use_id must match exactly.
  5. LoopGo back to step 1. Continue until stop_reason says end_turn.

Memorise this. Domain 2 of the Claude Certified Architect exam tests it directly.

Concept 2 of 6

Pseudocode

The toy agent: a time-lookup helper with one tool, get_time(city).

# --- Tool definition (what Claude sees) ---
DEFINE tool "get_time":
  input: city (string, required)
  returns: "HH:MM on Weekday, DD Mon YYYY"

# --- The loop ---
FUNCTION run_agent(user_message):
  messages = [user_message]

  WHILE true:
    response = CREATE_MESSAGE(
      model = sonnet,
      tools = [get_time],
      messages = messages
    )

    IF response.stop_reason == "end_turn":
      RETURN response.text

    # stop_reason == "tool_use" — run each tool
    messages.append(assistant = response.content)
    results = []
    FOR EACH block IN response.content:
      IF block.type == "tool_use":
        out = DISPATCH(block.name, block.input)
        results.append(tool_result(block.id, out))
    messages.append(user = results)

Note: each tool_result's tool_use_id must equal the matching tool_use's id. Off-by-one here is the #1 cause of "agent froze".

Concept 2 of 6

Misconceptions

"You exit the loop when content is empty."
No — you exit when stop_reason == "end_turn". Claude can return a final assistant message with both text and tool calls, but only stop_reason tells you the turn is complete.
"One tool call per turn."
Claude can return multiple tool_use blocks in one response — you must run all of them and append one tool_result per call, in the same user message, before looping.

The five-step loop is the entire agent. Send → check stop_reason → run tool → append both sides → loop.

Write it once with your own hands. After that, every higher abstraction will make sense as "what part of the loop did it just hide?"

Concept 3 of 6

Declare. Don't loop.

The claude-agent-sdk replaces the hand-rolled loop with a single async iterator. You declare tools using a decorator, you declare options in one config object, then you call query() and consume messages as they arrive. There is no while. There is no stop_reason check. The loop runs inside the SDK.

You gain hooks (PreToolUse, PostToolUse, can_use_tool), MCP server wiring, permissions, sessions, and subagents — all the production scaffolding you'd otherwise build yourself. From M12 onward this is the default in the course.

Concept 3 of 6

The bean-to-cup espresso machine

BEFORE: Picture a commercial espresso machine. You pick the bean, the cup size, and the milk. Inside the box, a calibrated grinder, a temperature-controlled boiler, and a pressure pump do the rest.

PAIN: Make ten lattes by hand and you'll be exhausted by number three. Hand-tune temperature on every shot and your shop never opens. You need consistent throughput.

MAPPING: The SDK is the espresso machine. You declare what you want (a tool, a system prompt, a model, an allow-list) and the machine runs the loop, dispatches the tools, and streams results. The protocol you wrote by hand in Approach 1 is now a service you configure.

Concept 3 of 6

What the SDK adds

  1. @tool decoratorTurns a plain function into an MCP tool with auto-generated schema. The JSON you wrote by hand in Approach 1 disappears.
  2. create_sdk_mcp_serverWires your tools into an in-process MCP server. Local and remote tools end up with the same call shape.
  3. ClaudeAgentOptionsOne config object holds the system prompt, model, max turns, allowed tools, hooks, and permission callbacks. No more loose arguments scattered through your code.
  4. query() async iteratorOne call replaces the entire while loop. It yields messages as they stream — assistant text, tool calls, tool results — in order.
  5. Hooks & permissionsPreToolUse, PostToolUse, and can_use_tool let you gate, log, or rewrite tool calls without rewriting the loop. You meet these in M16, M17, and M26.
Concept 3 of 6

Pseudocode

Same agent, no loop:

# --- Tool: decorator generates the schema for you ---
@tool("get_time", "current time in a city", {city: string})
FUNCTION get_time(args):
  RETURN text("HH:MM on Day, DD Mon YYYY")

# --- Wire the tool into an in-process MCP server ---
server = CREATE_MCP_SERVER(tools=[get_time])

# --- One config object for everything ---
options = AGENT_OPTIONS(
  system_prompt = "You are a time assistant.",
  mcp_servers   = {time: server},
  allowed_tools = ["mcp__time__get_time"],
  max_turns     = 4,
  model         = sonnet
)

# --- The entire agent loop, replaced by one line ---
FOR EACH msg IN SDK.QUERY(prompt=question, options=options):
  IF msg is assistant_text:
    PRINT msg.text

No while, no stop_reason, no tool_result assembly. The SDK runs the loop you wrote in Approach 1 — reliably, with hooks built in.

Concept 3 of 6

Misconceptions

"The SDK is just messages.create with extra steps."
No. The SDK adds the loop, MCP server wiring, hooks, permissions, and session management. Calling messages.create by hand and labelling it "SDK" misses every benefit and breaks any cert question that asks "which SDK call replaces the loop?"
"Once you adopt the SDK, you can't drop back."
The raw API is always there. A common production pattern is SDK for the main agent and raw API for a tight retry path or a streaming endpoint that needs custom backpressure. The two coexist in the same project.

The SDK trades visibility for productivity. Declare tools and options, call query(), iterate. The loop disappears, hooks appear.

This is the default from M12 onward. Every Tier 3 capstone starts here.

Concept 4 of 6

Write the spec. Claude writes the code.

Spec-driven flips the role. Instead of writing agent.py by hand, you write spec/agent-spec.md — a markdown document that describes what the agent does, what tools it has, what tests must pass, and what's out of scope. Then you run one Claude Code command and an entire SDK-based project appears.

Claude Code reads the spec, generates the agent, the tests, the requirements file, and the README, then runs the tests and shows you the results. When you want to change behaviour, you edit the spec and regenerate. The spec is the source of truth; generated code is a build artefact.

Concept 4 of 6

The architect and the engineer

BEFORE: On a real engineering team, an architect writes a design doc — system shape, interfaces, invariants, tests. An engineer implements it. When the design changes, you update the doc and the engineer rebuilds; you don't patch the binary.

PAIN: If the design doc and the code drift apart, every new engineer reads the code instead of the doc, and the doc rots. The fix is to make the doc executable — to wire it directly into the build.

MAPPING: In spec-driven, you are the architect and Claude Code is the engineer. The spec is your design doc, and it's executable: one claude command turns it into a working project. When the design changes you edit the spec, not the code.

Concept 4 of 6

The spec-driven workflow

  1. Write the specSave a markdown file at spec/agent-spec.md with sections: Overview, Agent Configuration, System Prompt, Tools, Files to Generate, Tests, and Out of Scope.
  2. Run Claude CodeOne command: claude "Read spec/agent-spec.md and build the entire project as described." Claude Code reads the file, plans, and generates.
  3. Claude generates the projectagent.py, tests/test_agent.py, requirements.txt, and README.md appear. The agent uses claude-agent-sdk under the hood — spec-driven sits on top of Approach 2.
  4. Tests run automaticallyClaude Code runs pytest and shows you the results. If a test fails, it iterates and fixes — or surfaces the failure to you.
  5. Iterate on the spec, not the codeTo add a city, fix a tone, or tighten a behaviour, edit the markdown and regenerate. The discipline keeps spec and code in lockstep.
Concept 4 of 6

Pseudocode

The whole workflow in two blocks — the spec sketch and the build command:

# --- spec/agent-spec.md (your single source of truth) ---
SPEC:
  overview        = "a time-lookup helper agent"
  model           = sonnet
  framework       = claude-agent-sdk
  system_prompt   = "You are a time assistant..."
  tools           = [get_time(city) -> "HH:MM on Day, ..."]
  files_to_emit   = [agent.py, tests/, requirements.txt, README.md]
  tests_must_pass = [
    "Tokyo returns matching regex",
    "Unknown city returns 'Unknown city:'",
    "Lookup is case-insensitive"
  ]
  out_of_scope    = [sessions, hooks, permissions]

# --- Build the project from the spec ---
READ spec/agent-spec.md
GENERATE project   # Claude Code emits all files
RUN pytest         # auto-verifies the spec's contract

# --- To change behaviour, edit spec; never patch generated code ---
EDIT spec/agent-spec.md
REGENERATE

The spec is short, complete, and testable. The "out of scope" list is as load-bearing as the in-scope items — it stops Claude Code from inventing features you didn't ask for.

Concept 4 of 6

Misconceptions

"Spec-driven means I don't need to read the generated code."
Always open agent.py after generation and confirm it imports claude-agent-sdk, uses query(), and matches the spec. If it doesn't, your spec was ambiguous — tighten and regenerate. Trust comes from inspection, not from the workflow.
"If the generated code has a bug, patch it directly."
No. Patching generated code is how spec and reality desynchronise. The discipline: fix the spec (the bug was an ambiguity) and regenerate. Domain 5 of the cert exam tests this habit explicitly.

Spec-driven inverts the source of truth. The markdown is canonical; the code is generated. Edit the spec, regenerate — never patch the output.

Every Tier 3 capstone (M15B, M22B, capstones 1–5/7) uses this workflow. M00B is the first place you see it.

Concept 5 of 6

One agent, three abstractions, measured

Same toy agent — the time-lookup helper — built three ways. Same model, same tool behaviour, same final answer. The interesting differences are not in what the agent does but in what you write to make it do that thing, and in where the loop lives.

Putting the three side by side makes the trade obvious: lines of code, where logic lives, how you test, how you change behaviour. None of the three is "best" universally — each wins in a specific situation. The comparison itself is the lesson.

Concept 5 of 6

Three drivers, one destination

BEFORE: Three drivers leave the same city at 9am and meet at the same restaurant at noon. One drives a stick-shift with no GPS. One drives an automatic with lane-keep. One sits in the back of an autonomous car reading the newspaper.

PAIN: Compare only the destination and they're identical. Compare effort, attention, repair-ability, and "what happens when something breaks" and they're radically different cars.

MAPPING: Three agents arrive at the same answer. The interesting comparison is what each cost you to operate: how many lines, who runs the loop, who knows when something failed, who can teach you the protocol when the magic stops working.

Concept 5 of 6

What changes, what stays the same

  1. What stays the sameThe model, the prompt-and-response protocol, the final answer the user sees. Every rung calls Claude with messages and tools and gets back text + tool_use blocks.
  2. Lines you write~50 (raw) → ~25 (SDK) → 0 lines of agent code (spec; you wrote a ~40-line markdown spec instead).
  3. Where the loop livesYour while (raw) → inside query() (SDK) → inside the generated agent that uses query() (spec).
  4. How you change behaviourEdit Python (raw) → edit config + Python (SDK) → edit markdown and regenerate (spec).
  5. What you give up at each stepVisibility → less of it but you gain hooks/permissions → less of it but you gain a single source of truth and automatic tests.
Concept 5 of 6

The numbers that actually matter

DimensionRawSDKSpec
Lines you write~50~250 code
Loop lives inYour whilequery()Generated
Tool schemaHand JSON@toolFrom spec
Hooks & permissionsDIYBuilt inSpec section
TestsDIYDIYGenerated
Source of truthCodeCodeSpec.md
Best forLearningShippingCapstones
Course tier12 & 33

Numbers are from the actual files in the M00B desktop lab. They'll match within ~10% on your machine.

Concept 5 of 6

Misconceptions

"Fewer lines means better."
Lines are a proxy, not the goal. Raw API is the longest and the right choice when you're learning, debugging the SDK, or embedding the call in a non-standard event loop. "Fewer lines" matters most when the lines you skipped are ones you'd have to maintain.
"The three approaches are mutually exclusive."
They stack. A Tier 3 capstone uses a spec to generate an SDK project, and you can drop into the raw API for one tight inner loop if you need control there. Real production agents often combine all three in the same repo.

The same agent, three abstractions, measurably different cost. The cost difference isn't latency or accuracy — it's mental load, who owns the loop, and where behaviour lives.

Pick the rung that matches your situation; rotate as your situation changes.

Concept 6 of 6

Match the rung to the moment

There is no single "right" approach — there's a right approach for your current situation. Are you learning the protocol? Debugging an SDK issue? Shipping a production endpoint with hooks and audit logs? Bootstrapping a brand-new agent whose design isn't fixed?

Each rung wins in a specific situation and loses in the others. The skill is not picking "the best" rung once — it's recognising the situation and reaching for the matching rung. The decision framework on the next two cards gives you a checklist.

Concept 6 of 6

The carpenter's three saws

BEFORE: A carpenter owns a hand saw, a circular saw, and a CNC router. The hand saw is slow and precise. The circular saw is fast and rough. The CNC router is programmed once and cuts a hundred identical pieces.

PAIN: The apprentice who only knows the CNC can't make a custom shelf for a friend. The veteran who refuses to use the CNC can't deliver fifty kitchens a month. The wrong tool wastes the project.

MAPPING: Raw API is the hand saw — precise, slow, teaches you everything. The SDK is the circular saw — fast, productive, the daily driver. Spec-driven is the CNC — design once, mill many. A complete carpenter owns and uses all three, on purpose.

Concept 6 of 6

Three situations, three winners

  1. Use Raw API when learningYour first agent should be hand-rolled. Once you can write the five-step loop from memory, the SDK stops feeling magical and starts looking like a useful tool.
  2. Use Raw API for unusual controlCustom retry policies, exotic streaming, embedding the call in an existing event loop, or debugging an SDK problem. Raw stays in your kit even after you adopt the SDK.
  3. Use the SDK when shippingHooks, sessions, permissions, MCP servers, subagents. Anything heading to production with observability and audit logs starts here.
  4. Use Spec-Driven for new projectsWhen the design isn't fixed, iterate on the spec instead of the code. You converge on the design without committing to an implementation that's expensive to throw away.
  5. Use Spec-Driven for handoffsThe spec is both the design doc and the build prompt. A new team-mate reads one markdown file and can regenerate the entire project from it.
Concept 6 of 6

Which rung, right now?

# What is your current situation?

IF you are learning the protocol for the first time:
  USE Raw Messages API
  # Write the five-step loop once, by hand

ELIF you are debugging the SDK or need unusual control:
  DROP to Raw Messages API for the affected path
  # The SDK and raw API coexist in one project

ELIF you are shipping production with hooks/audit/sessions:
  USE claude-agent-sdk
  # Default for everything from M12 onward

ELIF the design is unfixed OR the project is a Tier 3 capstone:
  USE Spec-Driven on top of the SDK
  # Iterate on the spec, regenerate the code

ELSE:
  DEFAULT to claude-agent-sdk
  # When unsure, the SDK is the safest default

These approaches stack. By M15B you'll write a spec that generates an SDK-based agent, with one tight raw-API code path inside it for retries — all in the same project.

Concept 6 of 6

Misconceptions

"Pick one approach and stick with it."
Mature teams use all three, often in the same repo. A spec generates the SDK-based agent, the SDK runs in production, and one raw-API code path handles a custom retry. Switching rungs intentionally is the mark of fluency.
"Spec-driven is only for big projects."
M00B's spec is ~40 lines and the agent it generates is a Hello World. Spec-driven scales down too — the discipline is the value, not the size. Use it any time the design might change.

Match the rung to the moment. Raw to learn or debug. SDK to ship. Spec to design and hand off.

You're at the start of the ladder. The next 30 modules walk you up every rung at least once.

Quick Quiz

Six questions, one per concept. Tap to reveal the answer.

Open the full module on desktop

The desktop version of M00B is a hands-on lab. You'll set up the project, install anthropic, claude-agent-sdk, and the Claude Code CLI, then build all three versions of the time-helper agent yourself — raw, SDK, and spec-driven — in one folder. The abstraction ladder animation, the full code blocks, and the live spec are all there.

Open M00B on Desktop → Real code · Setup · Hands-on lab · ~45 min

Module 0B of 30 · Foundation Track