Lesson 1.2
Workflow Patterns in Code
The five composable workflow patterns for Claude applications, when each one beats a free-running agent, and what they look like in Python and TypeScript.
Anthropic draws a sharp line between workflows, where your code decides the sequence of model calls, and agents, where Claude decides dynamically which steps to take. Most production systems are workflows. The exam expects you to recognise the standard patterns, choose between them from a scenario, and know why "start with the simplest thing that works" is the official guidance.
Workflow or agent?
Ask four questions before reaching for an agent: Is the task genuinely multi-step and hard to specify up front? Does the value justify the extra cost and latency? Is Claude demonstrably capable at this task type? Can errors be caught and recovered? If the answer to any of these is no, a single call or a fixed workflow is the better design. Workflows are more predictable, cheaper to run, and far easier to test because each step has a known input and output.
The patterns
Prompt chaining
Break a task into sequential calls where each output feeds the next, optionally with a programmatic gate between steps. Use it when a task decomposes cleanly and you are trading latency for accuracy.
def chain(client, model, document):
outline = ask(client, model, f"Produce a bullet outline of:\n<doc>{document}</doc>")
if len(outline.splitlines()) < 3: # gate: reject weak intermediate output
raise ValueError("outline too thin")
return ask(client, model, f"Write a summary from this outline:\n{outline}")
def ask(client, model, prompt):
r = client.messages.create(model=model, max_tokens=1024,
messages=[{"role": "user", "content": prompt}])
return next(b.text for b in r.content if b.type == "text")
Routing
Classify the input first, then dispatch to a specialised prompt, tool set or even a different model. Routing lets you send simple requests to a fast, inexpensive model and reserve the most capable one for hard cases.
const route = await client.messages.create({
model: SMALL_MODEL, max_tokens: 10,
system: "Reply with exactly one word: billing, technical or general.",
messages: [{ role: "user", content: ticket }],
});
const label = textOf(route).trim().toLowerCase();
const handler = { billing: billingPrompt, technical: techPrompt, general: generalPrompt }[label]
?? generalPrompt;
Structured output (lesson 6-2) is the more robust way to get the label, because it guarantees a value from your enum rather than free text you have to normalise.
Parallelisation
Run independent calls concurrently and merge. There are two flavours: sectioning splits a task into independent parts, and voting runs the same prompt several times to get diverse judgements.
const [security, perf, style] = await Promise.all(
["security", "performance", "style"].map((focus) =>
client.messages.create({
model: MODEL, max_tokens: 2048,
system: `Review only for ${focus} issues.`,
messages: [{ role: "user", content: code }],
})
)
);
Parallel calls count against your rate limits simultaneously, so respect the concurrency your tier allows.
Orchestrator-workers
A central call breaks a task into subtasks it determines at runtime, worker calls handle each one, and the orchestrator synthesises the results. Unlike parallelisation the subtasks are not known in advance. This is the pattern behind multi-file coding changes and research tasks with an unknown number of sources.
Evaluator-optimiser
One call produces, another evaluates against explicit criteria, and the loop repeats until the evaluator passes it or a retry cap is hit. It works when you have clear evaluation criteria and iterative refinement measurably helps, such as translation or code that must pass tests.
for attempt in range(3):
draft = ask(client, model, f"Write the function.\n{spec}\nFeedback: {feedback}")
verdict = ask(client, model, f"Does this satisfy the spec? Reply PASS or list issues.\n{spec}\n{draft}")
if verdict.startswith("PASS"):
break
feedback = verdict
Choosing between them
| You know the steps in advance | Steps are independent | Pattern |
|---|---|---|
| Yes | No | Prompt chaining |
| Yes | Yes | Parallelisation |
| Yes, but the input type varies | n/a | Routing |
| No, the model must decide | Yes, after decomposition | Orchestrator-workers |
| No, quality must converge | n/a | Evaluator-optimiser |
Agents sit beyond this table: Claude plans, uses tools in a loop, and decides when it is done. Combine patterns freely; a router in front of an orchestrator is common.
Engineering notes
- Each workflow step is an ordinary Messages API call, so caching, structured output and token counting all apply per step.
- Put programmatic checks between steps: schema validation, length checks, test runs. Code is cheaper and more reliable than another model call.
- Keep intermediate outputs in your own state, not in a growing conversation, unless later steps genuinely need the dialogue history.
- Log
usageandstop_reasonfor every step so you can see where cost and failures concentrate.
Exam focus
- Workflows have code-determined control flow; agents let Claude decide the steps. Anthropic recommends starting with the simplest option.
- Prompt chaining trades latency for accuracy via sequential steps with optional programmatic gates.
- Routing classifies first, then dispatches to specialised prompts or cheaper models.
- Parallelisation covers sectioning (independent parts) and voting (multiple attempts on the same task).
- Orchestrator-workers is for subtasks that cannot be predicted in advance; evaluator-optimiser is for iterative refinement with clear criteria.
- Agents are justified only when the task is complex, valuable, viable for the model, and errors are recoverable.
Check yourself
5 questions
1.A developer writes: category = classify(ticket) # one cheap Claude call returns "billing" | "bug" | "howto" prompt = PROMPTS[category] # pick a specialised prompt answer = respond(prompt, ticket) # second call Which workflow pattern is this?
2.Consider this code: for i in range(3): draft = generate(brief, feedback) verdict = critique(draft, rubric) # returns {"approved": bool, "feedback": str} if verdict["approved"]: break feedback = verdict["feedback"] Which pattern does it implement, and what is its main cost?
3.A content pipeline does: outline = call("Produce an outline with at least 3 sections", topic) if len(parse_sections(outline)) < 3: raise ValueError("outline too short") article = call("Write the article following this outline", outline) What is the programmatic check between the two calls called, and which pattern is this?
4.A code-review service runs: const reviews = await Promise.all(files.map(f => reviewFile(f, "Look for SQL injection"))); const report = merge(reviews); Which workflow pattern is this?
5.According to Anthropic's guidance on building effective agents, what distinguishes a workflow from an agent?
