Building AI Agents with Claude Bonus · Anthropic Engineering Digest
Bonus Module ~60 min Intermediate

Building Effective Agents

A guided walkthrough of Anthropic's official engineering guidance: what an agent actually is, the seven building-block patterns, and the rules for picking the simplest one that solves your problem.

🔗 Based on
https://www.anthropic.com/engineering/building-effective-agents

This bonus module synthesizes the article's core lessons in the same teach-by-analogy style as the rest of the course, with runnable Python and Node.js examples for each pattern. Quotes are paraphrased; visit the source for the original.

Learning Objectives

  • Distinguish workflows (LLMs on predefined code paths) from agents (LLMs that direct their own work) and explain why the distinction matters
  • Recognize the foundational augmented LLM building block (retrieval + tools + memory) underneath every other pattern
  • Identify the five workflow patterns — prompt chaining, routing, parallelization, orchestrator-workers, evaluator-optimizer — and pick the right one for a given task
  • Decide when full agent autonomy is justified and when a simpler workflow is the better engineering choice
  • Apply the agent-computer interface (ACI) principles when designing tools your agent will actually use well

Prerequisites: M00 (Course Overview), M05 (Function Calling), M12 (ReAct Loop) help, but this module is self-contained.  |  Level: Intermediate

Workflows vs. Agents — The Core Distinction

The first — and most important — thing the article does is define its terms. The word “agent” gets used loosely in the industry to mean anything from a chatbot to a fully autonomous robot. Anthropic narrows it down to one specific axis: who decides what step happens next?

Everyday Analogy

Before this distinction: Imagine the difference between a vending machine and a personal shopper. The vending machine has a fixed flow — insert coin, press B4, item drops. Every step is predetermined by the machine's wiring. A personal shopper, by contrast, hears your goal (“I need a gift for my mom’s birthday”) and decides themselves which stores to visit, what to compare, when to call you for clarification, and when to stop.

The pain: Most teams call both systems “AI agents” the moment Claude is involved. That hides the actual engineering choice. A vending machine is cheap, predictable, and easy to debug, but rigid — new product lines mean new buttons. A personal shopper handles novelty for free, but you can’t predict their cost, latency, or exact path on any given errand.

The mapping: Anthropic’s vocabulary draws the line cleanly. Workflows are vending machines: LLMs and tools wired together by code paths you wrote. Agents are personal shoppers: LLMs that read the goal and dynamically pick their own next step using tools in a loop. Both are useful. They are not the same thing, and conflating them leads to over-engineered prototypes and under-engineered production systems.

Technical Definition

The article splits the “agentic systems” umbrella into two precise categories:

Workflows are systems where LLMs and tools are orchestrated through predefined code pathsA workflow is code you wrote that calls one or more LLMs in a fixed sequence. The structure is static. The LLM fills in steps, but the path through those steps is hard-coded.. Your code calls the LLM once, takes its output, calls the LLM again, applies a rule, and so on. The structure of the program is fixed. You can read the source and predict every API call that will happen.

Agents are systems where LLMs dynamically direct their own processes and tool usageAn agent is an LLM running in a loop: it picks a tool, observes the result, picks the next tool, and decides on its own when to stop. Your code provides the tools and the loop, but not the order of operations.. You give the model a goal and a toolbox, and it decides which tool to call, in which order, and when to stop. The structure of the run is decided at runtime, by the model.

Why this split matters: every recommendation in the article hangs off it. Different patterns suit different points on this spectrum. Cost, debuggability, latency, and reliability all change as you move from workflow to agent.

One subtle but important note: the article doesn’t tell you to always pick one or the other. Most production systems combine both — a workflow at the top level that calls an agent for the genuinely open-ended sub-task, or an agent that internally invokes deterministic workflows as “tools.”

Fixed Path vs. Self-Directed Loop
Workflow — Predefined Path
1. Extract
2. Validate
3. Format
4. Return Result
Every run takes the same path.
Agent — Self-Directed Loop
Goal received
Plan
Pick tool
Reflect
Observe
Claude decides what to do next.
Path is decided at runtime.
Why It Matters

Picking the wrong side of this line is the most common cause of failed AI projects. Teams reach for “an agent” when their problem is actually a 3-step workflow — and end up with a system that costs 10× more, runs 5× slower, and is impossible to debug. Other teams build a rigid 50-step workflow when an agent could have handled the variety in 8 lines of code. Anthropic’s recommendation throughout the article is consistent: start simple, escalate complexity only when needed.

If workflows and agents are the two ends of a spectrum, when do you actually need the agent end? The next section is the article’s most quoted — and most ignored — recommendation.

When (and When Not) to Use Agents

The article opens with a recommendation that’s easy to read and easy to skip past: find the simplest solution possible, and only increase complexity when needed. It then drives the point home with a sharper version: “This might mean not building agentic systems at all.” Coming from the company that sells the model, that’s a notable thing to print. Take it seriously.

The reasoning is grounded in tradeoffs. Agentic systems trade latency and cost for better task performance on open-ended problems. That trade is worth it for some workloads and a bad deal for others. The decision tree below summarizes the article’s heuristics:

If your task is…Then start with…Why
A single LLM call solves itJust an LLM call (no pattern at all)Optimize the prompt with examples and retrieval before adding orchestration.
Decomposable into fixed sub-stepsPrompt chainingEach step is small and validatable. You buy accuracy with a tiny bit of latency.
Distinct categories needing different handlingRoutingOne specialized prompt per category beats one generalist prompt.
Multiple independent sub-tasks or perspectivesParallelizationSpeed (sectioning) or robustness via voting.
Sub-tasks unknowable in advanceOrchestrator-workersLet the model decompose at runtime; structure the rest.
Output quality has clear evaluation criteriaEvaluator-optimizerIterative refinement when feedback is measurable.
Open-ended, # of steps unknowable, sandboxedFull agentWorth the cost when nothing simpler fits.
Common Misconceptions

“An agent is more powerful, so it’s always better.” — No. An agent solving a problem a routing workflow could solve is just an unreliable, expensive, slow version of the same answer. Power isn’t free.

