AI Workflows & Orchestration
IC4IC5

The Five Composable Workflow Patterns

Five LEGO bricks — chaining, routing, parallelization, orchestrator-workers, evaluator-optimizer — that snap together into every production LLM pipeline, with the cost and failure mode of each baked in.

15 min read · 14 sections
0

1. Quick anchor

A workflow is an LLM pipeline whose control flow you can draw as a flowchart before you run it. Anthropic's "Building Effective Agents" distilled the design space down to five composable patterns: prompt chaining (sequential steps with gates between them), routing (classify, then dispatch to a specialist), parallelization (fan out into independent sections or repeated votes), orchestrator-workers (a planner LLM decomposes a task at runtime and farms out subtasks), and evaluator-optimizer (generate, critique, refine in a loop). These are not competing frameworks — they are LEGO bricks. A real product is usually a chain whose third step is a router that hands off to a worker pool that internally runs an evaluator loop. The whole point of learning the five is to reach for the smallest pattern that solves the problem, because each step up the ladder buys flexibility by spending tokens, latency, and your ability to debug a failure at 3am.

2. Why interviewers probe this

  • IC4 — Can you name the five and, more importantly, pick the right one for a described problem instead of reaching for an agent by reflex? They want to see you draw the flowchart and reject patterns that are overkill. The tell of a strong IC4 is starting with prompt chaining and justifying every step up.
  • IC5 — Can you reason quantitatively about reliability (compounding per-step error), cost (the 3–10x multiplier of orchestrator-workers), and the failure modes each pattern introduces? Can you compose patterns into a real system and defend where you drew the line between deterministic code and model autonomy? They are listening for "I'd start with a workflow and add autonomy only in a bounded scope."
  • Both — Do you treat structured outputs, routing classifiers, and observability as load-bearing infrastructure rather than afterthoughts? An engineer who has shipped this knows the router's classifier and the gate between chain steps are where production breaks.

3. Concept build-up

Beginner explainerNew here? The words first

The words first.

  • Workflow — an LLM pipeline whose steps are wired in code; you can draw the flowchart before running it.
  • Agent — a system where the model decides its own next step at runtime; you cannot fully draw the flowchart in advance.
  • Prompt chaining — feed the output of one LLM call into the next, like an assembly line.
  • Gate — a cheap check between steps that stops the line if a step's output is bad (wrong format, failed a rule).
  • Routing — a first call classifies the input, then code sends it to the handler built for that class.
  • Parallelization — run several LLM calls at once: either on different pieces of the task (sectioning) or the same task many times to vote (voting).
  • Orchestrator-workers — a "manager" LLM breaks a task into subtasks it didn't know in advance, hands each to a "worker" LLM, then merges the results.
  • Evaluator-optimizer — one LLM writes a draft, a second grades it against criteria, and the first revises until it passes.

Step by step.

  1. Write down the task and ask: can I draw the flowchart now? If yes, it's a workflow — keep going.
  2. If the task is one well-defined sequence, use a chain and put a gate after any step that can fail loudly.
  3. If different inputs need different handling, classify first, then route.
  4. If the work splits into independent pieces or you need a confidence vote, parallelize.
  5. If you can't predict the subtasks until you see the input, use orchestrator-workers — but only then.
  6. If quality matters more than latency and you can write a grader, wrap the output in an evaluator-optimizer loop.
  7. Compose: most real systems are a chain of these bricks, not a single pattern.

Remember this: start with the smallest pattern that works and step up the ladder only when the task forces you to.

3.1 The spectrum: workflow vs. agent

The cleanest mental test Anthropic gives is: can you draw the flowchart before execution? If yes, you have a workflow — LLM calls and tools orchestrated through predefined code paths. If no — if the path depends on what the model discovers as it runs — you have an agent. This isn't a binary, it's a dial of autonomy. The five patterns live on the low-to-mid part of that dial. They matter because every step toward autonomy trades away three things interviewers will press you on: cost predictability (a workflow has a fixed token budget; an agent can spike 4–15x on exploration loops), auditability (every workflow path is testable pre-deploy, which SOC 2 / GDPR / financial regulators love), and debuggability (a workflow failure has a known path; an agent failure requires reconstructing a trajectory). The senior instinct is to push as far down the spectrum as the task allows.

