M00B: Hello World — Three Ways to Build an Agent

Before you spend 30 modules on agent theory, build the same tiny agent three different ways — raw API, Agent SDK, and spec-driven — in one sitting. By the end, you'll know which approach each later module is teaching you, and why.

Learning Objectives

  • Build the same "time helper" agent three different ways: raw Messages APIAnthropic's HTTP endpoint for sending a list of messages to Claude and getting back a response. The lowest-level building block in this course., claude-agent-sdk, and a spec-driven generation
  • Recognise the hand-rolled tool-use loopA while-loop that sends messages to Claude, checks if Claude requested a tool, runs that tool, sends the result back, and repeats until the model says it's done. — and see exactly what the SDK abstracts away
  • Read a real agent-spec.md and watch Claude Code generate the entire agent from it
  • Identify which approach matches your situation: learning, shipping, or prototyping fast
  • Map the three tiers (Tier 1, Tier 2, Tier 3) to the modules and capstones you'll meet later in the course

Why Three Approaches?

Everyday Analogy

BEFORE: Imagine three ways to make morning coffee. You can grind beans by hand, heat water on the stove, and pour-over with a kettle — full control, every variable yours. Or you press a button on an espresso machine — the machine handles temperature, pressure, and timing, you just pick the bean and the cup size. Or you tell your phone "make my usual latte before 7am" and a smart appliance follows the recipe you wrote last week.

PAIN: If you only ever pressed the button, you'd never understand why your coffee tastes bitter when the grind is wrong. If you only ever ground by hand, you'd never ship coffee at any reasonable scale. And if you only ever wrote recipes, you'd be lost the day the appliance breaks and someone hands you raw beans.

MAPPING: Building agents has the same three modes. Raw Messages APIAnthropic's HTTP endpoint for sending a list of messages to Claude and getting back a response. is grinding by hand — you write the loop, manage state, parse tool calls. claude-agent-sdk is the espresso machine — you declare tools and options, the SDK runs the loop. Spec-driven is the recipe — you describe the agent in markdown, Claude Code generates the project. This module shows you all three with the same toy agent so the differences are obvious, not abstract.

Technical Definition — The Three Tiers

Tier 1 (Raw API): You write the agent loop yourself. Modules M01–M11 use this so you can see every moving part.

Tier 2 (Dual-track): Same agent shown two ways — the manual loop and the SDK version — so the SDK stops feeling like a black box. Modules M12–M19.

Tier 3 (SDK + Spec): The SDK is your default; on flagship labs and capstones you write a spec and Claude Code generates the project. Modules M15B, M22B, M25–M27B, and capstones 1–5/7.

The Abstraction Ladder

Each approach hides more of the machinery. The animation below climbs the ladder one rung at a time. Watch the red boxes (the parts you have to write) shrink, and the cyan/pink boxes (the parts the SDK or generator handles) grow.

The Abstraction Ladder — What You Write vs What's Provided
Approach 1
Raw Messages API
Tool-use loop (you)
Tool dispatch (you)
Tool definitions JSON (you)
Message accumulation (you)
HTTP — messages.create
Lines of code: ~50 · Mental load: high
Approach 2
claude-agent-sdk
query() loop (SDK)
Tool dispatch (SDK)
@tool decorators (you)
MCP server wiring (SDK)
HTTP — messages.create
Lines of code: ~25 · Mental load: medium
Approach 3
Spec-Driven
agent-spec.md (you)
Code generation (Claude Code)
query() loop (SDK)
Tools, tests, deps (generated)
HTTP — messages.create
Lines of code you write: ~0 · Mental load: design-first
Why It Matters

If you skip Tier 1 and start with the SDK, you'll panic the first time a tool call fails or the loop hangs — you won't know what's actually happening underneath. If you stay on Tier 1 forever, you'll re-implement the same loop in every project and miss out on hooks, sessions, and permissions that the SDK gives you for free. The course walks the ladder in order. This module is your first peek at every rung at once.

Setup (5 minutes)

You'll build all three versions in a single project folder. Each version is self-contained so you can run them independently.

STEP 1Create the project and a virtual environment.

bash
mkdir hello-agent && cd hello-agent
python -m venv .venv
# macOS / Linux:
source .venv/bin/activate
# Windows PowerShell:
# .venv\Scripts\Activate.ps1

