Five ways to wire a model into a system. Tonight we implement all five on the same task and measure every one on screen, so the choice stops being a matter of taste. The dearest costs six times the cheapest, and nobody picks that on purpose.
Ask a team what they're building and you'll hear "an agent". Ask which steps it takes and most of them can list the steps, in order, on a whiteboard, before any request has arrived.
"It's an agent." Autonomy, tools, a loop, a plan. The word does a lot of work in a room where nobody wants to sound behind.
Three prompts in a fixed order with a retry. Which is fine. It's a workflow, it's cheaper, it can be load tested, and calling it an agent costs them the ability to reason about it.
An agent is one you cannot. That single distinction predicts everything downstream: whether you can forecast the bill, whether you can cap the latency, whether a load test means anything, and whether you can write the runbook before the incident.
The names are the common vocabulary now, and using the common names matters more than the names being perfect. The point is being able to say "that's an orchestrator-workers shape" and have the room know what you mean.
flowchart TB A["Chaining
step N+1 needs step N's answer"] --> B["Routing
inputs are different kinds of thing"] B --> C["Parallelisation
pieces don't need to see each other"] C --> D["Orchestrator-workers
the model picks the subtasks"] D --> E["Evaluator-optimiser
you can write the bar down"] style A fill:#EDE9FE,stroke:#0D1B33,stroke-width:2px style B fill:#EDE9FE,stroke:#0D1B33,stroke-width:2px style C fill:#EDE9FE,stroke:#0D1B33,stroke-width:2px style D fill:#FEF3C7,stroke:#0D1B33,stroke-width:2px style E fill:#DCFCE7,stroke:#0D1B33,stroke-width:2px
One support ticket. One call. One prompt. 120 tokens, 169 milliseconds. This is the row every other pattern is competing against, and a surprising number of production systems lose to it on every axis at once.
npm run patterns
Look at that table again. Parallelisation ran three branches at once and still took 697ms against chaining's 643ms, for 58% more tokens. The pattern whose entire job is to be faster lost to the pattern whose latency is additive by definition.
Three calls at once beats three calls in a row. Obviously.
Three branches, then a merge call that has to wait for all of them. At N=3 you're dominated by the one call you still have to make afterwards.
npm run race
await in a loop versus Promise.all.The meter in this repo reports two different things, and the difference between them is the pattern.
The sum of every call's duration. This is the bill. It does not change when you parallelise, and it should not.
First call starts to last call ends. This is the wait. It is the only thing concurrency moves.
Parallelisation and orchestrator-workers look identical on a diagram. In parallelisation you wrote the branches before the request arrived. In orchestrator-workers the model writes them, per request.
That is a real capability, and it costs you the ability to forecast anything.
flowchart LR
R["request"] --> P{"who decides
the subtasks?"}
P -->|"you, at design time"| W["workflow
forecastable bill
latency you can cap
load testable"]
P -->|"the model, at run time"| A["agent-shaped
bill is unknown
latency is unknown
load test means little"]
style W fill:#DCFCE7,stroke:#0D1B33,stroke-width:2px
style A fill:#FEE4E2,stroke:#0D1B33,stroke-width:2px
style P fill:#FEF3C7,stroke:#0D1B33,stroke-width:2px
export const MAX_WORKERS = 4;
export function planToSubtasks(plan: string): string[] {
const tasks = parse(plan);
return tasks.slice(0, MAX_WORKERS); // <- the whole slide
}
Without that line the number of workers is whatever the model felt like. A $0.002 request becomes a $2 one on an input nobody tested, and it will be a real customer's input, on a Friday.
The critique loop is the best pattern on this list when it fits: translation against a style guide, code against tests, copy against a brand voice. It fits when a model can apply a bar more reliably than it can hit it first time.
When the bar isn't written down, what you have is a loop that costs money and terminates on a token budget. The stopping condition is the design, not a detail.
isAcceptable returns true when it can't tell. Erring toward stopping is the safe direction.
The cost of shipping one mediocre draft is bounded. The cost of a loop that won't terminate is not.
npm run choose
Five real-shaped systems through one ordered set of questions. Cheapest shape first, and every pattern has to earn its place by naming what it buys. An agent is last, and it is gated on cost, not on capability.
Steps unknowable. Needs to act. Unbounded cost accepted.
→ an agent
Steps unknowable. Needs to act. Unbounded cost not accepted.
→ orchestrator-workers, with a hard cap
Identical engineering problem. The thing that flipped the answer was money, and money was never discussed in the design review.
npm install
npm run patterns # all five, measured, one ticket
npm run race # serial vs concurrent, at two widths
npm run choose # the rule
Zero runtime dependencies, 20 tests, typecheck clean. If you only run one, run race, because it's the one you just watched and it's the one you can point a colleague at.
Not "does it use tools". Not "does it have a loop". Just: could you write its steps on a whiteboard this afternoon, without knowing what the user is going to ask?
The repo is public and MIT. Run race first and read the README's caveat section before the code.
Then reply and tell me about a system on your team that genuinely cannot be drawn in advance. I read every reply, and those are the interesting ones, because the honest agent case is much rarer than the word suggests.