◐ InteractiveThe five workflow patterns
instep 1step 2step 3out

Sequential steps with checkpoints. Use when a task splits into fixed, ordered subtasks (e.g. outline → draft → polish).

3.2 Pattern 1 — Prompt chaining (+ gates)

A chain decomposes a task into a fixed sequence of LLM calls, each consuming the previous output. The canonical example: write an outline → check the outline meets criteria → write the document from the outline. You use it when the task is genuinely a pipeline of well-defined, verifiable sub-steps, and where doing it in one giant prompt would overload the model and tank accuracy. Decomposition trades a little latency (more round-trips) for a lot of accuracy, because each call has a narrower, cleaner job.

The load-bearing word is gate. Between steps you insert a programmatic check — schema validation, a regex, a relevance test, or a small cheap LLM call — that either passes the output forward or short-circuits the chain. A gate that catches a malformed outline before you spend tokens writing 2,000 words from it is the entire ROI of the pattern. Without gates, a chain just compounds the first step's error through every subsequent step. Cost and complexity are both low; this is your default for multi-stage reasoning.

3.3 Pattern 2 — Routing (classify → specialize)

Routing puts a classifier at the front door. One upstream call (or a fine-tuned small model, or even a regex tier) labels the input — pricing, refund, returns — and code dispatches it to the handler purpose-built for that class. The win is that each specialist handler gets a tight, focused prompt instead of one mega-prompt trying to cover every case, which raises accuracy and lets you route easy classes to a cheap model (Haiku) and hard classes to a frontier model. The upstream classification is a single cheap call, so cost is low.

Routing is also where relevance classifiers earn their keep: a binary "is this even in scope?" check rejects out-of-scope inputs before any expensive tool fires, cutting both token spend and error propagation. The failure mode to name in an interview: a misroute sends the input to the wrong specialist, and the specialist confidently answers the wrong question. So you measure routing accuracy as its own metric, and you give handlers a cheap escape hatch ("this doesn't look like a refund — re-route") rather than letting them hallucinate.

Routing on real numbers — does specialization pay?

Setup: 10,000 support tickets/day. One mega-prompt on a frontier model gets 82% right and costs (say) 1.0x per ticket. A router adds one cheap classification call, then sends each ticket to a focused specialist.

  • Classifier accuracy: 95% route to the correct specialist.
  • Correctly-routed tickets resolve at 92% (focused prompt > mega-prompt).
  • Mis-routed tickets (5%) resolve at ~20% (wrong specialist, wrong answer).

End-to-end accuracy = 0.95 * 0.92 + 0.05 * 0.20 = 0.874 + 0.010 = 87.4%, up from 82%.

Cost: the classifier is a tiny call on a cheap model — call it 0.05x. Two of your three specialists run on Haiku at 0.3x, one on a frontier model at 1.0x. Blended specialist cost ≈ 0.05 + (0.7 * 0.3 + 0.3 * 1.0) = 0.05 + 0.51 = 0.56x per ticket.

What it did to the data: +5.4 accuracy points at roughly half the per-ticket cost — because most traffic was easy and the router stopped paying frontier prices for it. The 1% absolute loss from mis-routes is the price you pay; that's the number you'd attack next by improving the classifier.

3.4 Pattern 3 — Parallelization (sectioning + voting)

Parallelization has two distinct shapes, and naming both signals depth.

Sectioning breaks a task into independent subtasks that run concurrently, then aggregates. Reviewing a PR? Run a security-review prompt, a style prompt, and a test-coverage prompt in parallel, then merge. Latency drops to roughly the slowest branch instead of the sum of all branches; token cost stays about the same (you were going to do the work anyway) but wall-clock improves dramatically.