STEP 2Install dependencies for all three approaches. The raw approach needs only anthropic. The SDK and spec-driven approaches need claude-agent-sdk. Claude Code is the CLI that powers the spec-driven generation.

bash
# Python packages
pip install anthropic claude-agent-sdk

# Claude Code CLI (for Approach 3)
npm install -g @anthropic-ai/claude-code

# Node.js packages (only if you want the JS versions)
npm init -y
npm install @anthropic-ai/sdk @anthropic-ai/claude-agent-sdk

STEP 3Set your API key. Get one from console.anthropic.com. Both the raw API and the SDK read this environment variable.

bash
# macOS / Linux
export ANTHROPIC_API_KEY="sk-ant-..."

# Windows PowerShell
$env:ANTHROPIC_API_KEY = "sk-ant-..."
⚠ Don't Hardcode Keys

Never paste your API key directly into a script. Always read it from an environment variable so it doesn't end up in version control. The same warning applies in every later module — this is your one and only friendly reminder.

✅ Checkpoint: Run python -c "import anthropic, claude_agent_sdk; print('ok')" and claude --version. If both succeed, you're ready.

Approach 1 — Raw Messages API

What you're building: An agent that answers "what time is it in [city]?" by calling a get_time tool. The agent has zero scaffolding — you write the entire loop. Everything you'll learn in M01–M11 lives inside this file.

The Tool-Use Loop — In Plain English

Every agent does the same five-step dance:

  1. Send the conversation so far to Claude.
  2. Check the response. If stop_reason is "end_turn", the agent is done.
  3. If stop_reason is "tool_use", find each tool_use block, look up the tool by name, and run it.
  4. Append the assistant message and a matching tool_result for every tool call back into the conversation.
  5. Loop back to step 1.

The Code

Save this as raw_agent.py. Read the comments — they label every step of the loop above.

"""raw_agent.py — Hello World, hand-rolled tool-use loop."""
import os
from datetime import datetime
from zoneinfo import ZoneInfo
import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY

# ---- Tool implementation (plain Python) ----
TIMEZONES = {
    "new york": "America/New_York",
    "london": "Europe/London",
    "tokyo": "Asia/Tokyo",
    "sydney": "Australia/Sydney",
    "san francisco": "America/Los_Angeles",
}

def get_time(city: str) -> str:
    tz_name = TIMEZONES.get(city.lower())
    if tz_name is None:
        return f"Unknown city: {city}. Known cities: {', '.join(TIMEZONES)}."
    now = datetime.now(ZoneInfo(tz_name))
    return now.strftime("%H:%M on %A, %d %b %Y")

# ---- Tool schema (what Claude sees) ----
TOOLS = [{
    "name": "get_time",
    "description": "Get the current local time in a major city.",
    "input_schema": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "City name, e.g. Tokyo"}
        },
        "required": ["city"],
    },
}]

# ---- The loop ----
def run_agent(user_message: str) -> str:
    messages = [{"role": "user", "content": user_message}]
    while True:
        resp = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=TOOLS,
            messages=messages,
        )

        # Step 2: did Claude finish?
        if resp.stop_reason == "end_turn":
            return "".join(b.text for b in resp.content if b.type == "text")

        # Step 3+4: Claude requested at least one tool call.
        messages.append({"role": "assistant", "content": resp.content})
        tool_results = []
        for block in resp.content:
            if block.type != "tool_use":
                continue
            if block.name == "get_time":
                result_text = get_time(block.input["city"])
            else:
                result_text = f"Unknown tool: {block.name}"
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": result_text,
            })
        messages.append({"role": "user", "content": tool_results})
        # Step 5: loop back

if __name__ == "__main__":
    print(run_agent("What time is it in Tokyo right now?"))
// raw_agent.mjs — Hello World, hand-rolled tool-use loop.
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic(); // reads ANTHROPIC_API_KEY

const TIMEZONES = {
  "new york": "America/New_York",
  "london": "Europe/London",
  "tokyo": "Asia/Tokyo",
  "sydney": "Australia/Sydney",
  "san francisco": "America/Los_Angeles",
};

