Agents & Retrieval · Agent Patterns

Building Effective Agents

A shared vocabulary and decision framework for structuring LLM-powered systems, drawn straight from Anthropic's engineering blog post of the same name. Every pattern named here — chaining, routing, parallelization, orchestration, autonomous agents — gets reused and referenced throughout the rest of this course.

☺ Explain it like I'm 10

Think about running a group project. Sometimes you hand each teammate a numbered list of exact steps — do this, then that, then check with me before the next part — and you, the boss, decide what happens next. That's a workflow. Other times you tell one very capable teammate "just get it done" and let them figure out the steps themselves, checking in only when they're finished. That's an agent. This page is Olly's map of when to use which, and how many helpers to add before things get too complicated.

🐙Your host for this topic: Olly the Octopus (runs many helpers, keeps them in sync).

Workflows vs. agents: two ways to orchestrate an LLM

☺ Like you're 10: A workflow is a recipe card you wrote yourself. An agent is a chef you trust to cook dinner and decide the recipe as they go.

Anthropic uses "agentic systems" as an umbrella term, then splits it into two categories with a precise distinction. Workflows are "systems where LLMs and tools are orchestrated through predefined code paths." Agents are "systems where LLMs dynamically direct their own processes and tool usage, maintaining control over how they accomplish tasks." In a workflow, your code decides what happens next — call this prompt, then that one, then branch on a condition. In an agent, the model itself decides what happens next, turn by turn, based on what it observes.

Neither is strictly better. Workflows are predictable, testable, and cheap to debug, because the control flow lives in code you wrote and can step through. Agents are flexible and can handle tasks whose shape you can't predict in advance, at the cost of predictability, latency, and spend. This page walks through five workflow patterns first, then autonomous agents — roughly the order in which you should reach for them too.

The augmented LLM: the building block underneath every pattern

Every pattern below is built from the same unit: an LLM enhanced with retrieval, tools, and memory — what Anthropic calls "the augmented LLM." A single Claude Messages API call that can search a knowledge base, call a function, and remember prior turns is already more than a raw completion engine. It's the atomic building block that chaining, routing, parallelization, and orchestration all compose together.

Retrievalsearch a knowledge base Toolscall a function Memoryremember prior turns Augmented LLMone Messages API call
⌁ Note

Anthropic's framing of the whole topic: "Success in the LLM space isn't about building the most sophisticated system. It's about building the right system for your needs." Start with a single well-crafted prompt to the augmented LLM. Add a workflow pattern only when that single call demonstrably falls short. Reach for a fully autonomous agent only when a workflow falls short too.

Five patterns for structuring a workflow

☺ Like you're 10: Each pattern below is the same LEGO brick — the augmented LLM — arranged end to end, side by side, or in a loop.

These patterns all keep the control flow in your code — the LLM does the reasoning inside each step, but your program decides which step runs next. They're deliberately simple: Anthropic notes that "many patterns can be implemented in a few lines of code" using the Claude API directly, without a framework.

Prompt chaining

Prompt chaining decomposes a task into a sequence of steps, where each LLM call processes the output of the one before it. Between steps you can insert programmatic "gates" — plain code checks that validate progress before spending another API call, or that stop the chain entirely if something looks wrong. This trades latency (multiple sequential calls instead of one) for higher accuracy, because each individual call has a narrower, easier job. It's the right pattern when a task decomposes cleanly into fixed subtasks known in advance — generate an outline, then expand it into a draft; generate marketing copy, then translate it into another language.

The most common chaining pattern in practice is self-correction: generate a draft, have Claude review it against stated criteria, then have Claude refine it based on that review — three separate API calls, each one inspectable, loggable, and independently evaluable.

import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from env

def generate_text(prompt: str, model: str = "claude-sonnet-5") -> str:
    response = client.messages.create(
        model=model,
        max_tokens=800,
        messages=[{"role": "user", "content": prompt}],
    )
    return "".join(b.text for b in response.content if b.type == "text")

# Step 1: draft
draft = generate_text(f"Write marketing copy for: {product_brief}")

# Gate: a cheap, deterministic check before spending another call
if len(draft.split()) < 20:
    raise ValueError("Draft too short - check the input brief")

# Step 2: review against explicit criteria
review = generate_text(
    f"Review this copy against our style guide. List concrete issues only.\n\n{draft}"
)

# Step 3: refine using the review as feedback
final_copy = generate_text(
    f"Revise this copy to address every issue below.\n\nCopy:\n{draft}\n\nIssues:\n{review}"
)

Routing

