Top 1% Engineers, issue 1
AI agents vs workflows: make the workflow your default
AI agents vs workflows is the first call in any AI system. What each choice costs, how it fails, and the code for the safe default.

Who decides what runs next sets the bill
Choose before you write a prompt: your code, one agent, or a team of agents.
- WorkflowYour code picks each step, so you test it like software
- Single agentThe model picks each step, at about 4× chat's tokens
- Several agentsAbout 15× chat's tokens; helps only when work splits
- Evals and gatesRerun every case and cap the loop before giving more control
Who decides in the system you are building now?
Top 1% Engineerslearnwithparam.com/newsletter
Should this be an agent? It is the first design question in any AI feature, and I think too many teams let their framework answer it for them. When τ-bench was published, Sierra tested 12 agents on customer service tasks, and even the best stayed under 50% average success1.
Whoever decides what runs next, your code or the model, sets what the system costs, how it fails and how you debug it. That is the whole of AI agents vs workflows.
If your code decides, you have a workflow. If the model decides, you have an agent. If one model hands work to other models, you have a multi-agent system.
Make the workflow your default and let the agent earn its place
The workflow is the default because the difference is one of control, not intelligence. Anthropic draws the line in two sentences: workflows are systems where models and tools are orchestrated through predefined code paths2, and agents are systems where the model directs its own process and tool use2.
Follow the support ticket through all three. In the workflow, one model call labels the ticket and a plain match statement picks the next step, so billing, bug and account tickets each take a fixed path. In the single agent, the model chooses which tool to call next and when to stop. With several agents, a lead model also chooses which other models to wake.
Anthropic's advice, from building with customers, is to find the simplest solution possible and only add complexity when needed2. The most successful implementations they saw used simple, composable patterns rather than complex frameworks2. OpenAI's guide makes the same call one level up: maximize a single agent's capabilities first3.
The agent earns its place where the path cannot be written down. Anthropic reserves agents for open-ended problems where the number of steps is hard or impossible to predict and a fixed path cannot be hardcoded2. In the ticket system only a refund dispute fits: the agent may need the order, then the payments, then the delivery record, in an order no rule predicts.
The router is where you keep that boundary honest. Structured outputs guarantee schema-compliant responses through constrained decoding4, except, the same docs warn, on a refusal or a max_tokens cutoff. So the code checks stop_reason first, hands those to a person, and the match only sees labels your code knows.
triage.pyfrom typing import Literal
from pydantic import BaseModel
from settings import client, MODEL
from routes import billing_reply, file_bug, account_reply
from refund_agent import refund_agent
from handoff import hand_to_person
class Label(BaseModel):
kind: Literal["billing", "bug", "account", "refund_dispute"]
summary: str
def classify(ticket: str) -> Label | None:
response = client.messages.parse(
model=MODEL,
max_tokens=1024,
system="Label this support ticket and summarise it in one sentence.",
messages=[{"role": "user", "content": ticket}],
output_format=Label,
)
if response.stop_reason != "end_turn": # a refusal or a max_tokens cutoff
return None
return response.parsed_output
def handle_ticket(ticket: str):
label = classify(ticket) # the model judges inside one step
if label is None:
return hand_to_person(ticket, [])
match label.kind: # your code picks the next step
case "billing":
return billing_reply(ticket, label)
case "bug":
return file_bug(ticket, label)
case "account":
return account_reply(ticket, label)
case "refund_dispute":
return refund_agent(ticket, label) # the one step the model steersThe same split decides how you debug it. Each fixed case is a function you can unit test alone, retry as a single step, and explain to an auditor from its input and output. The refund agent is the one branch whose path is chosen at run time, so it is the one you debug by reading its trace.
When someone proposes another agent step in review, ask which case it replaces and which real ticket showed the fixed path failing.
Every decision you hand the model is paid for in tokens
Each decision you hand the model is another call that rereads the context so far, and you pay for every call in tokens. In Anthropic's data, from its own workloads, agents use about 4× more tokens than chat and multi-agent systems about 15× more5.
- One chat exchange1×
- A single agent4×
- Several agents15×
On BrowseComp, a benchmark of browsing agents finding hard-to-find facts, token usage by itself explained 80% of the variance in performance, with tool calls and model choice as the other factors5. More tokens meant more searching, and on a browsing task, breadth is the job.
Anthropic states the trade plainly: agentic systems often trade latency and cost for better task performance, and you should consider when that makes sense2. For a router that runs on every ticket all day, I think it rarely does. For the few refund disputes a person would otherwise untangle by hand, it can.
The multipliers are Anthropic's, so price one of your tickets before you build the agent step. Count the model calls on the happy path of each route, multiply by the context each call carries, and weight by how often each route fires. The fixed routes cost a classify call and a drafting call. The refund agent costs whatever its loop runs to, so the loop gets a cap, and the cap is its price ceiling.
Tools set the size of each turn. Anthropic restricts tool responses in Claude Code to 25,000 tokens by default6. Your own API loop has no limit until you add one, so truncate each tool result yourself. When a call fails validation, Anthropic suggests error messages that say what to fix rather than opaque codes6.
Cap the turns as well. When the cap is hit, the ticket goes to a person instead of the loop running on, so the worst dispute has a price you wrote down.
An agent that succeeds once has not shown it will succeed twice
One successful run says little about the next, yet one run is the first measurement most teams take. Sierra built τ-bench as a simulated customer with real tools and a policy to follow. From deploying support agents, it found that measuring reliability well is critical to deployment1. When τ-bench was published, its best agent fell to about 25% on pass^8 in retail, a 60% drop from its pass^1 score1.
One try asks whether the agent can do the task. pass^k asks whether it does the task every time: Anthropic defines it as the probability that all k trials succeed7.
42% chance an agent passes all three of three trials
75% chance a single trial passes
Anthropic says the metric especially matters for customer-facing agents, where users expect reliable behavior every time7. Each customer meets a single run. A green demo is one try too, and it says little about the refund dispute your agent gets wrong on its fourth. Anthropic also notes that minor issues for traditional software can derail agents entirely5, and in the refund loop one wrong order lookup becomes the premise for every step after it.
The fix is a test that reruns real failures. Anthropic's evals team writes that 20 to 50 simple tasks drawn from real failures is a great start7.
test_refund_agent.pyimport json
import pytest
from triage import handle_ticket
RUNS = 8 # pass^8: a case passes only if every run passes
# Each line: a ticket the agent once got wrong, and the action a person says was right.
CASES = [json.loads(line) for line in open("cases/refund_failures.jsonl")]
@pytest.mark.parametrize("case", CASES, ids=lambda c: c["id"])
def test_passes_every_run(case):
actions = [handle_ticket(case["ticket"]).action for _ in range(RUNS)]
wrong = [a for a in actions if a != case["expected_action"]]
assert not wrong, f"{len(wrong)} of {RUNS} runs chose {wrong[0]}"The assert counts a case only when every run passes, so a flaky case fails the build instead of averaging into a green number. Run it before you widen the agent's tools and after every prompt change. Hamel Husain led the team behind CodeSearchNet and has watched many LLM products succeed and fail. The failed ones, he finds, almost always share one root cause, no robust system for evaluating them8, and he puts error analysis first:
The alternative? Error analysis - the single most valuable activity in AI development and consistently the highest-ROI activity.
A second agent helps only when the work splits cleanly
A second agent helps only when the work splits cleanly, as in Anthropic's multi-agent system, where a lead model with subagents beat a single agent by 90.2% on its internal research eval5. That gain is on Anthropic's own task and was bought with heavier token spend. Since token use explains most of the variance in performance5, we think part of it comes from the extra tokens rather than the split alone. Research also splits well: many independent searches, each returning a short summary to the lead.
better than a single agent on Anthropic's internal research eval, where subagents search in parallel
The same team names where splitting fails. Domains where all agents must share the same context, or where agents depend on each other heavily, are not a good fit today5. Most coding tasks have fewer truly parallel parts than research, and agents are not yet great at coordinating and delegating to each other in real time5.
Cognition, which builds a coding agent, reached the same place from the other side. Its principle is that actions carry implicit decisions, and conflicting decisions carry bad results10. Its answer is one single-threaded linear agent, where the context is continuous10.
While I’m optimistic about the long-term possibilities of agents collaborating with one another, it is evident that in 2025, running multiple agents in collaboration only results in fragile systems.
A Berkeley-led team measured how this goes wrong. From a close analysis of a smaller set of traces they defined 14 failure modes, grouped as system design, misalignment between agents, and task verification11. They then applied them to a dataset of more than 1600 multi-agent traces11. Their summary is blunt: despite the enthusiasm, gains on popular benchmarks are often minimal11.
Back to the ticket. A refund dispute needs the order history and the payment record, and the decision depends on both at once. Split them across two agents and each decides with half the facts, which is Cognition's conflict in miniature. One agent with both tools keeps one context.
Before you add an agent, write down what each one owns, what it hands back and what it never sees. If the handoff needs the whole context, you have one agent with extra steps and a larger bill. When the list comes out clean, as it does for research, that list is the build.
Control sets the cost and the failure mode, whatever the model
However good the model, who picks the next step sets the bill, the failure mode and how you debug it. Model choice matters, and Anthropic counts it among the factors behind performance5, but a better model does not change where a wrong step can go. I think the reason is that a model is a probability over actions, and only a gate outside the model holds whatever the prompt says.
So the agent step gets gates that live in code. OpenAI's guide says to set limits on agent retries or actions3, and names high-risk actions such as canceling user orders, authorizing large refunds or making payments3. Refunds are one of those actions, so a person approves every refund the agent proposes.
- Gate: schemaLabel checkThe router accepts only the ticket kinds your code knows
- Gate: loopTurn capThe agent stops after a fixed number of turns and hands off
- Gate: loopTool allowlistOnly order, payment and proposal tools run
- Support leadRefund approvalA person approves every refund the agent proposes
None of those gates asks the model to behave; each one holds in code whatever the model does. Cognition's warning10 and the Berkeley failure taxonomy11 point the same way: a team of agents built because it looks like good engineering moves the bugs into the handoffs. Start with the workflow, measure it, and promote one decision at a time to the model when a real case shows the fixed path breaks. When the work behind that case splits cleanly, build the team, with every handoff written down and checked in code.
What to do on Monday
- Draw the steps. For your next AI feature, write one line per step saying whether your code or the model picks what runs after it.
- Price one request. Count the model calls on the happy path, multiply by the context each one carries, and weight by how often the route fires.
- Cap the agent step. Give it a turn limit, an allowlist of tools, and a person for anything that pays, refunds or deletes.
- Rerun every case. Turn real failures into cases, run each several times, and count only the cases that pass every run.
Build it with me: the Free Live Lab Build a Team of AI Agents, live on Thursday, October 29.
For the feature you are building now, who decides what runs next: your code or the model?
Param
Sources
- Sierra, τ-Bench: Benchmarking AI agents for the real-world, 2024
- Anthropic, Building effective agents, 2024
- OpenAI, A practical guide to building agents, 2025
- Claude docs, Structured outputs, 2026
- Anthropic, How we built our multi-agent research system, 2025
- Anthropic, Writing effective tools for agents, 2025
- Anthropic, Demystifying evals for AI agents, 2026
- Hamel Husain, Your AI Product Needs Evals, 2024
- Hamel Husain, A Field Guide to Rapidly Improving AI Products, 2025
- Cognition, Don’t Build Multi-Agents, 2025
- Cemri et al., Why Do Multi-Agent LLM Systems Fail?, 2025