function getTime(city) {
  const tz = TIMEZONES[city.toLowerCase()];
  if (!tz) return `Unknown city: ${city}. Known cities: ${Object.keys(TIMEZONES).join(", ")}.`;
  return new Intl.DateTimeFormat("en-GB", {
    timeZone: tz, hour: "2-digit", minute: "2-digit",
    weekday: "long", day: "2-digit", month: "short", year: "numeric",
  }).format(new Date());
}

const TOOLS = [{
  name: "get_time",
  description: "Get the current local time in a major city.",
  input_schema: {
    type: "object",
    properties: { city: { type: "string", description: "City name, e.g. Tokyo" } },
    required: ["city"],
  },
}];

async function runAgent(userMessage) {
  const messages = [{ role: "user", content: userMessage }];
  while (true) {
    const resp = await client.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 1024,
      tools: TOOLS,
      messages,
    });

    if (resp.stop_reason === "end_turn") {
      return resp.content.filter(b => b.type === "text").map(b => b.text).join("");
    }

    messages.push({ role: "assistant", content: resp.content });
    const toolResults = [];
    for (const block of resp.content) {
      if (block.type !== "tool_use") continue;
      const text = block.name === "get_time"
        ? getTime(block.input.city)
        : `Unknown tool: ${block.name}`;
      toolResults.push({ type: "tool_result", tool_use_id: block.id, content: text });
    }
    messages.push({ role: "user", content: toolResults });
  }
}

console.log(await runAgent("What time is it in Tokyo right now?"));
What Just Happened?

Two things went out to Claude (the user message, then a tool_result) and two things came back (a tool_use request, then final text). You wrote ~50 lines, of which exactly one is the actual API call. The other 49 are loop, dispatch, and bookkeeping. That bookkeeping is what the SDK abstracts away in Approach 2 — but knowing it exists is the difference between "the agent froze" and "I forgot to append the tool_result."

Run It

bash
# Python
python raw_agent.py

# Node.js
node raw_agent.mjs
Expected output (yours will differ by minute)
The current local time in Tokyo is 14:37 on Friday, 09 May 2026.
🎓 Cert Tip

The Claude Certified Architect exam expects you to recognise this loop on sight. Domain 2 (Tool Use) explicitly tests stop_reason == "tool_use", the tool_use_id/tool_result pairing, and the strict alternation of assistantuser(tool_result) messages. If any one of those is missing in an exam answer, it's the wrong answer.

Approach 2claude-agent-sdk

Same agent, fewer lines. The SDK hides the loop, the message accumulation, and the JSON tool schemas. You declare tools as decorated functions, declare options once, then call query() and consume an async stream.

What the SDK Adds That Raw Doesn't
  • @tool decorator — turns a Python function into an MCP tool with auto-generated schema.
  • create_sdk_mcp_server — wires your tools into an in-process MCPModel Context Protocol — Anthropic's open standard for connecting tools, data, and prompts to LLMs. The SDK wraps your tools as an MCP server so the same code works locally and remotely. server.
  • ClaudeAgentOptions — one place for system prompt, model, max turns, allowed tools, hooks, and permissions.
  • query() — async iterator that yields message events as they arrive. The loop is gone.
  • Hooks & permissionscan_use_tool, PreToolUse, PostToolUse. You meet these in M16/M17/M26.

The Code

Save this as sdk_agent.py. Note the call to the model is now a single async for — everything else is configuration.

"""sdk_agent.py — Hello World, claude-agent-sdk."""
import asyncio
from datetime import datetime
from zoneinfo import ZoneInfo

from claude_agent_sdk import (
    query,
    tool,
    create_sdk_mcp_server,
    ClaudeAgentOptions,
    AssistantMessage,
)

TIMEZONES = {
    "new york": "America/New_York",
    "london": "Europe/London",
    "tokyo": "Asia/Tokyo",
    "sydney": "Australia/Sydney",
    "san francisco": "America/Los_Angeles",
}

@tool("get_time", "Get the current local time in a major city.", {"city": str})
async def get_time(args):
    tz_name = TIMEZONES.get(args["city"].lower())
    if tz_name is None:
        text = f"Unknown city: {args['city']}. Known cities: {', '.join(TIMEZONES)}."
    else:
        text = datetime.now(ZoneInfo(tz_name)).strftime("%H:%M on %A, %d %b %Y")
    return {"content": [{"type": "text", "text": text}]}

server = create_sdk_mcp_server(name="time_tools", version="1.0.0", tools=[get_time])