Voting runs the same task multiple times and aggregates — majority vote, or "flag if any of 5 runs says unsafe." This buys confidence, not speed, and it costs Nx the tokens for N votes. You use voting where a false negative is expensive (content moderation, "is this code change risky?") and where the model's errors are uncorrelated enough that voting actually denoises them. The honest caveat: if the model is systematically wrong on a class of input, five votes give you the same wrong answer five times — voting fixes variance, not bias.

3.5 Pattern 4 — Orchestrator-workers

Here the structure becomes dynamic. A central orchestrator LLM reads the input and decides at runtime how to decompose it — how many subtasks, of what shape — then delegates each to a worker LLM and synthesizes the results. The defining property: you can't predict the subtask count or structure upfront, so you can't pre-wire it as a sectioning workflow. Classic use: "research this topic," where the orchestrator decides which sub-questions to investigate based on what the topic turns out to be.

This is the most powerful workflow pattern and the one Anthropic explicitly warns most production systems should not need — they advise staying at parallelization or below unless the task genuinely demands runtime decomposition. The cost is real: 3–10x more tokens per task than a simple chain, plus three new failure modes you must name — the orchestrator mis-decomposes (splits the task badly), a worker fails or drifts, or the synthesis step hallucinates a coherent answer from incomplete worker outputs. Because the decomposition is non-deterministic, two runs on the same input can diverge, which is why durable checkpointing (covered in the next lesson) becomes important here.

3.6 Pattern 5 — Evaluator-optimizer

A generator LLM produces a draft; an evaluator LLM grades it against explicit success criteria; if it fails, the generator revises using the critique; repeat until pass or a max-iteration cap. This works precisely when two conditions hold: you can articulate clear evaluation criteria, and iteration measurably improves the output. Legal-document drafting, code review, and translation polishing are the sweet spot — domains where "is this good?" has a checkable answer and a second pass genuinely helps.

The cost is variable and unbounded unless you cap it — loop depth depends on how fast the generator converges, so you always set a max-iterations limit (and ideally an early-stop when the evaluator score plateaus). The subtle failure mode: if the evaluator and generator share the same blind spot, the loop converges to confidently-wrong, because the grader can't see the error either. Strong answers note that the evaluator should use a different prompt framing — or even a different model — than the generator, so their errors aren't perfectly correlated.

4. Minimal implementation

The five patterns are small enough that you should be able to write any of them from memory. Here are three of the trickier ones — routing, voting, and an evaluator loop — in production-shaped Python against the Claude API, using structured outputs so the router's decision and the evaluator's verdict are guaranteed-valid JSON, not regex-scraped prose.

import os
import asyncio
import json
from collections import Counter
from anthropic import Anthropic, AsyncAnthropic
 
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
aclient = AsyncAnthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
 
# --- Pattern 2: ROUTING ------------------------------------------------------
# A routing classifier returns a constrained label via tool-use, so the route
# is type-safe — never a hallucinated category. tool_choice forces the call.
ROUTE_TOOL = {
    "name": "route",
    "description": "Classify a support ticket to the correct specialist.",
    "input_schema": {
        "type": "object",
        "properties": {
            "category": {"type": "string", "enum": ["pricing", "refund", "returns", "other"]},
            "confidence": {"type": "number", "minimum": 0, "maximum": 1},
        },
        "required": ["category", "confidence"],
    },
}
 
# Map each class to (model, system_prompt). Easy classes -> cheap Haiku.
HANDLERS = {
    "pricing": ("claude-haiku-4-5", "You are a pricing specialist. Be exact about plans and tiers."),
    "refund":  ("claude-sonnet-4-5", "You are a refund specialist. Quote the refund policy precisely."),
    "returns": ("claude-haiku-4-5", "You are a returns specialist. Give clear step-by-step instructions."),
    "other":   ("claude-sonnet-4-5", "You are a general support agent."),
}
 