“If we use a framework, we don’t need to think about this.” — The article warns that frameworks (Claude Agent SDK, LangChain, Vellum, Rivet) make it easy to start but often add abstractions that obscure prompts and make debugging painful. Even when you use one, understand the underlying pattern. Prefer LLM APIs directly until the abstraction earns its weight.

“Agents and chatbots are the same thing.” — A chatbot generates responses. An agent uses tools in a loop to take actions in the world. The difference is whether side effects happen mid-conversation.

💵 The Cost/Latency Reality

A single LLM call: 1 API request, ~1-3 seconds, ~$0.003 (Sonnet). A 3-step prompt chain: 3 calls, ~3-9 seconds, ~$0.009. An agent that loops 10 times to finish a task: 10 calls, 30+ seconds, ~$0.030 — and unbounded if it doesn’t converge. The simplest pattern that works wins. Reach for the next pattern only when the simpler one demonstrably fails on your eval set.

Before any of the patterns make sense, you have to understand the building block they’re all assembled from. The article calls it the “augmented LLM,” and it’s the foundation under everything that follows.

The Augmented LLM — The Foundation Block

Every pattern in the article — chaining, routing, agents, all of them — is built out of one repeating piece: an LLM that has been augmented with three capabilities: retrieval, tools, and memory. Anthropic calls this the “augmented LLM,” and the framing matters: don’t think of the LLM as a chatbot, think of it as a reasoning engine with three plug-in slots.

Everyday Analogy

Before augmentation: A bare LLM is like a brilliant new hire on day one. They’re smart and well-read but they don’t know your codebase, can’t open Jira, and forgot what you discussed yesterday. They produce confident-sounding answers from training data alone.

The pain: A bare LLM hallucinates because it has no way to look things up (no retrieval), no way to act on the world (no tools), and no continuity across sessions (no memory). Treating that bare LLM as your agent is the source of most of the failures the rest of the article exists to prevent.

The mapping: The augmented LLM is the same brilliant hire on day 30. They have access to the wiki (retrieval), can run scripts and update tickets (tools), and remember the past month of decisions (memory). That same brain becomes dramatically more useful once it’s plugged into the systems around it. Every pattern in this module is a way of arranging these augmented LLMs — how many you use, how they connect, who decides what.

Technical Definition

The augmented LLM is an LLM call enriched with three capabilities the model can actively use:

  • Retrieval: Pulling relevant context into the prompt at query time. Vector search, BM25, SQL queries, document loading — whatever brings the right knowledge into the context window. (Course modules M09 and M10 cover this.)
  • Tools: Functions the model can invoke through the Messages API to take actions or fetch data the model cannot produce on its own — a database query, a web search, a file read, an API call. (Modules M05M07.)
  • Memory: Persisting state across turns and sessions so the model has continuity. Conversation history, scratchpads, vector-indexed notes. (Modules M08 and M11.)

The article calls out the Model Context ProtocolAn open protocol for connecting AI models to external tools and data sources in a standardized way. Lets one server expose tools to multiple AI clients (Claude, others) without bespoke integrations. Covered in M07. as one good way to wire third-party tools into the augmented LLM without bespoke per-tool code. The takeaway: tailor each capability to your specific use case — the right retrieval index, the right tool surface, the right memory representation — and ensure the LLM has a documented, easy-to-use interface to all three.

The Augmented LLM — Three Plug-in Slots
🧠 LLM
📚 Retrieval vector search, RAG
🔧 Tools function calls, MCP
💾 Memory history, scratchpad
Why It Matters

Most production agent failures aren’t reasoning failures — they’re augmentation failures. The retrieval pulled stale data. The tool returned an error the model didn’t know how to interpret. The memory truncated the wrong message. Every “Claude hallucinated” bug ticket is usually a “we never gave Claude what it needed” bug ticket. Get the augmented LLM right first, and the patterns above it become much easier to make work.

With the building block in hand, here are the five workflow patterns the article catalogs — in roughly increasing complexity. Each one is a different shape for arranging augmented LLMs into a useful system.
Pattern Topology Carousel — Click each pattern
Input
LLM 1
?
LLM 2
?
LLM 3
Output
Sequential steps with optional programmatic gates between them.
Input
Router LLM
Specialist A
Specialist B
Specialist C
Output
Classify input, then dispatch to a specialized prompt for that category.
Input
Worker A
Worker B
Worker C
Aggregator
Output
Run in parallel for speed (sectioning) or for diverse outputs (voting).
Input
Orchestrator
Worker 1
Worker 2
Worker N
Synthesizer
Like routing, but workers and sub-tasks are decided at runtime by the orchestrator.
Input
Generator
Evaluator
Approved Output
feedback loop — revise & retry
One LLM generates, another critiques, the loop repeats until criteria are met.
Goal
Agent LLM
Tool
Tool
Env
Done
tool result → observe → pick next tool
LLM in a loop, calling tools, deciding when to stop. Maximum flexibility, maximum cost.

Pattern 1 — Prompt Chaining

Everyday Analogy

Before chaining: Imagine asking a copywriter to “write a marketing email, translate it to French, and verify the translation” in one breath. They’ll do all three, but a fluent French speaker would still spot rough patches because the copywriter was juggling three jobs at once.

The pain: A single LLM call asked to do three things produces work that’s mediocre at all three. Worse, you can’t inspect the intermediate steps — if the French translation is wrong, you can’t tell if the English draft was the problem or the translation step itself.

The mapping: Prompt chaining is the assembly line. Step 1: write the email. Stop. Inspect. Step 2: translate. Stop. Inspect. Step 3: verify. Each call is focused, the output of one becomes the input of the next, and you can drop in programmatic gates between steps to validate or short-circuit. The article phrases it as: “trade a little latency for higher accuracy by making each LLM call easier.”

Technical Definition

Prompt chaining decomposes a task into a sequence of LLM calls where each call processes the output of the previous one. Optional programmatic checks (the article calls them gates) sit between calls to validate that the previous step produced something usable before the next call fires.

When it’s the right tool: The task can be cleanly cut into fixed sub-steps, you know the order, and each sub-step is small enough that the model handles it reliably. Outline-then-write, draft-then-edit, extract-then-summarize, fetch-then-format are all canonical examples.

When it’s the wrong tool: The order of steps depends on the input, or some inputs need different sequences. That’s a routing or orchestrator-workers problem, not a chaining problem.

