Agent Workflow Patterns: Chaining, Routing, and Evaluator Loops
Introduction
Before building a fully autonomous agent, consider structured workflows where your code controls the flow. They are more predictable, cheaper to run, and easier to test. This lesson covers five common patterns and shows how to implement three of them with a small helper function.
Prerequisites
- The agent loop lesson
- A helper that calls the LLM and returns text
Step-by-Step Guide
Step 1: Start with a tiny helper
Every pattern below is built from plain calls to a helper like this one.
import anthropic
client = anthropic.Anthropic()
MODEL = "claude-opus-5-5"
def ask(prompt: str, system: str | None = None) -> str:
kwargs = {"system": system} if system else {}
response = client.messages.create(
model=MODEL,
max_tokens=16000,
messages=[{"role": "user", "content": prompt}],
**kwargs,
)
return next(b.text for b in response.content if b.type == "text")Step 2: Prompt chaining
Split a task into a sequence where each step's output feeds the next. Each call has a narrow job, and you can add checks between steps. Use it when a task decomposes into fixed stages.
notes = "..." # raw meeting notes
outline = ask("Create a five-point outline for a blog post from these notes:\n" + notes)
draft = ask("Write the post following this outline:\n" + outline)
final = ask("Edit for clarity and trim to 300 words:\n" + draft)Step 3: Routing
Classify the input first, then send it down the path best suited to it, each with its own prompt or even a different model. This separates concerns and lets you use cheaper models for easy categories.
category = ask(
"Classify as billing, technical, or general. Reply with one word:\n" + message
).strip().lower()
SYSTEMS = {
"billing": "You are a billing specialist. Be exact about amounts and dates.",
"technical": "You are a support engineer. Give step-by-step fixes.",
"general": "You are a friendly support agent.",
}
reply = ask(message, system=SYSTEMS.get(category, SYSTEMS["general"]))Step 4: Evaluator-optimizer loop
One call produces a draft, another critiques it against clear criteria, and the draft is revised until it passes or a limit is reached. It works when you can state what good looks like and revision measurably improves the result.
criteria = "Under 100 words. Friendly tone. States the refund timeline."
draft = ask("Write a reply to this refund request:\n" + request)
for _ in range(3):
review = ask(
"Reply PASS if the text meets every criterion; otherwise list the problems.\n"
"Criteria: " + criteria + "\nText:\n" + draft
)
if review.strip().upper().startswith("PASS"):
break
draft = ask("Revise to fix these problems:\n" + review + "\nText:\n" + draft)Step 5: Know parallelization and orchestrator-workers
Parallelization runs independent calls at the same time, either to split work into sections or to take several attempts and compare them. In orchestrator-workers, one model breaks a task into subtasks that are not known in advance, hands them to worker calls, and combines the results. Use these when the work is naturally splittable, and move to a full agent only when the steps genuinely cannot be predicted.
FAQs
- When should I move from a workflow to an agent?
- When you cannot enumerate the steps in advance and the task needs the model to decide dynamically what to do. If you can draw the flowchart, build the workflow.
- Does adding steps always improve quality?
- No. Each step adds cost and latency. Add a step only when testing shows it fixes a real failure.
Summary
Chaining, routing, parallelization, orchestrator-workers, and evaluator loops cover many real tasks with more control than an autonomous agent. Prefer them when you can describe the steps, and measure whether each one earns its cost.
Comments
Log in or create a free account to join the discussion.