The decision that quietly determines your token bill, your p99, and whether anyone can debug the 2am page: did the LLM follow your code path, or write its own?
A workflow is an LLM-powered system where you, the engineer, wrote the control flow: LLM calls and tools are orchestrated through predefined code paths. An agent is a system where the LLM directs its own process — it decides at runtime which tool to call next, when to stop, and how to recover. These are not two categories; they're the two ends of one autonomy spectrum, and almost everything good in production lives in the hybrid middle. The single sharpest test (from Anthropic's Building Effective Agents): can you draw the flowchart before execution? If yes, it's a workflow; if the path depends on what the model discovers along the way, it's an agent. The senior move is not "build the most autonomous thing" — it's the simplest thing that works, because every notch of autonomy you add trades away cost predictability, latency bounds, and debuggability.
This is the load-bearing first decision of any LLM system design, and it separates people who've shipped from people who've watched a demo.
The words first.
Step by step.
Remember this: Reach for the simplest pattern that works, and only buy autonomy when the path genuinely can't be known in advance.
Anthropic's framing (December 2024 onward) draws one line and puts everything on it. At the left: a single LLM call. Moving right, you add augmentations — retrieval, tools, memory — but the code still decides what happens when. That whole region is workflows: "systems where LLMs and tools are orchestrated through predefined code paths." Push further right and the model itself starts choosing the next step based on observations; now it "dynamically directs its own processes and tool usage." That's an agent.
The crucial reframe for an interview: agentic is not a binary badge, it's a dose. A system can be 95% scripted with one bounded autonomous step. The question is never "is this an agent?" but "how much of the control flow have I handed to the model, and what did I get for it?"
The flowchart test operationalizes this. If you can draw boxes and arrows that fully describe execution before you run it, you have a workflow — and that drawing is your test suite, your audit log, and your cost model. If the arrows can only be drawn after the run (because the model decided how many loops, which tools, in what order), you have an agent, and you've traded that drawing away.
Before anyone needs an agent, these five patterns (Anthropic's taxonomy) cover the overwhelming majority of production systems. Know all five cold.
pricing, refund, or returns. One cheap upstream classification, then a focused downstream prompt. Single entry point, high accuracy, because each handler does one thing well.Patterns 1–3 are unambiguously workflows. Patterns 4–5 are the on-ramp to autonomy: they introduce model-controlled loops. Anthropic's own guidance is blunt — most production systems shouldn't need to go beyond parallelization.
Name the symbols: p = probability a single step succeeds, n = number of sequential steps, P = probability the whole run succeeds end-to-end. For independent steps, P = p^n.
Work it. Say each step is a strong 99% reliable: p = 0.99.
P = 0.99^3 = 0.970 → ~97% of runs fully succeed.P = 0.99^10 = 0.904 → ~90%.P = 0.99^30 = 0.740 → ~74%.Drop per-step reliability to a still-decent 95% and the 10-step run is 0.95^10 = 0.599 — barely a coin-flip better than failing.
What it did to the data: it turned "each step is fine" into "the run usually breaks," purely by chaining length. This is the quantitative reason workflows (few, fixed steps) beat sprawling agent loops on reliability — and why bounding the number of agent steps is the highest-leverage knob you have.
Four concrete arguments, each one an interview-ready bullet:
Don't over-correct into "always workflow." Agents earn their keep on open-ended tasks where the solution path depends on discoveries: research, debugging, adaptive multi-tool problem-solving. If you cannot know in advance whether the task needs two steps or nine — if the next action must depend on the last observation — a fixed flowchart is a lie, and forcing one produces a worse system than an honest loop. The 2025+ tooling makes the loop better: Claude's interleaved thinking (beta header interleaved-thinking-2025-05-14 on Claude 4 / Opus 4.7+) inserts reasoning blocks between tool calls so the model reflects on intermediate results before choosing the next action — fewer wasted tool calls and hallucinated loops, at the cost of added latency.
In practice the winning architecture is a deterministic supervisor routing to bounded autonomous specialists: a scripted entry point (routing workflow) that hands well-scoped sub-problems to small agents which are free to loop within a fence (capped iterations, validated outputs, scoped tools). You get the audit trail and cost control of a workflow at the top, and the flexibility of an agent only where the task demands it. See /agents for how to build the autonomous specialists, and /system-design for wiring the supervisor.
Here's the same task — answer a customer-support query — built first as a routing workflow (you can draw the flowchart) and then the delta to make one branch agentic. Production-shaped: structured-output routing, a cheap model for the classifier, an iteration cap on the agent.
import os
from anthropic import Anthropic
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
# ---- WORKFLOW: routing. The flowchart exists before we run. ----
ROUTES = {
"pricing": "You are a pricing specialist. Answer only pricing questions.",
"refund": "You are a refunds specialist. Follow the refund policy exactly.",
"returns": "You are a returns specialist. Explain the returns process.",
"other": "You are a general support agent.",
}
def classify(query: str) -> str:
"""One cheap, schema-constrained call decides the branch."""
resp = client.messages.create(
model="claude-haiku-4-5", # small model: routing is easy + high-volume
max_tokens=64,
tools=[{
"name": "route",
"description": "Pick the handler for this support query.",
"input_schema": {
"type": "object",
"properties": {"label": {"type": "string", "enum": list(ROUTES)}},
"required": ["label"],
},
}],
tool_choice={"type": "tool", "name": "route"}, # force the schema -> ~99.9% valid
messages=[{"role": "user", "content": query}],
)
for block in resp.content:
if block.type == "tool_use":
return block.input["label"]
return "other"
def handle(query: str) -> str:
label = classify(query) # <-- predefined code path picks the branch
resp = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=512,
system=ROUTES[label],
messages=[{"role": "user", "content": query}],
)
return f"[routed: {label}] " + "".join(
b.text for b in resp.content if b.type == "text"
)
# ---- AGENT delta: ONE branch loops, the model picks tools, but we FENCE it. ----
def agentic_branch(query: str, tools: list, max_steps: int = 6) -> str:
"""The model directs its own process -- bounded by max_steps."""
messages = [{"role": "user", "content": query}]
for step in range(max_steps): # <-- the fence: cap the autonomy
resp = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason != "tool_use": # model decided it's done
return "".join(b.text for b in resp.content if b.type == "text")
# else: run the tools the MODEL chose, feed results back, loop again
results = [run_tool(b) for b in resp.content if b.type == "tool_use"]
messages.append({"role": "user", "content": results})
return "Hit step cap without resolving — escalating to human." # honest failure
if __name__ == "__main__":
print(handle("How much is the Pro plan annually?"))What to notice, and what an interviewer wants you to say about it:
classify → handle is a flowchart. You can unit-test every branch, compute the token cost (one Haiku call + one Sonnet call), and audit every path.tool_choice forces the schema. Claude's Structured Outputs (GA Nov 2025+) constrain generation to your JSON Schema, giving ~99.9% valid routing labels — no hallucinated branch, no KeyError. This is the production standard; never parse a free-text label with a regex.agentic_branch the for step in range(max_steps) loop is the single most important line. Unbounded, that loop is your 15x token spike. Bounded, it's a controlled experiment.| Dimension | Workflow | Agent | What changes at scale |
|---|---|---|---|
| Token cost / run | Fixed, pre-computable | 4–15x spike risk from exploration | Budget per-route; route cheap steps to Haiku |
| Latency (p99) | Bounded, low variance | High variance (loop depth varies) | Agent p99 dominated by max-step cap, not mean |
| Reliability | High; p^n with small n |
Degrades with trajectory length | A 10-step loop at 99%/step ≈ 90% end-to-end |
| Debuggability | Bisect a known flowchart | Reconstruct a unique trajectory | Step-level tracing becomes mandatory |
| Auditability | Every path testable pre-deploy | Non-deterministic | Regulated domains: keep control flow in code |
| Right tool for | Known, repeatable, high-volume | Open-ended, path-depends-on-discovery | Hybrid: deterministic supervisor + bounded specialists |
The prose that matters: at low volume, an agent's looseness is hidden by small numbers — the demo works, the cost is a rounding error. Scale exposes everything. The 8% per-run failure that was invisible at 50 runs/day is 80 angry tickets at 1,000 runs/day, each one a unique trajectory your on-call has to reconstruct. The token variance that was noise becomes a six-figure line item with a standard deviation finance can't forecast.
Three failure modes to name explicitly. Orchestrator misdecomposition: the coordinating model splits the task wrong, and every downstream worker does excellent work on the wrong subproblem. Synthesis hallucination: workers return correct pieces and the aggregator invents a confident, wrong summary. Silent loop creep: a refinement loop with no cap or no improving evaluator burns tokens converging on nothing. Mitigations are unglamorous and effective: cap iterations, gate steps with cheap deterministic checks, validate every structured output against a schema, and for long-running or restart-prone systems, reach for durable execution. Temporal's model — an event-history log replayed deterministically on restart, skipping already-completed activities, with at-least-once activity retries made effectively exactly-once via idempotency keys — turns "the box crashed at step 7" from a catastrophe into a no-op resume. LangGraph's checkpointing (Postgres/Redis backends, GA in v0.2+) brings the same pause/resume/recover to graph-shaped LLM systems. Evaluate durability early; in 2026 it's first-class, not an afterthought (/inference, /harness).
p^n math tells me trajectory length is likely the reliability culprit, so shortening and bounding the loop is the highest-leverage move.p^n. "Each step is 99%" feels safe until you chain 30 of them into a 74% run. Shorten and bound trajectories.tool_choice.Flashcard. Workflow = you wrote the control flow (LLM calls orchestrated through predefined code paths). Agent = the LLM wrote the control flow (it directs its own process at runtime). One spectrum. Test: can you draw the flowchart first? Default to the simplest pattern that works; buy autonomy only when the path can't be known in advance.
Next: /agents — build the bounded autonomous specialist that lives inside the hybrid you just designed.