Code: Outline → Draft → Polish

This is the smallest interesting prompt chain. We split “write a short blog post on topic X” into three calls, with a length-check gate between the outline and the draft.

from anthropic import Anthropic, APIError

client = Anthropic()
MODEL = "claude-sonnet-4-6"

def call(system: str, user: str, max_tokens: int = 1024) -> str:
    """One Messages API call. Centralized so error handling stays in one place."""
    try:
        resp = client.messages.create(
            model=MODEL,
            max_tokens=max_tokens,
            system=system,
            messages=[{"role": "user", "content": user}],
        )
        return resp.content[0].text
    except APIError as e:
        # In production, log + retry with backoff. Here we surface the error
        # so the chain halts visibly rather than producing silent garbage.
        raise RuntimeError(f"LLM call failed: {e}") from e

def chain_blog_post(topic: str) -> str:
    # Step 1: outline
    outline = call(
        system="You write tight, structured outlines.",
        user=f"Write a 5-bullet outline for a 300-word blog post about: {topic}",
        max_tokens=400,
    )

    # GATE: did we actually get a structured outline back?
    if outline.count("\n") < 3:
        raise ValueError(f"Outline too short to be useful:\n{outline}")

    # Step 2: draft from the outline
    draft = call(
        system="You expand outlines into clear, engaging prose.",
        user=f"Expand this outline into a 300-word draft. Use the outline order.\n\n{outline}",
        max_tokens=900,
    )

    # Step 3: polish (tighten language, fix awkward phrasing)
    polished = call(
        system="You are an editor. Tighten prose, remove filler, fix awkward sentences.",
        user=f"Polish this draft. Keep the meaning, improve flow.\n\n{draft}",
        max_tokens=900,
    )
    return polished

if __name__ == "__main__":
    print(chain_blog_post("why prompt chaining beats one giant prompt"))
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const MODEL = "claude-sonnet-4-6";

async function call(system, user, maxTokens = 1024) {
  try {
    const resp = await client.messages.create({
      model: MODEL,
      max_tokens: maxTokens,
      system,
      messages: [{ role: "user", content: user }],
    });
    return resp.content[0].text;
  } catch (err) {
    // Halt visibly rather than continuing with bad input downstream.
    throw new Error(`LLM call failed: ${err.message}`);
  }
}

export async function chainBlogPost(topic) {
  // Step 1: outline
  const outline = await call(
    "You write tight, structured outlines.",
    `Write a 5-bullet outline for a 300-word blog post about: ${topic}`,
    400,
  );

  // GATE: structural sanity check before we spend more tokens
  if ((outline.match(/\n/g) || []).length < 3) {
    throw new Error(`Outline too short to be useful:\n${outline}`);
  }

  // Step 2: draft
  const draft = await call(
    "You expand outlines into clear, engaging prose.",
    `Expand this outline into a 300-word draft. Use the outline order.\n\n${outline}`,
    900,
  );

  // Step 3: polish
  const polished = await call(
    "You are an editor. Tighten prose, remove filler, fix awkward sentences.",
    `Polish this draft. Keep the meaning, improve flow.\n\n${draft}`,
    900,
  );
  return polished;
}

const result = await chainBlogPost("why prompt chaining beats one giant prompt");
console.log(result);
What Just Happened?

You spent three API calls instead of one and added ~5 seconds of latency. In exchange, each step is small enough that the model handles it well, the gate kills bad runs early, and when the output is wrong you can pinpoint which step went wrong by inspecting intermediate text. That’s the prompt-chaining trade in one paragraph.

Pattern 2 — Routing

Everyday Analogy

Before routing: A general-practitioner doctor handles every complaint — broken leg, weird rash, chest pain, sore throat. They’re competent at all of it but excellent at none, and they’re always looking up reference material on the way to the next room.

The pain: One LLM prompt that has to handle “billing question, technical bug report, refund request, and account deletion” ends up bloated — the prompt grows to cover every case, and the model’s attention is split. Performance is mediocre across the board.

The mapping: Routing is triage. A short, fast classifier reads the input and dispatches it to the right specialist prompt — one for billing, one for bugs, one for refunds. Each specialist prompt is short and sharply focused. The model handles its narrow case excellently, and you can swap in a stronger model only for the harder categories without paying that cost on every request.

Technical Definition

Routing classifies the input and directs it to a specialized follow-up task. The classification can be done by an LLM call (when categories are fuzzy) or by a deterministic rule (when categories are obvious). Each downstream branch has its own prompt, possibly its own model, possibly its own tool set.

When it’s the right tool: Inputs fall into distinct categories that benefit from different handling, and a generalist prompt would underserve each category. Customer support intents, document types, query difficulties (route easy ones to Haiku, hard ones to Opus), language detection.

Watch for: Misclassification cascades. A bad classifier sends queries to the wrong specialist, who tries to answer anyway. Add a fallback branch for low-confidence classifications, and log misroutes for tuning.

Code: Customer-Support Router

A classifier picks one of three lanes — billing, technical, general — and dispatches to a specialized prompt with its own tone and constraints.

from anthropic import Anthropic, APIError

client = Anthropic()
MODEL_FAST = "claude-haiku-4-5-20251001"   # cheap classifier
MODEL_SMART = "claude-sonnet-4-6"          # main answerer

SPECIALISTS = {
    "billing": (
        "You are a billing specialist. Be precise about amounts, dates, and "
        "policies. Never invent fees. If unsure, say so and escalate."
    ),
    "technical": (
        "You are a technical support engineer. Ask for logs/versions when needed. "
        "Suggest one fix at a time. Never guess at internals you don't know."
    ),
    "general": (
        "You are a friendly customer support agent. Answer in 2-3 sentences. "
        "Defer to specialists when the question becomes billing or technical."
    ),
}

def classify(message: str) -> str:
    """Cheap, fast classifier. Returns one of the keys in SPECIALISTS."""
    resp = client.messages.create(
        model=MODEL_FAST,
        max_tokens=10,
        system=(
            "Classify the user message into exactly one label: "
            "'billing', 'technical', or 'general'. "
            "Reply with only the label, no punctuation."
        ),
        messages=[{"role": "user", "content": message}],
    )
    label = resp.content[0].text.strip().lower()
    return label if label in SPECIALISTS else "general"  # safe fallback

