learn-claude-with-phoebe / Deep dive 6.7
Learn Claude with Phoebe · Deep-dive track 6.7

Agents & workflows

You know the API, tools, RAG and MCP. This page is about composition: how single LLM calls become systems. The core lesson is restraint - most production "agents" are actually workflows, and knowing which one you're building saves you weeks of debugging.

🔴 Deep dive DS & AI 45 min self-paced or live
0-5 · Setup 5-35 · Core 35-45 · Try it
Part 0

Why this page exists

Anthropic's "Building effective agents" guidance boils down to one rule: use the simplest pattern that works, and only add autonomy when the task genuinely needs it. This page walks the pattern ladder in order of increasing complexity - chaining, routing, parallelization, orchestrator-workers, evaluator-optimizer - and only THEN gets to agents proper, plus the two Anthropic apps that put it all together: Claude Code and computer use.

Core - the patterns everyone needs Advanced - read when you're building one Covers the Agents & Workflows + Anthropic Apps modules
★ What you walk out with A decision rule for workflow vs agent, working python sketches for the five workflow patterns, the guardrail checklist for agents, and hands-on ideas for Claude Code as your daily agent case study.
Part 1 · the one distinction that matters

Workflows vs agents 10 min

Everything in this module hangs off one question: who owns the control flow - your code, or the model?

CoreTHE distinction: who owns the loop4 min
  • Workflow: a predefined code path with LLM steps inside it. YOU wrote the sequence, the branches, the retries. The model fills in the smart parts; your code decides what happens next. Deterministic shape, predictable cost, testable step by step.
  • Agent: the model owns the loop. It sees tool results, decides the next action itself, and keeps going until it judges the task done. Flexible, powerful, and harder to bound - cost, latency and failure modes all become open-ended.
WorkflowAgent
Control flowYour code (if/else, loops you wrote)The model, reacting to tool results
Cost & latencyKnown upfront - N calls, roughly fixedOpen-ended - depends on the model's choices
DebuggingInspect each step like normal codeRead transcripts, reason about behavior
Best forTasks you can decompose in advanceTasks where the path is unknowable upfront
Production realityMost systems that work are theseEarned, not default - start simpler
The rule Use the simplest thing that works. A single well-prompted call beats a chain; a chain beats a router; a router beats an agent - IF it solves the task. Complexity is a cost you pay, not a feature you ship. Most production systems that people call "agents" are workflows, and that's a compliment.
CoreChaining: fixed sequence, each step eats the last output6 min

The simplest composition: break a task into a fixed sequence of LLM calls, each consuming the previous output. Classic shape: draft, critique, revise. Between steps you can add a gate - a cheap code check that decides whether the next (expensive) step is even needed.

★ Chain: draft, critique, revise (with a gate)from anthropic import Anthropic client = Anthropic() MODEL = "claude-sonnet-4-5" # check docs for the current model def ask(prompt: str) -> str: msg = client.messages.create( model=MODEL, max_tokens=1024, messages=[{"role": "user", "content": prompt}], ) return msg.content[0].text report = open("incident.md").read() # Step 1: draft draft = ask(f"Summarize this incident report in 3 paragraphs " f"for an exec audience:\n\n{report}") # Step 2: critique against explicit rules critique = ask( "Critique this summary. Check: no jargon, timeline correct, " "one clear next step. Reply PASS if all hold, else list fixes.\n\n" f"Summary:\n{draft}\n\nSource:\n{report}" ) # Gate: only pay for step 3 when the critique demands it if critique.strip().upper().startswith("PASS"): final = draft else: final = ask(f"Revise the summary to fix these issues:\n{critique}" f"\n\nSummary:\n{draft}\n\nSource:\n{report}") print(final)

When do gates between steps pay? When steps are expensive and failures are detectable: a length check, a regex for required sections, a schema validation, or (as here) a cheap critique call. The gate turns "hope the chain worked" into "prove each step worked" - the same instinct as the evals page (6.2).

CoreRouting: classify first, then dispatch to a specialist5 min

When inputs fall into distinct categories, don't build one prompt that handles everything - classify the input first, then send it to a specialized prompt (and a right-sized model). Easy tickets go to a cheap fast model with a short prompt; hard ones go to the capable model with the full playbook.