Routing classifies an input and directs it to a specialized followup task, prompt, or model. It lets you separate concerns cleanly: a cheap, fast model like Claude Haiku 4.5 can handle classification and simple lookups, while a more capable model like Claude Sonnet 5 or Claude Opus 5 only gets invoked for the requests that actually need it. A classic example is customer-service triage — billing questions, technical issues, and general questions each get a different downstream prompt (or a different model entirely). The main risk is misclassification: if the router gets the category wrong, everything downstream inherits that mistake.

Python
import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from env

def classify_intent(ticket: str) -> str:
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=20,
        system="Classify the support ticket as exactly one word: billing, technical, or general. Respond with only that word.",
        messages=[{"role": "user", "content": ticket}],
    )
    for block in response.content:
        if block.type == "text":
            return block.text.strip().lower()

def handle_ticket(ticket: str) -> str:
    category = classify_intent(ticket)
    # cheap, fast model for the common case; capable model for technical issues
    model = "claude-opus-5" if category == "technical" else "claude-sonnet-5"
    response = client.messages.create(
        model=model,
        max_tokens=500,
        messages=[{"role": "user", "content": ticket}],
    )
    for block in response.content:
        if block.type == "text":
            return block.text
JavaScript
import Anthropic from "@anthropic-ai/sdk";

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

async function classifyIntent(ticket) {
  const response = await client.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 20,
    system: "Classify the support ticket as exactly one word: billing, technical, or general. Respond with only that word.",
    messages: [{ role: "user", content: ticket }]
  });
  const block = response.content.find((b) => b.type === "text");
  return block ? block.text.trim().toLowerCase() : "general";
}

async function handleTicket(ticket) {
  const category = await classifyIntent(ticket);
  const model = category === "technical" ? "claude-opus-5" : "claude-sonnet-5";
  const response = await client.messages.create({
    model,
    max_tokens: 500,
    messages: [{ role: "user", content: ticket }]
  });
  const block = response.content.find((b) => b.type === "text");
  return block ? block.text : "";
}

Parallelization: sectioning and voting

Parallelization runs multiple LLM calls at once instead of sequentially, and comes in two flavors. Sectioning breaks a task into independent subtasks that run in parallel and get combined — for example, one call screens a submission for guardrail violations while a separate call generates the actual response, so a slow safety check doesn't have to block generation (or vice versa). Voting runs the same task multiple times to get diverse outputs and takes a consensus — for example, running several independent vulnerability-review passes over the same code and flagging anything that any pass catches, on the theory that different sampling runs surface different issues.

Use sectioning when subtasks are genuinely independent and parallelizing them just saves wall-clock time. Use voting when a single pass has meaningful variance and running several attempts and comparing them raises your confidence in the result.

Orchestrator-workers

Here a central orchestrator LLM dynamically breaks a task down and delegates pieces to worker LLMs, then synthesizes their results. The key difference from parallelization is that the subtasks aren't fixed in advance by your code — the orchestrator determines them at runtime based on the specific input. This suits complex, unpredictable tasks like a multi-file coding change (the orchestrator doesn't know which files need touching until it reads the codebase) or multi-source research (the orchestrator decides which sources are relevant as it goes).

🎬 At the Claude Crew
🦊

Foxy: Wait — orchestrator-workers just sounds like parallelization with extra steps. Why not just run the workers at the same time and call it a day?

🦉

Professor Owl: Because with sectioning, you already know the pieces before you start — screen the submission, generate the reply, always those two. Orchestrator-workers is for when you don't know the pieces yet.

🦊

Foxy: So something has to figure out the pieces first, on the fly.

🐙

Olly: That's basically me. I don't decide in advance which of my arms opens which jar — I look at the counter, then send each arm to the job it's actually needed for. The orchestrator LLM reads the task first, then delegates. Fixed subtasks are sectioning; subtasks decided at runtime are orchestrator-workers.

Evaluator-optimizer

One LLM generates a response while a second LLM evaluates it and gives feedback, in a loop that mimics human iterative refinement. This only pays for itself when two conditions both hold: LLM responses demonstrably improve when given feedback, and the evaluator can produce feedback that's actually meaningful rather than generic. A canonical example is literary translation, refined across rounds against an evaluator's critiques of nuance and register that a single generation pass would miss.

⌁ Note

Before reaching for evaluator-optimizer, check both preconditions explicitly. If a second pass of feedback doesn't reliably improve the output, or your evaluator can only say "looks fine" without pointing at anything specific, the extra loop just adds cost and latency for no benefit.

PatternControl flowReach for it when
Prompt chainingFixed sequence of LLM calls with gates between themThe task decomposes cleanly into known, ordered subtasks
RoutingClassify, then dispatch to a specialized pathInputs fall into distinct categories needing different handling or models
Parallelization — sectioningIndependent subtasks run concurrently, then combineSubtasks don't depend on each other and speed matters
Parallelization — votingSame task run multiple times, results comparedA single pass has enough variance that consensus adds confidence
Orchestrator-workersOrchestrator LLM decides subtasks at runtime, delegates, synthesizesSubtask decomposition can't be predicted upfront
Evaluator-optimizerGenerator and evaluator LLMs loop until criteria are metFeedback demonstrably improves output and the evaluator gives real critique

