A support-triage agent is the canonical first project: hand Claude the inbox, a few tools, and the instruction to sort tickets and draft replies. It demos beautifully. In production it does things the demo never showed you — re-reads a ticket it already read, calls the refund API on a billing question because the API was in scope, spends nine model round-trips on what a switch statement does in one.
None of that is a malfunction. The agent is doing exactly what you built it to do: given a goal and a set of tools, it decides its own steps from scratch, every time. The trouble is that triage doesn't need decided steps — it has known ones. You reached for an agent when the task was a workflow.
What separates a workflow from an agent
The difference is not size or sophistication. It is who decides what happens next.
In a workflow, you decide. The steps live in your code — fetch, classify, route, draft — and the model supplies the fuzzy judgment inside a step (which category is this ticket?) while the control flow stays yours. In an agent, the model decides. You give it tools and a goal; it chooses each step at runtime and loops until it judges the task complete.
Triage has a fixed shape: read, categorize, route, reply. You know those steps before a single ticket arrives. Building it as an agent asks the model to rediscover that shape on every ticket, and it will — slightly differently each time. That variability is what you paid for flexibility you did not need.
Why an agent's loop is expensive
An agent's capability and its risk come from the same mechanism: the loop. The model reasons, calls a tool, reads the result, and reasons again, repeating until it decides to stop.
For open-ended work, that loop is the point. But every turn is another chance to go wrong, and the errors accumulate: a small misreading on the third turn becomes a confident mistake on the seventh. Token cost rises with each turn, and because the path differs between runs, a failure you saw once can be hard to reproduce.
claude -p) usage on subscription plans draws from a separate monthly Agent SDK credit; budget it apart from interactive use, and authenticate production with an API key rather than a personal login.
So the question to settle before writing any code is concrete: can you list the steps in advance? For triage you can. When you can, you do not need an agent — you need one of five workflow patterns.
The five workflow patterns
These cover most multi-step work. They are ordered by how much judgment each hands to the model, so you take the first one that fits and stop.
- Prompt chaining — steps in sequence, each feeding the next, with an optional check between them. Draft a reply, then verify it before sending.
- Routing — classify the input, then send it to a handler built for that case. This is the shape triage needs: one model call to label the ticket, then your code selects the path.
- Parallelization — split a task into independent parts, or ask the same question several ways and combine the answers. Score a ticket for urgency, sentiment, and topic at once.
- Orchestrator-workers — a coordinator decides at runtime how to divide the work. The first pattern with real dynamism, for when the subtasks are not known ahead of time.
- Evaluator-optimizer — generate, check against criteria, and revise until it passes. Use it when quality must be reliable rather than hoped for.
Triage is a routing problem. That is the whole diagnosis: an agent was doing a router's job.
Building it
Here is the triage bot rebuilt as the router it should have been. Note where each decision is made:
# A workflow: the control flow is yours; the model handles only the fuzzy step.
category = await classify(ticket) # one scoped model call -> "billing" | "bug" | "how-to"
handler = ROUTES[category] # your code chooses the path, not the model
reply = await handler(ticket) # a handler written and tested for that case
The model never touches the routing decision and never sees the refund tool. It does the one thing it is good at — turning fuzzy language into a label — inside a path you control and can test.
When a task genuinely has no fixed shape, you still do not write the loop by hand. The Claude Agent SDK (Python and TypeScript; the same loop that runs Claude Code) runs reason-act-observe for you. You supply configuration, and the configuration is where your control lives:
from claude_agent_sdk import query, ClaudeAgentOptions # pip install claude-agent-sdk (Python 3.10+)
# allowed_tools is a boundary, not a default: a tool that is not listed cannot be called.
options = ClaudeAgentOptions(allowed_tools=["Read"]) # the refund API is simply absent
allowed_tools to exactly what the step needs, and bound the number of turns. An agent will use every tool and every turn you leave available to it.
The decision
The whole article reduces to one question.
Start with a single model call and measure; a surprising amount of "agentic" work is one good prompt. If that falls short and you can list the steps, choose the workflow pattern that fits — it is cheaper, faster, and reproducible. Only when the steps genuinely cannot be known in advance does an agent earn its cost, and even then it runs inside guardrails: scoped tools, bounded loops, explicit permissions.
Applied to triage: the steps are listable, so it is a routing workflow, not an agent.
What it comes down to
Teams that run LLM systems reliably are not the ones with the most autonomous agents. They are the ones who use a workflow wherever the steps are known and an agent only where they are not, treating autonomy as a cost to justify rather than a default to assume. The triage bot did not need to be smarter. It needed to be a router.
System design scenarios
Interview questions
The questions an interviewer actually probes on this topic, and what a strong answer covers.
allowed_tools to exactly what the step needs, capping turns, setting an explicit permission mode, and quarantining autonomy to the smallest step that requires it.