options = ClaudeAgentOptions(
    system_prompt="You are a friendly time assistant. Always include the day of the week.",
    mcp_servers={"time": server},
    allowed_tools=["mcp__time__get_time"],
    max_turns=4,
    model="claude-sonnet-4-6",
)

async def main():
    async for msg in query(prompt="What time is it in Tokyo right now?", options=options):
        if isinstance(msg, AssistantMessage):
            for block in msg.content:
                if hasattr(block, "text"):
                    print(block.text)

if __name__ == "__main__":
    asyncio.run(main())
// sdk_agent.mjs — Hello World, claude-agent-sdk.
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const TIMEZONES = {
  "new york": "America/New_York",
  "london": "Europe/London",
  "tokyo": "Asia/Tokyo",
  "sydney": "Australia/Sydney",
  "san francisco": "America/Los_Angeles",
};

const getTimeTool = tool(
  "get_time",
  "Get the current local time in a major city.",
  { city: z.string() },
  async (args) => {
    const tz = TIMEZONES[args.city.toLowerCase()];
    const text = !tz
      ? `Unknown city: ${args.city}. Known cities: ${Object.keys(TIMEZONES).join(", ")}.`
      : new Intl.DateTimeFormat("en-GB", {
          timeZone: tz, hour: "2-digit", minute: "2-digit",
          weekday: "long", day: "2-digit", month: "short", year: "numeric",
        }).format(new Date());
    return { content: [{ type: "text", text }] };
  }
);

const server = createSdkMcpServer({ name: "time_tools", tools: [getTimeTool] });

for await (const msg of query({
  prompt: "What time is it in Tokyo right now?",
  options: {
    systemPrompt: "You are a friendly time assistant. Always include the day of the week.",
    mcpServers: { time: server },
    allowedTools: ["mcp__time__get_time"],
    maxTurns: 4,
    model: "claude-sonnet-4-6",
  },
})) {
  if (msg.type === "assistant") {
    for (const block of msg.content) {
      if (block.text) console.log(block.text);
    }
  }
}
What Just Happened?

There is no while loop. There is no stop_reason check. There is no manual tool_result assembly. You declared what tools exist and what options apply; the SDK handles when to call them and how to plumb the results back. The agent loop went from ~50 lines to a single async for. This is what every Tier 3 lab in the course looks like.

Run It

bash
# Python
python sdk_agent.py

# Node.js (needs "type": "module" in package.json)
node sdk_agent.mjs
Expected output
The current local time in Tokyo is 14:37 on Friday, 09 May 2026.
✅ Checkpoint: Both raw_agent.py and sdk_agent.py should produce semantically identical answers. If they do, you've just built the same agent at two different abstraction layers.

Approach 3 — Spec-Driven

This is the approach you'll use on every Tier 3 capstone (M15B and CAPSTONEs 1–5/7). Instead of writing the agent, you write a specification — a markdown file that describes what the agent does, what tools it has, and what tests should pass. Then you ask Claude CodeAnthropic's terminal-based AI engineer. The CLI you installed in Step 2. It can read your project, generate files, run commands, and iterate based on feedback. to build the project.

Why This Mirrors Real Engineering

In a real team, an architect writes a design doc; an engineer implements it. With Claude Code, you are the architect and Claude Code is the engineer. The spec is your contract: when something is wrong, you fix the spec and regenerate — you don't patch generated code by hand. This pattern is on the certification exam (Domain 5: Claude Code) and is the workflow you'll see in M25 and every Tier 3 capstone.

The Spec

Create the folder and save the file below as spec/agent-spec.md. This is a real, complete spec — not pseudocode. Read it carefully; every section has a purpose.

bash
mkdir -p spec
# then save the file below as spec/agent-spec.md
spec/agent-spec.md
# Agent Specification: Time Helper

## Overview
A small assistant agent that answers natural-language questions about the
current local time in major world cities. Built as the canonical "Hello World"
example for spec-driven agent development.

## Agent Configuration
- Model: claude-sonnet-4-6
- Framework: claude-agent-sdk (Python). Use `query`, `tool`,
  `create_sdk_mcp_server`, `ClaudeAgentOptions`, and `AssistantMessage`.
- Max turns per request: 4
- Max tokens per response: 1024
- Temperature: default