Autonomous agents: when the model drives itself

☺ Like you're 10: It's the difference between a friend who checks the map after every turn and one who just keeps driving on a hunch.

Every pattern above keeps the loop in your code. Autonomous agents flip that: the LLM itself decides what to do next, turn after turn, until the task is done. Anthropic's framing is that agents "emerge when tasks are open-ended and the number of steps can't be predicted or hardcoded." In practice, an agent implementation is often just an LLM calling tools in a loop based on what the environment tells it back — the same multi-turn tool-use loop covered in the tool use module, just without your code deciding when to stop.

The critical requirement Anthropic calls out is that "it's crucial for the agents to gain 'ground truth' from the environment at each step" — actual tool results, actual code execution output, not the model's own unverified assumptions about what happened. Without that grounding, an agent has no way to notice it's off track.

⚠ Careful

"The autonomous nature of agents means higher costs, and the potential for compounding errors" — an early wrong turn can steer every subsequent step further off course, and each additional turn is another API call. Anthropic's mitigation is extensive testing in sandboxed environments with appropriate guardrails, and deploying fully autonomous agents only in trusted environments where the blast radius of a mistake is bounded.

Matching complexity to the task

The pattern you reach for should track the actual complexity of the problem, not the complexity you find interesting to build. A single, well-crafted prompt to the augmented LLM solves more tasks than it gets credit for. Workflow patterns add predictable, debuggable structure when a task decomposes into known steps. Autonomous agents are for genuinely open-ended problems where the steps can't be enumerated in advance — and they cost more, in both latency and dollars, for that flexibility.

⚠ Careful

On frameworks: Anthropic warns that agent frameworks "often create extra layers of abstraction that can obscure the underlying prompts and responses, making them harder to debug," and recommends starting with direct API calls, since most of these patterns fit in a few lines of code. If you do adopt a framework, make sure you understand the underlying prompts and responses it generates on your behalf — you'll need that visibility the first time something goes wrong in production.

Three principles run through all of Anthropic's guidance here: simplicity of design, transparency (making the model's planning steps explicit rather than hidden), and a well-documented agent-computer interface — the same tool-naming and tool-description discipline covered in the tool use module, since an agent is only as reliable as the tools it's given to observe and act on the world.

◆ Pattern

A support-ticket triage feature uses routing: a fast Haiku call classifies the ticket, then dispatches to a scripted response, a Sonnet-generated reply, or a human queue. Three deterministic branches, each independently testable, each costing exactly what it needs to.

⚠ Anti-pattern

The same triage feature is instead built as a fully autonomous agent with a dozen tools (search docs, draft reply, check account, escalate, send email...) and no fixed structure, "in case it needs to do something more complex someday." It's slower, costs more per ticket, and when it misroutes a billing question, there's no single step you can point to and fix — the whole run has to be re-diagnosed.

✎ Try it yourself

Take the routing example above and extend it into a two-step prompt chain: after handle_ticket generates a reply, add a gate that checks the reply against a simple rubric (does it acknowledge the customer's issue? is it under 150 words?) using a second, cheap Claude Haiku 4.5 call, and only return the reply if it passes — otherwise regenerate once with Claude Sonnet 5. Log which model handled the ticket and whether the gate passed on the first try, so you can see the cost/reliability tradeoff directly.

🐙 Olly's checkpoint

You should now be able to name the six agentic-system patterns from Anthropic's framework, explain the difference between a workflow (your code decides what runs next) and an agent (the model decides), and argue for the simplest pattern that fits a task instead of the most impressive one. This page is the vocabulary the rest of the course keeps borrowing — when tool use and the Agent SDK talk about loops and delegation, this is the map they're pointing back to.

Check your answers
  1. What's the precise difference between a workflow and an agent? In a workflow, your code decides what happens next through predefined paths; in an agent, the LLM itself dynamically directs its own process and tool use, deciding the next step based on what it observes.
  2. What single building block sits underneath every pattern on this page? The augmented LLM — a model call enhanced with retrieval, tools, and memory — from a single well-crafted prompt up through the most complex orchestration.
  3. Why is sectioning different from orchestrator-workers, even though both run multiple LLM calls? Sectioning's subtasks are fixed in advance by your code (for example, always run a guardrail screen and a response generator); orchestrator-workers has a central LLM determine the subtasks at runtime based on the specific input, which suits tasks whose shape you can't predict ahead of time.