
The default answer to a software problem is increasingly an agent. A user describes a goal. The model decides what to do, calls a tool, looks at the result, and decides again.
That loop is a real architecture. For some work it is the right one. Often it is autonomy dropped onto a sequence that was already known.
I have a rule I use before I reach for an agent.
Do not start by asking how many agents you need. Start by asking how much of the path is still unknown.
Give a model a document and ask it to extract structured fields. Input goes in. One call happens. JSON comes back. That is an LLM-powered feature. The stages were scheduled before the call ran.
Now take a different task: investigate why this deployment failed and name the most likely cause. The system may need to inspect a deploy, read logs, search docs, compare config, notice a gap, go get more evidence, and change its mind. The next action depends on what the last action returned. That is the thing that makes a system agentic.
The difference is not how impressive the model is. It is whether you can name the path before execution starts.
Vendors draw the same line in their own words. Anthropic treats workflows as LLMs and tools orchestrated through predefined code paths, and agents as systems where the model directs the process. OpenAI's product split is even more blunt: use the Responses API when you want to own the loop, and the Agents SDK when you want the runtime to run it.
Those are two names for one question. Who chooses the next step?
When I design an AI feature, I start with the most deterministic version that could solve the problem.
Caption: A model sits inside a sequence the application already knows. If this path works, an agent loop is optional, not implied.
If that works, I do not need an agent.
Every stage the application owns is a stage you can test, time, log, retry, and refuse. Predictability. Cheaper runs. Failures that look like software failures instead of a story you have to reconstruct. I only want to give those properties up when the unknown path is the actual problem.
A common mistake is treating tool calling as agency. It is not.
OpenAI's function-calling flow has the model propose a call, and your application execute the code. Receive the tool call. Run it yourself. Send the result back. Repeat if you want. The model never has to own sequencing, permissions, or retries.
You can keep even more of that in code:
Caption: The model participates in the workflow without owning it. Code still decides which tool runs.
The application retains control over permissions, order, retries, and business rules. The model handles the parts where language or interpretation is useful. Sometimes that is exactly the split you want.
An agent becomes interesting when I can define the goal and cannot reliably define the sequence.
Research is the clean example. The first search decides the second. One source contradicts another. Something is missing. A different tool is the only way forward. The loop exists because the system is responding to information it could not completely predict before it started.
Caption: The loop is the architecture. It is justified only when observation can change what should happen next.
Incident investigation has the same shape. So does any task where "what to look at next" is the work. If you can write the steps on a whiteboard before the first tool call, you are looking at a workflow with a model in it, not an agent.
Once one agent works, the temptation is to split the job the way a company splits a job. Research agent. Planning agent. Coding agent. Reviewer. Manager. Now the diagram looks like an org chart.
That does not mean the system got better.
Every agent is another probabilistic boundary. Agent A's output is agent B's input. Information drops. Instructions get reread. Agents disagree. Context gets copied. Latency and tokens go up. Debugging means reconstructing a conversation between components that do not share a debugger.
Specialization can be real. Analyzing a large codebase in parallel is a decent case: one pass on the frontend, one on infrastructure, one on tests, a controller collecting results and deciding what needs a second look. The work has actual boundaries. If three agents need almost the same context and wait on each other continuously, you paid for coordination and got a slower single agent.
I usually do not want those agents talking to each other until they are done. I want a controller, bounded tasks, and state that is explicit.
Caption: The controller owns the workflow. Agents do bounded work. Outputs can be validated before they become someone else's input.
The controller does not need to be another model. Often it is a loop, a state machine, or a workflow engine. The deterministic parts should stay deterministic.
Every time a model decides what happens next, the number of possible traces grows. That flexibility is the feature. It is also why a wrong answer is hard to explain.
A five-stage workflow has five known places to look. An agent might call one tool once, another five times, skip a step you expected, retry something you did not, or decide it needs a source you never listed. Without a trace you are staring at a final sentence and inventing a history for it.
This is no longer a private preference. OpenTelemetry's GenAI conventions record invoke_agent, chat, and execute_tool spans, plus model name, token counts, and finish reasons. OpenAI's Agents SDK ships tracing for the same reason: once the runtime owns the loop, you need a picture of what the loop actually did.
I want to know what the model decided, which tool it picked, what arguments went out, what came back, how many iterations ran, where the final claim got its evidence, how much the run cost, and how long it took. If you cannot answer those, you do not have an agent you can operate. You have a demo.
I like using models where interpretation is useful and ordinary code where correctness is required.
The model can decide that a request looks like a refund. The application should decide whether this user is allowed to issue one. The model can propose parameters. The application can validate them against a schema and a policy. The model can select an action. The application can enforce rate limits, allowlists, and "this tool does not run without approval."
That split gets more important as the tools start changing real systems. OpenAI's own guidance is that the model can still decide an action is needed, and the run should pause until your application or a human approves it. LLMs are good inside ambiguity. Authorization rules should not be.
There is a quiet hierarchy in how these systems get discussed. A function looks naive. A single model call looks like a feature. An agent looks advanced. Multi-agent looks like a platform.
I do not think that ranking is useful. The best architecture is the smallest one that reliably solves the problem.
| Shape | What is known before the run | Who chooses the next step | Reach for it when |
|---|---|---|---|
| A function | The whole path | Code | No interpretation is required |
| Model inside a pipeline | The stages, not the wording | Code | Language or structure is hard, sequence is not |
| App-owned tool dispatch | The tools and the permission rules | Code, after a model suggestion | You need tools without giving away control |
| Agent loop | The goal and the tool set | The model | The next step depends on what just happened |
| Controller plus bounded agents | The boundaries of the work | Code for routing, models inside the bounds | The work is genuinely separable |
Before I introduce an agent loop I try to answer one question:
What decision can this system not reasonably make ahead of time?
If I cannot name that decision, I probably do not need an agent. If the workflow is a sequence of known steps, code can orchestrate it and the model can sit inside the steps where reasoning helps. If the next step genuinely depends on information discovered during execution, then giving the model some control over the loop starts to make sense.
The goal is not to make software behave more like an autonomous system. The goal is to solve the problem with the least amount of uncertainty the problem actually requires.
Sometimes that requires an agent. A lot of the time, it does not.
Thanks for reading.
More writing