## System Prompt
You are a friendly time assistant. When asked about the time, always call the
get_time tool rather than guessing. Always include the day of the week and the
city in your response. If the user asks for an unknown city, apologise briefly
and list the cities you know about.

## Tools

### get_time
- Description: Get the current local time in a major city.
- Parameters: city (string, required) — name of a city such as "Tokyo".
- Returns: A short string of the form "HH:MM on Weekday, DD Mon YYYY".
- Implementation: Use Python's `zoneinfo.ZoneInfo` with this exact mapping:
  - "new york"      -> "America/New_York"
  - "london"        -> "Europe/London"
  - "tokyo"         -> "Asia/Tokyo"
  - "sydney"        -> "Australia/Sydney"
  - "san francisco" -> "America/Los_Angeles"
  Lookup must be case-insensitive. Unknown cities return:
  "Unknown city: {city}. Known cities: {comma-separated list}."

## Files to Generate
- `agent.py`             — entry point. Wires the tool, options, and async loop.
- `tests/test_agent.py`  — pytest tests (see "Tests" below).
- `requirements.txt`     — pinned `claude-agent-sdk` and `pytest`.
- `README.md`            — one paragraph: what it does and how to run it.

## Tests (must all pass)
1. `get_time` for "Tokyo" returns a string matching the regex
   `^\d{2}:\d{2} on \w+, \d{2} \w{3} \d{4}$`.
2. `get_time` for "Mars" returns a string starting with "Unknown city:".
3. Lookup is case-insensitive: "TOKYO", "tokyo", and "ToKyO" all succeed.

## Out of Scope
- Persistence / sessions
- Hooks or permission gates (those come in M16/M17/M26)
- Multi-language responses

Generate & Run

From the project root, with the spec saved, run Claude Code with this exact prompt:

bash
claude "Read spec/agent-spec.md and build the entire project as described. \
Generate every file in 'Files to Generate', then run pytest and show me the results."

Claude Code will read the spec, generate agent.py, the tests, the requirements file, and the README, then execute the test suite. Watch it work — the workflow is the lesson.

Expected end of session (abridged)
Created agent.py
Created tests/test_agent.py
Created requirements.txt
Created README.md
Running: pytest -q
...                                                                      [100%]
3 passed in 0.42s

Now run the generated agent end-to-end:

bash
python agent.py
What Just Happened?

You did not write a single line of agent code. You wrote a specification and Claude Code wrote, tested, and verified the implementation. To change behaviour — say, add Berlin to the timezone list — you edit the spec and regenerate. The spec is the source of truth; generated code is the build artefact. This inverts how most developers think about prompts and code, and it is exactly how every Tier 3 capstone in this course works.

⚠ Read the Generated Code

Spec-driven does not mean "trust the output blindly". After generation, open agent.py and verify it imports from claude_agent_sdk (not raw anthropic), uses query(), and matches the spec. If it doesn't, your spec was ambiguous — tighten it and regenerate. This habit is what makes you good at spec-driven development.

🎓 Cert Tip

Domain 5 of the Claude Certified Architect exam tests Claude Code workflows including spec-driven generation. Recognise the pattern: a spec lives in spec/agent-spec.md, generation is one claude command, and iteration is "edit the spec, regenerate" — never "edit the generated file directly".

Side-by-Side

The same agent, three abstractions. Numbers below come from the actual files you just wrote.

Dimension Raw API Agent SDK Spec-Driven
Lines of code you wrote~50~250 (you wrote a spec)
Where the agent loop livesYour whileInside SDK query()Generated by Claude Code
Tool schemasHand-written JSON@tool decoratorGenerated from spec
Hooks & permissionsBuild yourselfBuilt-in fieldsAdd a section to the spec
TestsBuild yourselfBuild yourselfListed in spec, generated
Best forLearning the protocolShipping production agentsCapstones, fast prototyping
Course tierTier 1 (M01–M11)Tier 2 & 3 (M12+)Tier 3 (M15B, capstones)

When to Use Which

Use Raw API when…

You're learning. The first time you build an agent, write the loop yourself — once. Then you'll never confuse "agent magic" with "SDK convenience".

You need fine-grained control: custom retry logic, unusual streaming, or you're embedding the call in a system that already has its own event loop.

You're debugging an SDK problem and want to confirm the underlying behaviour.