def respond(message: str) -> str:
    lane = classify(message)
    try:
        resp = client.messages.create(
            model=MODEL_SMART,
            max_tokens=500,
            system=SPECIALISTS[lane],
            messages=[{"role": "user", "content": message}],
        )
        return f"[lane={lane}] {resp.content[0].text}"
    except APIError as e:
        raise RuntimeError(f"Specialist call failed for lane={lane}: {e}") from e

if __name__ == "__main__":
    print(respond("My credit card was charged twice for last month's plan."))
    print(respond("Login button does nothing on Firefox 124."))
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const MODEL_FAST = "claude-haiku-4-5-20251001";
const MODEL_SMART = "claude-sonnet-4-6";

const SPECIALISTS = {
  billing:
    "You are a billing specialist. Be precise about amounts, dates, and policies. Never invent fees. If unsure, say so and escalate.",
  technical:
    "You are a technical support engineer. Ask for logs/versions when needed. Suggest one fix at a time. Never guess at internals you don't know.",
  general:
    "You are a friendly customer support agent. Answer in 2-3 sentences. Defer to specialists when the question becomes billing or technical.",
};

async function classify(message) {
  const resp = await client.messages.create({
    model: MODEL_FAST,
    max_tokens: 10,
    system:
      "Classify the user message into exactly one label: 'billing', 'technical', or 'general'. Reply with only the label, no punctuation.",
    messages: [{ role: "user", content: message }],
  });
  const label = resp.content[0].text.trim().toLowerCase();
  return SPECIALISTS[label] ? label : "general";   // safe fallback
}

export async function respond(message) {
  const lane = await classify(message);
  try {
    const resp = await client.messages.create({
      model: MODEL_SMART,
      max_tokens: 500,
      system: SPECIALISTS[lane],
      messages: [{ role: "user", content: message }],
    });
    return `[lane=${lane}] ${resp.content[0].text}`;
  } catch (err) {
    throw new Error(`Specialist call failed for lane=${lane}: ${err.message}`);
  }
}

console.log(await respond("My credit card was charged twice for last month's plan."));
console.log(await respond("Login button does nothing on Firefox 124."));

Notice the cost optimization: the classifier uses Haiku (cheap, fast) and the specialist uses Sonnet. You only pay for the smarter model on the work that needs it.

Pattern 3 — Parallelization

Everyday Analogy

Before parallelization: A single proofreader reads a manuscript end-to-end checking grammar, fact accuracy, and tone all at once. By page 80 they’re tired, mixing up the passes, and missing things.

The pain: Doing several independent jobs serially in one LLM call wastes wall-clock time and dilutes attention. And for safety-critical checks, a single pass gives you a single point of failure — if the model misses a problem, no one catches it.

The mapping: Parallelization splits the work two different ways. Sectioning: hand different chapters to different proofreaders so they finish in parallel — useful when sub-tasks are independent. Voting: hand the same chapter to three proofreaders and combine their findings — useful when one careful pass isn’t reliable enough and you want consensus or coverage.

Technical Definition

Parallelization runs multiple LLM calls simultaneously and aggregates their outputs. The article calls out two flavors:

Sectioning: The task splits into independent sub-tasks that can be done at the same time. Translate a long document by sending each chapter to a different call. Validate three different aspects of one input (toxicity, on-topic, factual) in parallel and combine the verdicts.

Voting: The same task is run multiple times with varied prompts or temperature, and the results are combined — majority vote, union of findings, or LLM-aggregated synthesis. Used when one shot isn’t robust enough: code-vulnerability scans (catch as many issues as possible), output safety classifiers (flag if any pass flags it), or content evaluation where multiple perspectives improve quality.

Watch for: Cost. N calls cost N× one call. Use parallelization when speed or robustness justifies that cost — not as a default.

Code: Voting-Style Safety Triple-Check

A user-generated post is checked for three risks in parallel: toxicity, off-topic, and contains personal info. Any flag fails the post.

import asyncio
from anthropic import AsyncAnthropic, APIError

client = AsyncAnthropic()
MODEL = "claude-haiku-4-5-20251001"

CHECKS = {
    "toxicity":  "Is this post toxic, hateful, or harassing? Reply only YES or NO.",
    "offtopic":  "The forum is about home gardening. Is this post off-topic? Reply only YES or NO.",
    "pii":       "Does this post contain personal info (full names, phone, email, address)? Reply only YES or NO.",
}

async def check_one(name: str, prompt: str, post: str) -> tuple[str, bool]:
    try:
        resp = await client.messages.create(
            model=MODEL,
            max_tokens=4,
            system=prompt,
            messages=[{"role": "user", "content": post}],
        )
        verdict = resp.content[0].text.strip().upper().startswith("YES")
        return name, verdict
    except APIError:
        # Fail closed: treat unreachable check as a flag, not as a pass.
        return name, True

async def screen_post(post: str) -> dict:
    results = await asyncio.gather(
        *(check_one(n, p, post) for n, p in CHECKS.items())
    )
    flags = {name: flagged for name, flagged in results}
    return {"approved": not any(flags.values()), "flags": flags}

if __name__ == "__main__":
    sample = "Loving my new tomato variety this year, way better than last season's!"
    print(asyncio.run(screen_post(sample)))
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const MODEL = "claude-haiku-4-5-20251001";

const CHECKS = {
  toxicity: "Is this post toxic, hateful, or harassing? Reply only YES or NO.",
  offtopic: "The forum is about home gardening. Is this post off-topic? Reply only YES or NO.",
  pii:      "Does this post contain personal info (full names, phone, email, address)? Reply only YES or NO.",
};

async function checkOne(name, prompt, post) {
  try {
    const resp = await client.messages.create({
      model: MODEL,
      max_tokens: 4,
      system: prompt,
      messages: [{ role: "user", content: post }],
    });
    const flagged = resp.content[0].text.trim().toUpperCase().startsWith("YES");
    return [name, flagged];
  } catch {
    // Fail closed: a missing check counts as a flag, not a pass.
    return [name, true];
  }
}

