Skip to content

Real-World Examples

architecture-overview.md's decision tree gives the decision logic for picking a pattern. This page applies it to concrete scenarios end to end — one worked example per pattern, plus two cases where the tempting first choice turns out to be wrong. Each example follows the same shape: the scenario, the alternative that looks plausible at first glance, why the tree actually routes elsewhere, and the trade-off accepted by going with it.

One example per pattern

prompt_chaining — turning a draft into a published doc

A content team publishes long-form docs through a fixed pipeline: outline the structure, draft the body from the outline, edit for tone and length, then fact-check the final draft. Each step consumes the previous step's output directly, in a fixed order that never changes.

Tempting alternative: one big prompt asking for "a polished, fact-checked doc" in a single call. It saves round-trips, but a single call has to hold structure, prose quality, and factual accuracy in mind simultaneously — in practice it does all three worse than four calls that each focus on one job.

Why prompt_chaining fits: the number and order of steps are known up front, and each step strictly depends on the one before it — exactly the "fixed number of steps, linear dependency chain" branch of the decision tree.

Trade-off accepted: total latency is the sum of all four steps, and an early bad step (a bad outline) cascades downstream unless a gate catches it.

routing — support ticket intake

A support platform classifies incoming tickets into billing, technical, or account-closure, then hands each to a differently-tuned prompt with its own tone and its own tool access (billing tickets can look up invoices; technical tickets can search a knowledge base).

Tempting alternative: a single react_agent that reads the ticket and decides what to do turn by turn. It would work, but the "what kind of ticket is this" decision only ever needs to be made once, up front — every ticket in this system takes exactly one classify-then-handle path, not an open-ended trajectory.

Why routing fits: inputs fall into a fixed, known set of categories, each better served by a focused handler than one generic prompt — the tree's "different inputs need different handling" branch.

Trade-off accepted: misclassification is a silent failure with no automatic detection — a ticket routed to the wrong handler just gets a wrong-flavored response, not an error.

parallelization — content moderation

A moderation pipeline runs three independent checks on every submission — a safety check, a PII check, a tone check — then combines the three verdicts into one decision.

Tempting alternative: run the checks as a prompt_chaining sequence, one after another. Cheaper to write, but the checks don't depend on each other's output at all — chaining them only adds latency for no benefit.

Why parallelization fits: the same input goes through a fixed, known number of independent branches evaluated separately and combined afterward — the tree's "same steps, different independent aspects, fixed branch count" path.

Trade-off accepted: total cost is the sum of all three branches (not the max), and a partial branch failure has to be handled explicitly — there's no single call to just retry.

orchestrator_workers — repo-wide migration

A migration tool inspects a codebase, decides how many files actually need a particular change, and dispatches one worker per affected file.

Tempting alternative: parallelization — it looks identical from a distance (fan out, then fan in), but the branch count here isn't knowable until the model has actually inspected the repo. A parallelization graph needs its branch list fixed at graph-build time; this task can't supply one until runtime.

Why orchestrator_workers fits: the model decides the number of parallel subtasks at runtime rather than the graph fixing it in code — the tree's explicit distinction between parallelization and orchestrator_workers.

Trade-off accepted: cost and latency are only as predictable as the model's own subtask count — an unbounded fan-out needs an explicit cap.

evaluator_optimizer — SQL generation against a live schema

An internal analytics tool drafts a SQL query from a natural-language question, dry-runs it against the schema, and — if it errors — revises the query using the error message, repeating until it's valid or a retry cap is hit.

Tempting alternative: prompt_chaining — draft, then "clean up" the query in a second step. But a chain never loops back to fix a step based on that step's own failure; it only ever moves forward. This task specifically needs to revise against a concrete pass/fail signal (does the query execute), which is a cycle, not a line.

Why evaluator_optimizer fits: there's a clear, automatic evaluation criterion (does the dry-run succeed) and iterative refinement against it measurably improves the result — the tree's "no fixed step count, needs iterative refinement against explicit criteria" branch.

Trade-off accepted: up to 2 × max_iterations calls in the worst case, and non-convergence is a real failure mode if the generator and evaluator share a blind spot.

react_agent — a debugging assistant

A debugging assistant runs shell commands, reads their output, and decides the next command based on what it just saw — narrowing in on a root cause over an unpredictable number of turns.

Tempting alternative: orchestrator_workers — enumerate a set of likely causes up front and dispatch a worker to check each one in parallel. This fails because the next useful command depends on what the previous command's output revealed; there's no static task list to hand out at the start, only a decision that has to be made turn by turn.

Why react_agent fits: the number and order of steps genuinely can't be known in advance, and the model has to decide what to do next based on intermediate results — the tree's "model must decide, turn by turn" branch, with read-only tools only.

Trade-off accepted: no compile-time bound on turns, cost, or latency — p99 is fundamentally less predictable than any fixed-shape workflow above.

human_in_the_loop — the same assistant, with a deploy tool

Take the debugging assistant above and give it one more tool: it can actually push the fix it found. That single tool call — the one with a real, hard-to-undo consequence — is gated behind human approval; every read-only investigation step before it runs exactly as it did in the plain react_agent version.

Tempting alternative, two directions: skip the gate entirely for speed — fast, until the one autonomous deploy that's wrong is exactly the irreversible mistake this pattern exists to prevent. Or gate every tool call, including the read-only investigation steps — safer-looking, but it kills the agent's autonomy for the 95% of steps that carry no real risk (see human_in_the_loop/README.md's own "Where not to use it" note on this exact failure mode).

Why human_in_the_loop fits: the agent has at least one tool with a real-world, hard-to-undo consequence — the tree's "calls tools with real-world side effects" branch, gating only that tool rather than the whole loop.

Trade-off accepted: latency becomes human-paced for the gated call, and a stuck, never-resumed approval blocks the run forever unless a timeout policy is added.

When the reasoning goes wrong

The rule of thumb in architecture-overview.md — prefer the most constrained pattern that still solves the problem — cuts both ways. Over-reaching for flexibility you don't need is one mistake; so is under-fitting a pattern that's outgrown its constraints.

Over-reaching: a team defaults to a full react_agent for a weekly report job. The job is, in practice, always the same three steps in the same order: pull last week's metrics, summarize them, format the summary as an email. Nothing about the task ever varies — but because it's "an agent task," it gets built as one. The result pays every cost of react_agent (unbounded latency, unbounded turns, three failure surfaces to test instead of one fixed path) and gets nothing back for it, because the flexibility an agent buys was never actually needed. prompt_chaining would have been cheaper, faster, and fully testable against three fixed inputs.

Under-reaching: a team builds a routing classifier for ticket type with a fixed set of branches — billing, technical, account-closure. Six months later, the branch list has grown to eleven, with a new one added by hand roughly every month as the product surface grows. That pattern of change is itself the signal: the branch set was never actually fixed, it was just small enough not to notice. Either orchestrator_workers (let the model decide how many categories apply to a given ticket, if handling per category stays a fixed shape) or react_agent (if the handling itself also needs to be open-ended) fits the system this has actually become better than continuing to hand-add branches to a routing table.

See also

  • architecture-overview.md — the decision tree and comparison table these examples apply. Use the tree to narrow down a pattern, use this page to sanity-check the reasoning against a concrete case.
  • harnesses-and-loops.md — how these patterns show up inside a real agent harness, beyond this repo's demo graphs.