Use Agent SDK when…

You're shipping production code. The SDK gives you hooks, sessions, permissions, MCP servers, and subagents out of the box. Re-implementing those by hand is wasted work.

You're going to need observability, audit logs, or human-in-the-loop — the SDK's hook points are where those plug in cleanly.

This is the default for everything from M12 onward in this course.

Use Spec-Driven when…

You're at the start of a new project and the design isn't fixed. Iterate on the spec, regenerate, run tests — you converge on the design without committing to code.

You're working on a Tier 3 capstone or M15B / M22B. The course mandates this workflow there because it's the cert pattern and it scales.

You want a clean handoff: the spec is the design doc and the prompt at the same time. New team-mates read one file.

The Bigger Picture

These approaches stack. By the end of M15B you'll be writing a spec, generating an SDK-based agent, and reading the SDK's source to understand what it abstracts — all in the same lab. M00B was the trailer. M01 starts the film.

Knowledge Check

Five quick questions to lock in the difference between the three approaches before you move on to M01.

Q1: In the raw API approach, what tells the agent loop to stop?

AAn empty content array on the response
Bresp.stop_reason == "end_turn"
CA finished field on the last tool result
DThe SDK fires a stop event automatically
Correct! In the raw API loop you check stop_reason. "end_turn" means Claude is done; "tool_use" means it wants you to run a tool and reply with a tool_result.

Q2: Which line in the SDK version replaces the entire while loop from the raw version?

Acreate_sdk_mcp_server(...)
BClaudeAgentOptions(...)
Casync for msg in query(...)
DThe @tool decorator
Correct! query() is an async iterator that runs the entire tool-use loop for you and yields each message as it arrives. The decorator and options blocks are configuration; async for is the execution.

Q3: In spec-driven development, where is the source of truth?

Aspec/agent-spec.md — the generated code is a build artefact
BThe generated agent.py — the spec is just a starting prompt
CWhichever was edited most recently
DThe README.md
Correct! In spec-driven workflows, you change behaviour by editing the spec and regenerating — not by patching generated code. That discipline keeps the spec and the implementation in sync, and it's the workflow tested on the cert exam.

Q4: A team-mate is using the SDK but is debugging an issue where the agent doesn't use a tool they registered. Which approach gives them the fastest signal about what's actually happening?

ASwitch to spec-driven and regenerate the project
BAdd more options to ClaudeAgentOptions until it works
CIncrease max_turns to 100
DReproduce the same call against the raw API and inspect the response
Correct! When the SDK feels like a black box, dropping back to the raw API is a debugging superpower. You can see the exact tool_use blocks Claude is (or isn't) producing, and the fix is usually obvious once you do.

Q5: Which combination of (Approach → Tier → First module that uses it) is correct?

ARaw API → Tier 3 → M25
BAgent SDK → Tier 2 → M12 (ReAct)
CSpec-driven → Tier 1 → M01
DAgent SDK → Tier 1 → M02
Correct! Tier 2 starts at M12 (ReAct) where the course pivots from raw-only to "manual + SDK side-by-side". M01–M11 are pure Tier 1; Tier 3 (SDK + spec-driven) starts at M15B and dominates the capstones.

Module Summary

Key Takeaways

  • Same agent, three abstractions. A "what time is it?" agent is ~50 lines raw, ~25 lines with the SDK, and 0 lines you write under spec-driven generation.
  • The tool-use loop is the heart of every agent. The SDK doesn't make it disappear — it just hides the bookkeeping. Knowing it exists makes you a better debugger.
  • Spec-driven flips the workflow. The spec is the source of truth; generated code is the build artefact. Iterate on the spec, regenerate, run tests.
  • Tier 1 → Tier 2 → Tier 3. The course climbs the abstraction ladder deliberately. M01–M11 raw, M12–M19 dual-track, M15B and capstones spec-driven.
  • You'll switch tiers based on the situation. Learning → raw. Shipping → SDK. Designing or scaling → spec.

Next Module Preview: M01 — The LLM Mental Model

You've seen how an agent talks to Claude. Module 1 zooms in on the model itself: how it predicts text, why temperature matters, and the "thinker, not calculator" mental model that shapes every design decision in the rest of the course. After that, M05 is where you'll build your first real production-style raw-API tool-use loop end-to-end.