export async function screenPost(post) {
  const entries = await Promise.all(
    Object.entries(CHECKS).map(([n, p]) => checkOne(n, p, post))
  );
  const flags = Object.fromEntries(entries);
  return { approved: !Object.values(flags).some(Boolean), flags };
}

const sample = "Loving my new tomato variety this year, way better than last season's!";
console.log(await screenPost(sample));

Three calls fire concurrently, so the wall-clock time is roughly one call — about 3× faster than running them sequentially. The fail-closed default in the error handler is deliberate: if a safety check can’t run, treat the post as flagged rather than silently approve it.

Pattern 4 — Orchestrator-Workers

Everyday Analogy

Before orchestrator-workers: A senior editor receives a vague brief: “produce a comprehensive market briefing on emerging fintech in Southeast Asia.” They can’t pre-define the chapters — they don’t know yet whether the briefing needs three country deep-dives or seven, or whether regulation deserves its own section.

The pain: Routing won’t work because the categories aren’t known up front. Prompt chaining won’t work because the steps depend on what we find. The decomposition itself is part of the problem.

The mapping: The senior editor reads the brief, decides on the chapters, hands each chapter to a specialist writer, then weaves the results into a single briefing. That’s orchestrator-workers: a central LLM dynamically decomposes the work, dispatches sub-tasks to worker LLMs, and synthesizes their outputs. The shape of the run is decided at runtime, not at code time.

Technical Definition

Orchestrator-workers uses one LLM as a dispatcher: it reads the input, generates a list of sub-tasks dynamically, calls worker LLMs (in parallel where possible) to solve each one, and synthesizes a final answer from the results.

The key difference from parallelization is that the sub-tasks aren’t pre-defined — the orchestrator decides them at runtime based on the input. The key difference from a full agent is that the structure is still bounded: orchestrator decomposes, workers solve, orchestrator synthesizes, done. There’s no open-ended loop.

When it’s the right tool: Complex tasks where you can’t predict the sub-tasks in advance — multi-file code edits, research summaries from many sources, analysis pipelines that depend on what the data looks like.

This pattern shows up in this course as the supervisor-worker architecture in M14: Multi-Agent Systems, and is the foundation of the Anthropic Agent SDK’s subagent feature.

🎓 Cert Tip — Domain 1.2

The exam favors the orchestrator-workers (a.k.a. coordinator + subagents) pattern over flat or peer-to-peer multi-agent designs. Memorize the four properties it gives you: single coordination point, context isolation between workers, structured result aggregation, auditable decision flow.

Pattern 5 — Evaluator-Optimizer

Everyday Analogy

Before evaluator-optimizer: A novelist sends each chapter to a publisher and gets a single “looks good” or “please revise.” Without specific feedback, every revision is a guess at what the publisher actually wants.

The pain: Many tasks aren’t one-shot. Translation, code review, technical writing, summarization — these benefit from a critique pass and a revision. Asking the original LLM to “make it better” just produces a different version with the same blind spots.

The mapping: Evaluator-optimizer uses two LLM roles in a loop. A generator produces a candidate output. An evaluator — a separate prompt with a different focus — critiques it against explicit criteria. The generator revises using the critique. Loop until the evaluator approves or a max-iterations cap kicks in.

Technical Definition

Evaluator-optimizer is a generator + critic loop. Generator produces, evaluator scores against criteria, generator revises with the critique. The loop terminates when the evaluator’s score crosses a threshold or a max-iterations cap is reached.

When it’s the right tool: The article gives a specific test — use this when (1) you can articulate clear evaluation criteria the model can apply, and (2) iterative refinement provides measurably better outputs. Translation quality, literary editing, code that must pass tests, documents that must satisfy a checklist.

When it’s the wrong tool: If “good” is fuzzy and subjective, the evaluator will produce noisy critiques that the generator chases unproductively, burning calls without converging. Use only when criteria are concrete.

The cousin pattern in this course is reflexion / critique loops in M17: Output Guardrails and the eval-driven development covered in M18.

Code: Code Reviewer + Author Loop

Generator writes a function. Evaluator checks it for correctness, edge cases, and naming. Up to three revision rounds.

from anthropic import Anthropic, APIError

client = Anthropic()
MODEL = "claude-sonnet-4-6"
MAX_ROUNDS = 3

def llm(system: str, user: str, max_tokens: int = 800) -> str:
    try:
        r = client.messages.create(
            model=MODEL, max_tokens=max_tokens, system=system,
            messages=[{"role": "user", "content": user}],
        )
        return r.content[0].text
    except APIError as e:
        raise RuntimeError(f"LLM error: {e}") from e

def evaluate(code: str, spec: str) -> tuple[bool, str]:
    """Evaluator returns (approved, critique). 'APPROVED' on the first line means done."""
    review = llm(
        system=(
            "You are a strict code reviewer. Check correctness, edge cases, "
            "naming, and style against the spec. Reply with the literal first "
            "line 'APPROVED' if and only if the code fully meets the spec; "
            "otherwise list specific, actionable issues as a bulleted list."
        ),
        user=f"SPEC:\n{spec}\n\nCODE:\n{code}",
        max_tokens=400,
    )
    return review.strip().upper().startswith("APPROVED"), review

def write_with_review(spec: str) -> str:
    code = llm("You write clean, well-named Python.", spec)
    for round_num in range(1, MAX_ROUNDS + 1):
        approved, critique = evaluate(code, spec)
        if approved:
            return code
        # Revise using the specific critique
        code = llm(
            system="You are revising your own code to address reviewer feedback.",
            user=f"SPEC:\n{spec}\n\nYOUR CODE:\n{code}\n\nREVIEWER FEEDBACK:\n{critique}\n\nRevise the code.",
        )
    # Hit the cap; return best effort with a warning
    return f"# WARNING: not approved after {MAX_ROUNDS} rounds\n{code}"

if __name__ == "__main__":
    spec = "Write a Python function `median(nums)` that returns the median of a non-empty list of numbers. Handle even and odd lengths."
    print(write_with_review(spec))
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const MODEL = "claude-sonnet-4-6";
const MAX_ROUNDS = 3;

