Agentic workflow patterns
Chaining, routing, parallelization, orchestrator–workers, evaluator–optimizer
Most successful "agents" in production are compositions of five workflow patterns that keep control flow in your code and use the model for judgment. Knowing the patterns lets you reach for the simplest structure that solves a problem, estimate its cost, and name its failure modes before you build it. They are also the vocabulary you will use to read framework documentation (9.10 Frameworks and SDKs) and multi-agent designs (10.6 Multi-agent systems).
You'll be able to
- Implement prompt chaining, routing, parallelization, orchestrator–workers and evaluator–optimizer in plain Python
- Choose a pattern from task structure (fixed vs dynamic decomposition, independence, verifiability) and estimate its cost multiplier
- Name each pattern's characteristic failure mode and the guardrail that addresses it
- Compose patterns and apply the 'start simple, add complexity only when measured' rule
Key ideas
- Every pattern is wiring between augmented-LLM calls; the difference is who owns control flow and whether decomposition is known in advance.
- Prompt chaining: fixed sequence with programmatic gates — accurate per step, but errors propagate.
- Routing: classify then dispatch to specialized prompts/models — enables cost tiering; guard against misrouting with a default route and measured accuracy.
Show 4 more key ideas
- Parallelization: sectioning (independent subtasks) or voting (confidence) — N× tokens at 1× latency; needs a reconciliation step.
- Orchestrator–workers: a model decomposes dynamically and delegates; expensive (10–15×) and sensitive to vague task specs.
- Evaluator–optimizer: generate/evaluate loops pay off only with clear criteria, ideally deterministic checkers; cap rounds.
- Start with one prompt plus retrieval; add a pattern only when an eval shows the simpler system fails.
Suggested path through this lesson
The augmented LLM
Every pattern below is built from one block: an augmented LLM — a model call with retrieval (9.1 Why RAG, and the canonical pipeline), tools (10.2 Tool use and function calling) and some memory (10.4 Memory and context engineering). The patterns differ only in how calls are wired together and who owns the control flow. Anthropic's Building effective agents named five workflow patterns plus the autonomous agent; they map almost one-to-one onto what frameworks like LangGraph, the OpenAI Agents SDK and the Claude Agent SDK make easy.
In the sketches, llm(prompt, **kw) is a call to your model API that returns text (or, with schema=…, a parsed structured output, 9.6 Generation side: context assembly, citations, structured outputs and tool calling).
1. Prompt chaining
Decompose a task into a fixed sequence of steps where each call processes the previous output, with programmatic gates in between.
def write_marketing_copy(brief: str) -> str:
outline = llm(f"Write a 5-bullet outline for: {brief}")
if not passes_checks(outline): # gate: cheap deterministic check
outline = llm(f"Fix this outline so it has exactly 5 bullets:\n{outline}")
draft = llm(f"Expand the outline into 200 words of copy:\n{outline}")
return llm(f"Translate to Spanish, keeping the tone:\n{draft}")
Use when the task decomposes cleanly and each step is easier than the whole. Cost: number of steps × one call, but each call is small and accurate. Failure mode: error propagation — a bad step 1 poisons steps 2–3. Guardrail: validate between steps (schemas, regexes, unit checks), and keep the chain short.
2. Routing
Classify an input and dispatch it to a specialized prompt, tool set or model.
ROUTES = {"refund": handle_refund, "technical": handle_technical, "other": handle_general}
def route(ticket: str) -> str:
label = llm(f"Classify this support ticket as refund|technical|other:\n{ticket}",
schema={"type": "string", "enum": list(ROUTES)})
return ROUTES[label](ticket)
Use when inputs fall into categories that are best handled differently — including sending easy queries to a cheap, fast model and hard ones to a frontier model. Failure mode: misrouting, and the "no good route" case. Guardrail: a default route, confidence thresholds, and routing accuracy measured on labeled examples (3.5 Evaluating models: metrics, validation and the bias–variance trade-off).
3. Parallelization
Two flavors. Sectioning splits independent subtasks and runs them concurrently. Voting runs the same task several times and aggregates.
from concurrent.futures import ThreadPoolExecutor
def review_code(diff: str) -> dict:
prompts = {"security": f"Find security issues in:\n{diff}",
"perf": f"Find performance issues in:\n{diff}",
"style": f"Find style issues in:\n{diff}"}
with ThreadPoolExecutor() as pool: # sectioning
results = dict(zip(prompts, pool.map(llm, prompts.values())))
votes = [llm(f"Is this diff safe to merge? yes/no:\n{diff}") for _ in range(5)]
results["merge_vote"] = sum(v.strip().lower().startswith("yes") for v in votes) / 5 # voting
return results
Use when subtasks are independent (sectioning) or when confidence matters more than cost (voting; a guardrail model running alongside the main model is sectioning too). Cost: tokens but wall-clock time of one call. Failure mode: conflicting partial results that someone must reconcile; voting on tasks where all samples share the same bias. Guardrail: a merge step with explicit rules; diversify prompts for votes.
4. Orchestrator–workers
A central model dynamically decomposes the task, delegates subtasks to worker calls (often with their own tools and clean contexts), and synthesizes. Unlike parallelization, the subtasks are not known in advance.
def orchestrate(task: str) -> str:
plan = llm(f"Break this task into independent subtasks with precise instructions:\n{task}",
schema={"type": "array", "items": {"type": "string"}})
with ThreadPoolExecutor() as pool:
results = list(pool.map(lambda sub: run_agent(sub), plan)) # workers = bounded agents (10.2)
return llm("Synthesize these results into one answer:\n" +
"\n\n".join(f"Subtask: {s}\nResult: {r}" for s, r in zip(plan, results)))
Use when the number and nature of subtasks depend on the input: multi-file code changes, research that spans many sources (10.6 Multi-agent systems). Cost: high — plan + workers + synthesis, often 10–15× a chat turn. Failure mode: vague delegation (workers duplicate or miss work) and lost context between orchestrator and workers. Guardrail: make the orchestrator write detailed task specs (objective, output format, boundaries, tools), bound each worker's budget, and let the orchestrator see worker summaries, not raw transcripts.
5. Evaluator–optimizer
One call generates, another evaluates against explicit criteria, and the loop repeats until the evaluator passes or a budget is spent.
def refine(task: str, max_rounds: int = 3) -> str:
draft = llm(task)
for _ in range(max_rounds):
verdict = llm(f"Evaluate this against the criteria: {CRITERIA}\nOutput PASS or a list of fixes.\n\n{draft}")
if verdict.startswith("PASS"):
break
draft = llm(f"{task}\n\nPrevious draft:\n{draft}\n\nFix these issues:\n{verdict}")
return draft
Use when you have clear evaluation criteria and iteration measurably improves outputs — translation nuance, search refinement, code against tests (where the evaluator can be a real test runner, the strongest form, 10.3 Reasoning and planning for agents). Failure mode: the evaluator rubber-stamps or oscillates; cost grows with rounds. Guardrail: concrete rubrics, a round cap, and prefer deterministic evaluators (tests, linters, schema checks) whenever they exist.
6. The autonomous agent
When the task is open-ended and the number of steps cannot be predicted, you give the model tools and a goal and let it loop with environment feedback (10.1 What is an agent? Loops, autonomy and workflows vs agents). Use it when you trust the model's judgment on the task, can observe its progress, and can bound cost and blast radius. Everything in 10.9 Evaluating agents–10.11 Building production agents is about making this pattern safe.
Choosing a pattern
| pattern | control flow owner | parallel? | typical cost | best for | main risk |
|---|---|---|---|---|---|
| chaining | your code | no | steps × 1 | fixed decompositions | error propagation |
| routing | code + 1 classification | no | 1–2 calls | heterogeneous inputs, cost tiering | misrouting |
| parallelization | your code | yes | N× tokens, 1× time | independent parts; confidence via votes | reconciliation |
| orchestrator–workers | model (decomposition) | yes | 10–15× | input-dependent decomposition | vague delegation |
| evaluator–optimizer | your code (loop) | no | rounds × 2 | clear criteria, iterative gains | rubber-stamping |
| autonomous agent | model | sometimes | unbounded without caps | open-ended tasks with feedback | cost, safety, drift |
Intuition. Ask three questions: Is the decomposition known in advance? (yes → chaining/parallelization; no → orchestrator or agent). Are the parts independent? (yes → parallelize). Can something check the result cheaply? (yes → evaluator–optimizer with that checker). The answers pick the pattern.
Composition and the start-simple rule
Real systems compose: a router in front of chains; an orchestrator whose workers are evaluator–optimizer loops; a parallel guardrail beside everything. The discipline that keeps this sane is Anthropic's: start with a single prompt and good retrieval; add a pattern only when an evaluation shows the simpler version fails. Each added layer multiplies cost and adds a failure mode, so the eval set (10.9 Evaluating agents) is what licenses complexity — not the elegance of the diagram.
Where this shows up later
- Orchestrator–workers scaled up is the multi-agent system of 10.6 Multi-agent systems; the delegation-spec advice comes straight from Anthropic's research-system post.
- Evaluator–optimizer with real verifiers reappears in coding agents (10.8 Coding agents and computer use) and eval harnesses (10.9 Evaluating agents).
- Routing by difficulty and parallel guardrails are production cost and safety tools in 10.11 Building production agents and 10.10 Agent safety and security.
Common pitfalls (4)
- Building an orchestrator when the decomposition is always the same — that is a chain or a parallel map at a tenth of the cost.
- Voting with identical prompts on a task where every sample shares the same misconception; diversity, not repetition, gives signal.
- Letting the evaluator be the same vague prompt as the generator; without concrete criteria it passes everything.
- Delegating to workers with one-line instructions, producing duplicated or missing work.
Recap: all 7 key points
- Every pattern is wiring between augmented-LLM calls; the difference is who owns control flow and whether decomposition is known in advance.
- Prompt chaining: fixed sequence with programmatic gates — accurate per step, but errors propagate.
- Routing: classify then dispatch to specialized prompts/models — enables cost tiering; guard against misrouting with a default route and measured accuracy.
- Parallelization: sectioning (independent subtasks) or voting (confidence) — N× tokens at 1× latency; needs a reconciliation step.
- Orchestrator–workers: a model decomposes dynamically and delegates; expensive (10–15×) and sensitive to vague task specs.
- Evaluator–optimizer: generate/evaluate loops pay off only with clear criteria, ideally deterministic checkers; cap rounds.
- Start with one prompt plus retrieval; add a pattern only when an eval shows the simpler system fails.
Watch
The authors of the patterns on keeping systems simple and composable.
Read
Exercises
- Implement all five patterns
Using your 10.2
llmhelper, implement the five sketches on a single toy problem family (e.g. producing a product FAQ from a spec). Log calls and tokens per pattern. Expected: a table of cost multipliers roughly matching the one in the notes. - Routing with a cheap model
Label 50 mixed queries as easy/hard. Route easy ones to a small model and hard ones to a frontier model; compare quality and cost against frontier-only. Expected: most of the quality at a fraction of the cost; report routing accuracy.
- Evaluator–optimizer with a real checker
For 15 coding tasks, run generate → tests → fix for up to 3 rounds. Then replace the test runner with an LLM evaluator that cannot run code. Expected: the real checker converges more often; write down the rubber-stamp rate of the LLM evaluator.
Quiz
Flashcards
These cards are now in your spaced-repetition queue. Review all due cards →