Routing a ticket: fast tier or capable tier Is this an easy, common case? yes no Fast model: Haiku short specialist prompt Capable model full playbook prompt Classify first, then dispatch: cheap ticket, cheap model; hard ticket, the full playbook.
🔍 Click to zoom - classify first so you stop paying frontier prices for password resets
★ Router: cheap classifier, specialist handlersROUTES = { "refund": {"model": "claude-haiku-4-5", "system": "You handle refund requests. Policy: ..."}, "bug": {"model": "claude-sonnet-4-5", "system": "You triage bug reports. Ask for repro steps..."}, "account": {"model": "claude-haiku-4-5", "system": "You handle account and login issues. ..."}, "other": {"model": "claude-sonnet-4-5", "system": "You handle unusual requests. Escalate if ..."}, } def classify(ticket: str) -> str: msg = client.messages.create( model="claude-haiku-4-5", # cheap model does the sorting max_tokens=10, system="Classify the ticket. Reply with exactly one word: " "refund, bug, account, or other.", messages=[{"role": "user", "content": ticket}], ) label = msg.content[0].text.strip().lower() return label if label in ROUTES else "other" def handle(ticket: str) -> str: r = ROUTES[classify(ticket)] msg = client.messages.create( model=r["model"], max_tokens=1024, system=r["system"], messages=[{"role": "user", "content": ticket}], ) return msg.content[0].text

Two wins: each specialist prompt stays short and sharp (no "if it's a refund... but if it's a bug..." spaghetti), and you stop paying frontier-model prices for password resets. The classifier is itself a great eval target - a labeled set of 50 tickets tells you its accuracy in minutes.

CoreParallelization: sectioning and voting5 min

Two distinct flavors share the same machinery:

  • Sectioning: split independent work, run it concurrently, aggregate. Review 10 documents at once; check a draft for tone, accuracy and legal exposure in three parallel calls.
  • Voting: run the SAME task N times, then take the majority answer (or have a judge model pick). Buys reliability on judgment calls where any single call is noisy.
★ Voting classifier with asyncioimport asyncio from collections import Counter from anthropic import AsyncAnthropic aclient = AsyncAnthropic() MODEL = "claude-sonnet-4-5" # check docs for the current model async def judge_once(text: str) -> str: msg = await aclient.messages.create( model=MODEL, max_tokens=5, system="Does this comment violate our community policy? " "Reply exactly YES or NO.", messages=[{"role": "user", "content": text}], ) return msg.content[0].text.strip().upper() async def judge_by_vote(text: str, n: int = 3) -> str: votes = await asyncio.gather(*[judge_once(text) for _ in range(n)]) winner, count = Counter(votes).most_common(1)[0] return winner # majority of n verdict = asyncio.run(judge_by_vote("borderline comment here")) print(verdict)

Sectioning is the same gather() with different prompts per task, plus one final aggregation call. Costs scale linearly with N, so vote where mistakes are expensive (moderation, compliance flags) and skip it where they're cheap.

AdvancedOrchestrator-workers and evaluator-optimizer4 min read

The last two workflow patterns add dynamism while your code still owns the outer structure:

  • Orchestrator-workers: when you can't predict the subtasks upfront, let a lead model decompose the task at runtime ("this codebase change touches these 4 files, here's a work order for each"), fan the work orders out to worker calls, then synthesize. Unlike sectioning, the SPLIT itself is model-decided - but your code still runs the fan-out and the merge.
  • Evaluator-optimizer: a generate-evaluate-refine loop. One call produces, a second grades against explicit criteria, and the producer retries with the feedback until the grader passes or a retry budget runs out. This is your 6.2 evals discipline turned into a runtime component - the grader IS a model-based eval.
★ Evaluator-optimizer skeletondef generate(task: str, feedback: str = "") -> str: prompt = task if not feedback else f"{task}\n\nFix these issues:\n{feedback}" return ask(prompt) def evaluate(task: str, attempt: str) -> str: return ask( "Grade this attempt against the task. Criteria: correct, " "complete, matches the requested format. " "Reply PASS or a bullet list of concrete fixes.\n\n" f"Task:\n{task}\n\nAttempt:\n{attempt}" ) def refine_until_pass(task: str, max_rounds: int = 3) -> str: feedback = "" for _ in range(max_rounds): attempt = generate(task, feedback) verdict = evaluate(task, attempt) if verdict.strip().upper().startswith("PASS"): return attempt feedback = verdict return attempt # best effort after budget
Grader quality is the ceiling An evaluator-optimizer loop can never be better than its grader. Write the grading criteria as concretely as an eval rubric (6.2), test the grader on known-good and known-bad examples first, and always cap the rounds - a loop that can't fail is a loop that can't stop.
Part 2 · when the model owns the loop

Agents proper 8 min

An agent is a model in a loop with tools and environment feedback. The engineering is not in the loop - it's in choosing the right tasks and bounding the blast radius.

CoreThe loop, good agent tasks, and guardrails8 min

The whole agent loop fits in a dozen lines: call the model with tools, execute whatever tool it asks for, feed the result back, repeat until it stops asking - or until you cut it off.

★ The agent loop, minimal formMAX_STEPS = 15 # step budget: agents need an off switch messages = [{"role": "user", "content": task}] for step in range(MAX_STEPS): resp = client.messages.create( model=MODEL, max_tokens=4096, tools=TOOLS, # the allowlist IS the guardrail messages=messages, ) messages.append({"role": "assistant", "content": resp.content}) if resp.stop_reason != "tool_use": break # model says it's done results = [run_tool(b) for b in resp.content if b.type == "tool_use"] messages.append({"role": "user", "content": results})

The key lesson from the official module is environment inspection: a good agent doesn't assume - it looks. It lists the directory before editing, runs the test before claiming the fix, reads the error instead of guessing. Tool results are the agent's senses; design tools so their outputs are informative enough to steer the next step.

Qualities of tasks agents are good at:

  • Verifiable progress: there's a signal that says "closer" or "done" - a test suite, a compiler, a schema check. Without it the agent can't tell success from failure and neither can you.
  • Recoverable errors: a wrong step can be observed and undone. Editing a file in a git repo: recoverable. Sending an email: not.
  • Bounded blast radius: the worst plausible action is affordable. A sandbox, a branch, a staging environment - never the production database on day one.

Guardrails, in order of importance: tool allowlists (the agent literally cannot do what it has no tool for), step budgets (every loop gets a max), and human gates (irreversible actions - deploy, send, delete - pause for approval). Design the guardrails before the prompt, not after the incident.

Real world

A data team gave an agent three tools: read-only SQL, a scratch-schema write, and a Slack draft (never send). It investigates data-quality alerts overnight: queries the offending table, reproduces the anomaly in scratch, and drafts the findings for a human to post in the morning. Verifiable (the anomaly reproduces or it doesn't), recoverable (scratch schema), bounded (drafts only). That task profile is why it works unattended - not the prompt.

Part 3 · covers the "Anthropic apps" module

Claude Code and computer use 8 min

You don't have to build an agent to study one. Claude Code is a production agent you already run daily - and computer use is the same loop pointed at a screen.

CoreClaude Code as the case study5 min

Map Claude Code onto everything above: it's an agent loop (the model decides which file to read next), with environment inspection (it greps before editing, runs tests after), tool allowlists (your permission settings), step budgets and human gates (approval prompts on risky commands). Three practices from the official module:

What the agent loop adds over one call environment checks tool allowlists step budgets + gates AGENT LOOP (Claude Code) reacts to tool results, keeps going until done SINGLE CLAUDE CALL one response, no tools, no loop = bounded autonomy It greps before editing and runs tests after: inspection, not assumption.
🔍 Click to zoom - environment checks, allowlists and step budgets turn a call into a bounded agent
  • Parallelizing Claude Code: run multiple instances on independent tasks at once - one per git worktree so they can't step on each other's files. One instance fixes a bug on a branch while another writes docs on a second worktree. This is the sectioning pattern, applied to your own workday.
  • Automated debugging: point Claude Code at a failing test and let the loop run: run test, read failure, form hypothesis, edit, re-run. The failing test is the "verifiable progress" signal in its purest form - which is exactly why this is the canonical agent demo.
  • Enhancing with MCP: every MCP server you add (6.6) is a new sense or limb - a database server means it can inspect real data while debugging, a browser server means it can check the rendered page. The agent loop is fixed; the toolset is where you invest.
AdvancedComputer use: Claude operating a screen3 min read

How it works: the same agent loop, with the environment swapped for a desktop. Claude receives a screenshot, decides an action (click at coordinates, type text, press keys, scroll), your harness executes it, and a fresh screenshot comes back as the tool result. Look, act, look again - environment inspection at 100% literalness.

  • Good for: legacy UI automation (that internal tool with no API), end-to-end testing of real interfaces ("complete checkout as a new user and report anything confusing"), and one-off migrations through GUI-only admin panels.
  • Why tight sandboxing is non-negotiable: a screen-driving agent can click ANYTHING visible - real send buttons, real delete confirmations, whatever a malicious page displays (prompt injection now arrives via screenshot). Run it in a dedicated VM or container with a throwaway account, no credentials that matter, network limited to what the task needs, and a human gate before anything irreversible. Slower and costlier than an API path too - so if an API exists, use 6.3 tool use instead.
35-45 · hands on

Try it yourself ◐ 3 exercises

Source material

Official courses covered

This page covers the agents and apps modules shared by the three 8-hour engineering courses at claude.com/resources/courses.

Agents & Workflows module - all 3 coursesparallelization, chaining, routing, agents vs workflows, qualities of agents
Anthropic Apps: Claude Code + computer use - Bedrock/Vertexparallelizing Claude Code, automated debugging, MCP enhancements, computer use
Introduction to subagents - appliedtaught live in Session 6; here the same ideas appear as orchestrator-workers and parallel Claude Code

Deep dive 6.7 cheat sheet · pin this

The distinctionWorkflow = your code owns the flow, LLM fills steps. Agent = model owns the loop. Use the simplest thing that works.
ChainingFixed sequence, each step eats the last output. Add cheap gates between expensive steps.
RoutingClassify first with a cheap model, dispatch to specialist prompts and right-sized models.
ParallelizationSectioning = split, gather, aggregate. Voting = same task N times, majority wins. asyncio.gather does both.
Agent tasksVerifiable progress + recoverable errors + bounded blast radius. Guardrails: tool allowlist, step budget, human gates.
Case studyClaude Code IS the agent loop: worktrees for parallelism, failing tests for verifiable progress, MCP for new senses.