async function llm(system, user, maxTokens = 800) {
  try {
    const r = await client.messages.create({
      model: MODEL, max_tokens: maxTokens, system,
      messages: [{ role: "user", content: user }],
    });
    return r.content[0].text;
  } catch (err) {
    throw new Error(`LLM error: ${err.message}`);
  }
}

async function evaluate(code, spec) {
  const review = await llm(
    "You are a strict code reviewer. Check correctness, edge cases, naming, and style against the spec. Reply with the literal first line 'APPROVED' if and only if the code fully meets the spec; otherwise list specific, actionable issues as a bulleted list.",
    `SPEC:\n${spec}\n\nCODE:\n${code}`,
    400,
  );
  return [review.trim().toUpperCase().startsWith("APPROVED"), review];
}

export async function writeWithReview(spec) {
  let code = await llm("You write clean, well-named JavaScript.", spec);
  for (let round = 1; round <= MAX_ROUNDS; round++) {
    const [approved, critique] = await evaluate(code, spec);
    if (approved) return code;
    code = await llm(
      "You are revising your own code to address reviewer feedback.",
      `SPEC:\n${spec}\n\nYOUR CODE:\n${code}\n\nREVIEWER FEEDBACK:\n${critique}\n\nRevise the code.`,
    );
  }
  return `// WARNING: not approved after ${MAX_ROUNDS} rounds\n${code}`;
}

const spec = "Write a JavaScript function median(nums) that returns the median of a non-empty array of numbers. Handle even and odd lengths.";
console.log(await writeWithReview(spec));
Common Misconception

“The evaluator should be the same prompt as the generator.” — No. They need different framings. The generator’s prompt says “produce X.” The evaluator’s prompt says “find what’s wrong with X according to these specific criteria.” Reusing the same prompt collapses the loop — the “evaluator” just produces another generation, not a critique.

Those five workflows cover the vast majority of production AI systems. The next pattern is qualitatively different — we hand the whole loop to the model.

Pattern 6 — Agents

Everyday Analogy

Before full agents: Every workflow above has a code-defined shape. The path through the system is fixed by the code you wrote. Steps may differ in content, but the structure of the run — how many calls, in what order, with what tools — is decided at write-time.

The pain: Some tasks resist that. “Reproduce this bug, find the root cause, write a fix, run the tests, open a PR.” You can’t pre-decide how many test-runs, how many file edits, or what investigation order will be needed — that depends entirely on what the bug turns out to be.

The mapping: A full agent is the LLM running its own loop. You hand it a goal and a toolbox, and it picks tools, observes results, and decides on its own when the work is done. Path length is unbounded. Cost and latency are unbounded. In exchange, the agent can handle problems whose shape couldn’t have been pre-determined.

Technical Definition

An agent is an LLM in a tool-calling loop: receive task → pick tool → observe result → decide next step (more tools, or stop) → loop. It’s the architecture M12 (ReAct) and M13 (Planning & Decomposition) teach in detail.

When it’s the right tool: Open-ended problems where the number of steps is unknowable in advance, AND you can run in a trusted/sandboxed environment, AND you have evals to catch regressions. The article’s canonical examples: coding agents that solve real GitHub issues by iterating against tests, and computer-use agents that navigate UIs.

The cost is real. The article warns about three things specifically: higher costs (loops mean N× tokens), compounding errors (one wrong step contaminates everything that follows), and need for guardrails (sandboxed execution, max-step limits, human-in-the-loop checkpoints for high-stakes actions).

Code: Minimal Agent Loop

This is the smallest correct agent loop — the article’s recommendation distilled to its essentials. The model picks tools, the harness executes them, the loop continues until the model produces a non-tool response. Note the step cap: a real-world agent must always have one.

from anthropic import Anthropic, APIError

client = Anthropic()
MODEL = "claude-sonnet-4-6"
MAX_STEPS = 10  # hard cap so a confused agent can't loop forever

# 1. Define tools the model can call.
TOOLS = [
    {
        "name": "get_weather",
        "description": "Get current weather for a city. Returns short text.",
        "input_schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
    {
        "name": "calculator",
        "description": "Evaluate a basic arithmetic expression like '12 * (3 + 4)'.",
        "input_schema": {
            "type": "object",
            "properties": {"expr": {"type": "string"}},
            "required": ["expr"],
        },
    },
]

def execute_tool(name: str, args: dict) -> str:
    """The harness executes the tool and returns a string result."""
    if name == "get_weather":
        # Stub. In real life: call your weather API.
        return f"{args['city']}: 18C, partly cloudy."
    if name == "calculator":
        # Tightly-scoped eval: numbers and operators only.
        allowed = set("0123456789+-*/(). ")
        if not set(args["expr"]) <= allowed:
            return "ERROR: disallowed characters"
        try:
            return str(eval(args["expr"], {"__builtins__": {}}, {}))  # nosec
        except Exception as e:
            return f"ERROR: {e}"
    return f"ERROR: unknown tool {name}"

def run_agent(goal: str) -> str:
    messages = [{"role": "user", "content": goal}]
    for step in range(MAX_STEPS):
        try:
            resp = client.messages.create(
                model=MODEL, max_tokens=1024, tools=TOOLS, messages=messages,
            )
        except APIError as e:
            return f"ABORTED: {e}"

        # Stop condition: model is done calling tools.
        if resp.stop_reason != "tool_use":
            return next(
                (b.text for b in resp.content if hasattr(b, "text")),
                "(no text response)",
            )

        # Otherwise: append the model's tool calls and run them.
        messages.append({"role": "assistant", "content": resp.content})
        tool_results = []
        for block in resp.content:
            if block.type == "tool_use":
                result = execute_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                })
        messages.append({"role": "user", "content": tool_results})

    return f"ABORTED: hit MAX_STEPS={MAX_STEPS} without a final answer"

if __name__ == "__main__":
    print(run_agent("What's the weather in Tokyo, and what's 17% of the temperature?"))
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const MODEL = "claude-sonnet-4-6";
const MAX_STEPS = 10;

const TOOLS = [
  {
    name: "get_weather",
    description: "Get current weather for a city. Returns short text.",
    input_schema: {
      type: "object",
      properties: { city: { type: "string" } },
      required: ["city"],
    },
  },
  {
    name: "calculator",
    description: "Evaluate a basic arithmetic expression like '12 * (3 + 4)'.",
    input_schema: {
      type: "object",
      properties: { expr: { type: "string" } },
      required: ["expr"],
    },
  },
];