def route(ticket: str) -> dict:
    """Upstream classify call. One cheap model call; tool_choice forces JSON."""
    msg = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=256,
        tools=[ROUTE_TOOL],
        tool_choice={"type": "tool", "name": "route"},  # force the schema
        messages=[{"role": "user", "content": ticket}],
    )
    decision = next(b.input for b in msg.content if b.type == "tool_use")
    # A relevance gate: low confidence -> human handoff, not a guessed answer.
    if decision["confidence"] < 0.55:
        decision["category"] = "other"
    return decision
 
def handle(ticket: str) -> str:
    decision = route(ticket)
    model, system = HANDLERS[decision["category"]]
    reply = client.messages.create(
        model=model, max_tokens=512, system=system,
        messages=[{"role": "user", "content": ticket}],
    )
    return reply.content[0].text
 
# --- Pattern 3: PARALLELIZATION (voting) -------------------------------------
async def _one_vote(code_diff: str) -> str:
    msg = await aclient.messages.create(
        model="claude-haiku-4-5", max_tokens=8, temperature=1.0,  # variance is the point
        system="Reply with exactly one word: SAFE or RISKY.",
        messages=[{"role": "user", "content": code_diff}],
    )
    return msg.content[0].text.strip().upper()
 
async def vote_is_risky(code_diff: str, n: int = 5) -> bool:
    """Run the SAME judgment n times in parallel; majority wins. Costs n calls."""
    votes = await asyncio.gather(*[_one_vote(code_diff) for _ in range(n)])
    tally = Counter(votes)
    # Conservative: any 'RISKY' plurality blocks. Tune the threshold to your risk.
    return tally["RISKY"] >= (n // 2 + 1)
 
# --- Pattern 5: EVALUATOR-OPTIMIZER ------------------------------------------
GRADE_TOOL = {
    "name": "grade",
    "description": "Grade a draft against the rubric.",
    "input_schema": {
        "type": "object",
        "properties": {
            "passes": {"type": "boolean"},
            "score": {"type": "integer", "minimum": 0, "maximum": 10},
            "critique": {"type": "string"},
        },
        "required": ["passes", "score", "critique"],
    },
}
 
def refine(task: str, rubric: str, max_iters: int = 3) -> str:
    """Generate -> evaluate -> refine. ALWAYS cap iterations: loop cost is unbounded."""
    draft = client.messages.create(
        model="claude-sonnet-4-5", max_tokens=1024,
        messages=[{"role": "user", "content": task}],
    ).content[0].text
 
    for _ in range(max_iters):
        grade = client.messages.create(
            model="claude-sonnet-4-5", max_tokens=512,
            tools=[GRADE_TOOL], tool_choice={"type": "tool", "name": "grade"},
            # Different framing than the generator -> less correlated blind spots.
            system=f"You are a strict reviewer. Rubric:\n{rubric}",
            messages=[{"role": "user", "content": draft}],
        )
        verdict = next(b.input for b in grade.content if b.type == "tool_use")
        if verdict["passes"]:
            return draft
        # Feed the critique back into a targeted revision.
        draft = client.messages.create(
            model="claude-sonnet-4-5", max_tokens=1024,
            messages=[{"role": "user", "content":
                f"Task: {task}\n\nYour draft:\n{draft}\n\nFix this critique:\n{verdict['critique']}"}],
        ).content[0].text
    return draft  # ran out of iterations: return best effort, flag for review

Three things make this production-shaped rather than toy. First, the router uses tool-use with tool_choice forcing so the category is one of four valid enums — Claude restricts token generation to the schema grammar, giving ~99.9%+ valid JSON and eliminating the "model returned Pricing." parsing bug class. Second, the router has a confidence gate: low-confidence tickets fall back to human/general handling instead of being confidently mis-routed. Third, the evaluator loop has a hard max_iters cap and returns a flagged best-effort on timeout — never an infinite loop. Voting uses temperature=1.0 on purpose: votes only denoise if the runs actually vary. Model IDs here are illustrative; check current names in the Claude API reference before shipping.

5. Production tradeoffs

Pattern Relative token cost Latency profile Best when Primary failure mode
Prompt chaining Low (fixed steps) Sum of steps (serial) Task is a fixed, verifiable pipeline Errors compound through steps without gates
Routing Low (one classify call) Classify + one handler Distinct input classes need distinct handling Mis-route → confident wrong answer
Parallelization — sectioning ~Same as serial ≈ slowest branch Independent subtasks; latency matters Aggregation logic drops/double-counts a branch
Parallelization — voting N× (N votes) ≈ one call (parallel) Need confidence; uncorrelated errors Systematic bias survives the vote
Orchestrator-workers 3–10× Variable, unpredictable Subtasks unknown until runtime Mis-decompose / worker drift / synthesis hallucination
Evaluator-optimizer Variable (capped) Iters × round-trip Clear rubric + iteration helps Correlated evaluator/generator blind spot

The single most important production fact is compounding error. A workflow of k sequential LLM steps, each succeeding with probability p, has end-to-end success of p^k. At p = 0.99 and k = 10, that's 0.99^100.904 — a 10-step pipeline at 99% per-step reliability fails roughly one run in ten. This is the reason interviewers prefer workflows over agents for high-volume repeatable processes: fewer steps and deterministic paths keep k small and each p near 1. The levers to raise end-to-end success are: cut k (fewer steps), raise each p (gates, structured outputs, voting on the riskiest step), and make failures recoverable (durable checkpointing so a failed step retries instead of re-running the whole chain).

What changes at scale (1000s of runs/day): you can no longer eyeball failures, so observability becomes mandatory — OpenTelemetry spans for every LLM call, tool invocation, and state transition, with structured, queryable logs (model ID, temperature, token counts, the route taken). Industry data shows ~89% of orgs have agent observability and 62% have step-level tracing, but only ~52% close the loop with systematic evaluation — meaning most teams see failures but don't route them back into improvement. The senior move is annotation queues: production traces flagged as wrong get routed to domain experts whose judgments become evaluation datasets. Cost-wise, the discipline is pre-computing a token budget per run and routing easy traffic to cheap models (Haiku) — workflows let you do this because the path is known; agents fight you on it because the path isn't.

6. How it's asked

[IC4] "Walk me through Anthropic's five workflow patterns. For one of them, write the control-flow shape and say when it earns its keep." The five are prompt chaining, routing, parallelization (sectioning and voting), orchestrator-workers, and evaluator-optimizer. Take routing: a cheap upstream classifier labels the input, then code dispatches to a specialist handler built for that class — classify(x) → handlers[label](x). It earns its keep when distinct input classes need distinctly different handling, because each specialist gets a focused prompt (higher accuracy) and you can route easy classes to a cheaper model. The cost is one extra classification call, and the failure mode is a mis-route, which I'd guard with a confidence gate that falls back to a general handler.
[IC5] "You have a 7-step pipeline where each step succeeds 97% of the time. What's your end-to-end success rate, and what gets you to 99%?" End-to-end is 0.97^70.808 — about 81%, which is unshippable. Three levers: (1) cut steps — fold trivial steps into adjacent ones so k drops, since the exponent dominates; (2) raise per-step p — add a gate (schema validation / structured outputs) after the riskiest steps, and vote on the single least-reliable step to denoise it toward ~99.5%; (3) make it recoverable — durable checkpointing so a transient step failure retries that step instead of failing the whole run. To clear 99% end-to-end across 7 steps you need each step at roughly 0.99^(1/7) ≈ 0.9986, so realistically you combine cutting k to ~4–5 and hardening the top two failure points rather than chasing 99.86% everywhere.
[IC5] "When do you reach for orchestrator-workers versus a true agent, and what does it cost you?" I reach for orchestrator-workers when I can't predict the subtask structure upfront but the control loop is still mine — a planner LLM decomposes, fixed worker code executes, a synthesis step merges, and I can draw that flowchart even if the branch count is dynamic. I go to a true agent only when even the loop is unknowable — the model must decide its own next action based on what it observes, like open-ended debugging. The cost of orchestrator-workers is 3–10x tokens over a chain and three new failure modes (mis-decomposition, worker drift, synthesis hallucination); a full agent adds non-determinism that breaks cost budgeting (4–15x spikes), auditability, and reproducibility. So I default to the workflow and grant autonomy only in a bounded sub-scope with guardrails.
[IC4] "My evaluator-optimizer loop sometimes runs forever and the output still isn't great. What's wrong?" Two classic bugs. First, no hard iteration cap — loop depth is unbounded by construction, so you always set max_iters and an early-stop when the evaluator's score plateaus, returning a flagged best-effort otherwise. Second, the output stalls because the evaluator and generator share a blind spot — if both are the same model with the same framing, the grader can't see the error the writer made. Fix it by giving the evaluator a different prompt framing (a strict-reviewer persona, an explicit rubric) or a different model, so their errors are less correlated and the critique actually catches something.
[IC5] "Sketch how you'd compose these patterns for a customer-support product handling thousands of tickets a day." A chain at the top: ingest → route (relevance gate rejects spam/out-of-scope first, then classify to pricing/refund/returns) → specialist handler. Easy classes run on Haiku, hard ones on Sonnet. For risky actions like issuing a refund, the handler's output passes an evaluator gate against policy before execution, and I'd vote on the binary "is this refund authorized?" decision since a false approval is expensive. I deliberately stay at routing + gates + a bounded evaluator rather than an agent, because at thousands of runs/day I need fixed token budgets, testable paths for compliance, and OpenTelemetry traces with an annotation queue so flagged tickets become eval data. I'd only add autonomy inside one specialist if a task genuinely needed runtime decomposition.

7. Pitfalls & flashcards

  • Reaching for an agent by reflex. If you can draw the flowchart, it's a workflow — and a workflow is cheaper, auditable, and debuggable. Anthropic's own guidance is that most production systems shouldn't exceed parallelization. Start small; step up the ladder only when the task forces you.
  • Chains without gates. A gateless chain compounds the first error through every step. Put a cheap programmatic check (schema, regex, relevance test) after any step that can fail loudly.
  • Forgetting voting fixes variance, not bias. Five votes on a systematically-wrong model give you the same wrong answer five times. Voting only helps when errors are uncorrelated.
  • Uncapped evaluator loops. Loop depth is unbounded by construction; always set max_iters and an early-stop, and make the evaluator's framing differ from the generator's to break correlated blind spots.
  • Treating routing accuracy as free. A mis-route produces a confident wrong answer. Measure routing accuracy as its own metric and add a confidence gate with a fallback handler.
  • Scraping JSON from prose. Use structured outputs / tool-use with tool_choice forcing for any routing or grading decision — ~99.9%+ valid JSON beats regex every time.
  • Skipping observability until it hurts. At 1000s of runs/day you can't eyeball failures. Spans + structured logs + an annotation queue from day one; the trace is the debugger.

Flashcard. Five patterns, increasing autonomy: chain (fixed steps + gates) → route (classify → specialize) → parallelize (section for speed, vote for confidence) → orchestrator-workers (runtime decomposition, 3–10x cost) → evaluator-optimizer (generate-critique-refine, always capped). Pick the smallest that works. End-to-end success of k steps at per-step reliability p is p^k.

8. Further reading

Next: Durable execution & state — what happens when step 6 of 10 crashes, and how checkpoint-replay (Temporal, LangGraph) turns a fragile chain into one that survives restarts. Also see /agents for where the autonomy dial goes past these five, and /evals for turning those annotation-queue traces into regression suites.

Primary sources
← More in AI Workflows & Orchestration