function executeTool(name, args) {
  if (name === "get_weather") {
    return `${args.city}: 18C, partly cloudy.`;
  }
  if (name === "calculator") {
    if (!/^[0-9+\-*/(). ]+$/.test(args.expr)) return "ERROR: disallowed characters";
    try { return String(Function(`"use strict"; return (${args.expr});`)()); }
    catch (e) { return `ERROR: ${e.message}`; }
  }
  return `ERROR: unknown tool ${name}`;
}

export async function runAgent(goal) {
  const messages = [{ role: "user", content: goal }];
  for (let step = 0; step < MAX_STEPS; step++) {
    let resp;
    try {
      resp = await client.messages.create({
        model: MODEL, max_tokens: 1024, tools: TOOLS, messages,
      });
    } catch (e) {
      return `ABORTED: ${e.message}`;
    }

    if (resp.stop_reason !== "tool_use") {
      const text = resp.content.find((b) => b.type === "text");
      return text ? text.text : "(no text response)";
    }

    messages.push({ role: "assistant", content: resp.content });
    const toolResults = [];
    for (const block of resp.content) {
      if (block.type === "tool_use") {
        const result = executeTool(block.name, block.input);
        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: result,
        });
      }
    }
    messages.push({ role: "user", content: toolResults });
  }
  return `ABORTED: hit MAX_STEPS=${MAX_STEPS} without a final answer`;
}

console.log(await runAgent("What's the weather in Tokyo, and what's 17% of the temperature?"));
🔒 Sandboxing & Guardrails

The article is explicit: agents need extensive testing in sandboxed environments and appropriate guardrails before being trusted with autonomy. In the example above, the calculator eval is locked to numeric characters — without that, you have a remote-code-execution hole. Production agents add: max-steps caps, max-cost caps, an allowlist of tools per task, human-in-the-loop confirmation for irreversible actions (database writes, emails, deploys), and pervasive logging. (See M16 & M17.)

Combining Patterns — Real Systems Mix Them

The article makes one final point that’s easy to miss because it’s phrased gently: “These building blocks aren’t prescriptive. They’re common patterns that developers can shape and combine to fit different use cases.” In other words: don’t pick exactly one. Real production systems compose them.

Three useful compositions:

  • Routing → (specialized chain or agent): A top-level router classifies the request, then dispatches to either a deterministic chain (cheap, predictable cases) or a full agent (rare, open-ended cases). Most customer-support stacks look like this.
  • Agent with workflow tools: Inside an agent, individual “tools” can themselves be deterministic workflows. The agent calls a tool that internally does prompt-chaining to summarize a document — the agent doesn’t see that complexity.
  • Orchestrator-workers with evaluator-optimizer per worker: A research orchestrator spawns subagents, and each subagent runs a generator-critic loop on its draft before returning. M14 + M17 patterns combined.
Why It Matters

The mistake teams make is treating the patterns as competing options — “are we doing routing or are we doing agents?” — when in reality a real system is usually a tree: a workflow at the top, agents at the leaves where the open-ended work lives, and inside each agent, more workflows. The patterns are a vocabulary for talking about those tree levels, not a single choice for the whole system.

Tool Engineering — The Agent-Computer Interface (ACI)

Late in the article, Anthropic devotes a section to a topic that often gets shortchanged: how you design the tools the agent uses. They coin the term Agent-Computer Interface (ACI) — the agent’s analog of HCI. The takeaway: invest as much effort in the ACI as you’d invest in a UI for a human.

Technical Definition

The Agent-Computer Interface (ACI)The set of tool definitions, names, parameter schemas, descriptions, and expected return formats that an agent works with. Good ACI design dramatically improves agent reliability and is often the highest-leverage intervention. is the surface the model sees: tool names, descriptions, parameter schemas, return formats, error messages. The article reports that on internal coding-agent work, tool-design effort “often required more effort than overall prompt development.” Translation: better tools beat better prompts, and most teams under-invest here.

The ACI checklist (from the article and surrounding Anthropic guidance)

  • Give the model enough tokens to think before acting. Don’t cap output so tightly that the agent skips reasoning. Allow space for a brief plan before each tool call.
  • Keep formats close to what the model sees naturally on the open web. JSON, markdown, plain English — not bespoke binary formats or custom DSLs. Models are dramatically better at common formats they’ve seen during training.
  • Eliminate formatting overhead. Don’t require the model to count whitespace, escape exotic characters, or maintain rigid line numbers. Every char of overhead is one more thing to get wrong.
  • Test extensively with example inputs. Run the tools against realistic inputs and read the model’s tool calls. You’ll spot ambiguities the schema didn’t catch.
  • Apply poka-yokeJapanese for “mistake-proofing.” A design principle from manufacturing: shape the tool so common mistakes are physically/structurally impossible to make. design. Shape the tool so common errors are impossible. The article’s classic example: their coding agent kept generating relative file paths that broke when the working directory shifted, so they redesigned the tool to require absolute paths only — the bug class disappeared.
  • Document the tool the way you’d document it for a junior engineer. What does it do, what are the inputs, what does it return, what errors can it produce, what should the caller do about each error? The model reads the description and acts on it.
The ACI Insight

If your agent is misbehaving, the first instinct is to edit the system prompt. The article’s data points elsewhere: more often, the fix is in the tool. Rename a confusingly-named parameter. Make a return format unambiguous. Add a missing field to an error response. The same model on the same prompt with better tools performs dramatically better. ACI is the highest-leverage intervention most teams ignore.

Real Applications — Where These Patterns Show Up

The article’s appendix grounds the abstract patterns in two well-developed real applications. Both are worth understanding because they show what “the right pattern” looks like once a real production constraint is involved.

Customer Support

Customer support combines a chatbot interface with tool integration to access customer data, history, and account systems — and to take actions like issuing refunds or updating tickets. The article identifies it as a strong fit because:

  • The interaction is naturally conversational (LLMs are good at this).
  • Success is measurable: did the issue get resolved? Was the customer satisfied?
  • Pricing per resolution maps cleanly to value — one of the rare AI use cases where outcome-based pricing actually works.

The architecture is typically routing-at-the-top (intent classification) plus a constrained agent in each lane that has access to the tools relevant to that intent — refund tools for the billing lane, account tools for the account lane, and so on. Pure free-form agents are usually overkill.

Coding Agents

Coding agents (the SWE-bench category) are the article’s flagship example of when full agent autonomy is justified. The reasoning:

  • Solutions are verifiable through automated tests — the agent doesn’t need a human in the loop on every step because the test suite is the ground truth.
  • The agent can iterate using test results as feedback: try a fix, run tests, see what failed, try a refined fix.
  • The problem space is genuinely open-ended — the agent might need to read 3 files or 30, edit 1 line or rewrite a module — impossible to pre-script.

Even here, the article tempers the autonomy: “human review remains crucial for ensuring solutions align with broader system requirements.” The PR review step is the human guardrail. The agent works inside a sandbox; humans gate what gets merged.

The Common Thread

Both examples have one thing in common: a clear, automatic feedback signal. Customer support has resolution outcomes. Coding has test results. When that signal exists, agents become viable. When it doesn’t — when “good” can only be judged by a human reading the output — you’re probably looking at a workflow problem, not an agent problem.

Knowledge Check

1. Anthropic’s definition splits “agentic systems” into two specific categories. What is the actual difference?

AWorkflows use one LLM, agents use multiple
BWorkflows orchestrate LLMs through predefined code paths; agents let the LLM dynamically direct its own process
CWorkflows can’t use tools, agents can
DAgents are open source, workflows are proprietary
Correct. The clean line is who decides the path through the system. Workflows: predefined code paths. Agents: LLM dynamically picks tool order at runtime.
The article’s definition: workflows orchestrate LLMs through predefined code paths, while agents have the LLM dynamically direct its own process and tool usage. Both can use tools and multiple LLMs — the line is about who decides what step happens next.

2. Which task is best suited to routing rather than a single generalist prompt or a full agent?

AReproducing and fixing an unknown bug end-to-end
BTranslating a single document into French
CA customer-support inbox where each message is billing, technical, or general
DDoing one quick math calculation
Correct. Routing fits when inputs fall into distinct categories that benefit from different specialized prompts. The other options are: a full agent (A), a single LLM call (B and D).
Routing is for distinct categories that need different handling. A customer-support inbox with billing/technical/general lanes is the canonical example. Bug-fixing is open-ended (agent). Translation and math are single-call jobs.

3. What is the key difference between orchestrator-workers and plain parallelization?

AOrchestrator-workers is faster
BParallelization uses one model, orchestrator-workers uses many
CParallelization runs voting; orchestrator-workers runs sectioning
DIn parallelization the sub-tasks are pre-defined; in orchestrator-workers the orchestrator decides them at runtime
Correct. Both run worker LLMs, but parallelization splits a known task into known sub-tasks at code time. Orchestrator-workers decomposes the task at runtime, based on the input.
In parallelization, the sub-tasks are known up front (you wrote the split into three safety checks). In orchestrator-workers, the orchestrator LLM decides the decomposition at runtime based on the input. That’s the defining difference.

4. The article warns that agents (full autonomy) introduce three specific risks. Which list is correct?

AHigher cost, compounding errors, need for sandboxing/guardrails
BSlower training, weaker math, more hallucinations
CHigher API limits, lock-in, vendor risk
DPrivacy leaks, data exfiltration, prompt injection
Correct. Loops mean unbounded cost, errors in early steps contaminate later ones, and untrusted action requires sandboxes plus human-in-the-loop checkpoints for high-stakes actions.
The article’s explicit warnings: higher costs (because the loop multiplies tokens), compounding errors (one bad step taints the next), and the need for sandboxed environments and guardrails. The other options are real concerns elsewhere but not the agents-specific list.

5. Which statement about the Agent-Computer Interface (ACI) reflects the article’s recommendation?

ATool descriptions don’t matter; the model figures it out from the name
BInvest in the ACI as much as you would in a UI — tool design often takes more effort than prompt design and yields larger reliability gains
CUse a custom binary format to reduce token usage
DRestrict thinking tokens so the agent acts faster
Correct. The article’s point is that ACI is the often-ignored highest-leverage intervention. Better tools beat better prompts, and the same model performs dramatically better with a well-designed tool surface.
The article emphasizes investing in the ACI as you would a UI — tool design often takes more effort than prompt design. Keep formats natural (not custom binary), give the model space to think before acting, and use poka-yoke design to make mistakes structurally impossible.

Your Score

0/0

Module Summary

Key Concepts

  • Workflows vs. agents: The line is who decides the path. Workflows: predefined code paths you wrote. Agents: LLM dynamically directs itself.
  • Start simple: A bare LLM call beats a chain. A chain beats an agent. Reach for the next pattern only when the simpler one provably fails.
  • Augmented LLM: The reusable building block under everything. Retrieval + tools + memory, with each capability tailored to the use case.
  • Five workflow patterns: Prompt chaining (sequential), routing (classify and dispatch), parallelization (concurrent for speed or voting), orchestrator-workers (runtime decomposition), evaluator-optimizer (generator + critic loop).
  • Full agents: Worth the cost only for open-ended problems where steps are unknowable in advance and you can sandbox + add guardrails. Customer support and coding agents are the canonical fits.
  • ACI: Tool design is the highest-leverage intervention most teams ignore. Better tools beat better prompts.
  • Combining patterns: Real systems are trees — a workflow at the top, agents at the leaves, more workflows inside the agents.

How This Maps to the Rest of the Course

Article ConceptCourse Module(s)
Augmented LLM — retrievalM09, M10
Augmented LLM — tools / MCPM05, M06, M07
Augmented LLM — memoryM08, M11
Prompt chainingM03, M03B
Routing & orchestrator-workersM14, M15B
Evaluator-optimizerM17, M18
AgentsM12, M13
Sandboxing & guardrailsM15, M16, M17
ACI / tool engineeringM05, M07, M26

Where to Go Next

Pick the pattern that matches your current project and dive into the corresponding course module. If you’re unsure where you are: most teams overestimate how much agent autonomy they need. Try the simpler pattern first — you can always escalate when your eval set proves it’s necessary.