DevelopmentSenior

AI Agents

An AI agent is a loop in which the model decides what to do next: it calls a tool, reads the result, and decides again, until it judges the task done. That single design choice — control flow moved from your code into the model — buys the ability to handle tasks you cannot specify in advance, and costs you determinism, a predictable bill and a bounded blast radius. This page covers the loop, the failure modes that only appear in one, and how to tell whether you need an agent at all.

Control flow
Decided by the model
Cost per task
Unknown in advance
Must be bounded
Steps, tokens, permissions

The loop, and what it costs

Mechanically an agent is small. You describe a set of tools — each a name, a description and a parameter schema — and send them with the user’s request. The model answers either with text or with a request to call one of the tools and the arguments to use. Your code runs the tool, appends the result to the conversation, and calls the model again. That repeats until the model answers with text instead of a tool call, or until you stop it. There is no planner and no state machine: the loop is the architecture.

An agent is a loop in which the model holds the control flow. It answers either with text or with a request to call one of the tools you described; your code executes that tool, appends the result to the conversation, and calls the model again, until it answers with text instead of a call. Two consequences only appear in production. Every pass resends all previous results, so cost per step climbs as the run goes on. And every step must be right for the outcome to be right, so a step that is right ninety-five per cent of the time is right about sixty per cent of the time after ten steps. Nothing inside the loop limits it, so step caps, token budgets, least-privilege tools and a person in front of anything irreversible have to be enforced by your own code.

Two things about that loop are not obvious until you run one. The conversation grows on every iteration, because each tool result is appended and everything is resent — so step twelve carries all eleven results before it, and cost per step rises as the task goes on. And accuracy compounds downward: a step that is right ninety-five per cent of the time is right about sixty per cent of the time after ten steps, because every step has to be right for the outcome to be. Long agent runs fail for that arithmetic reason far more often than for any dramatic one.

Workflow or agent

Most systems described as agents are workflows, and most systems built as agents should have been. The distinction is precise and worth applying deliberately: in a workflow your code decides what happens next and calls the model for the steps that need language or judgement; in an agent the model decides what happens next. Everything a workflow gives you follows from that — a fixed cost, a traceable path, a step you can retry, and a failure that points at a line of code.

A workflow

  • Your code holds the control flow; model calls sit inside known steps.
  • Cost and latency per run are predictable before you ship it.
  • A failure is attributable to one step, which can be retried on its own.
  • Handles anything you can enumerate — which is most of what products actually do.

An agent

  • The model holds the control flow and chooses each next action.
  • Cost per task varies by an order of magnitude between two similar requests.
  • Handles tasks whose steps depend on what earlier steps found.
  • Needs explicit limits, permissions and observability before it is safe to run.

The honest test is whether you can write down the steps. If you can — even as a long branching list — write them down; the result will be cheaper, faster and debuggable, and you can still call a model inside any step. Reach for an agent when the steps genuinely depend on what earlier steps discover: investigating an incident where the second query depends on the first result, working through a codebase whose shape is unknown, or research where the next source is chosen from what the last one said.

What an agent needs to be safe

The tool surface is the design. Every tool is a capability you have granted, so grant the fewest that let the task be done, scoped to this user’s permissions rather than the service’s. The descriptions are part of the prompt and are read by the model on every call — a description that says when not to use the tool prevents more mistakes than any instruction elsewhere. And every argument the model produces is untrusted input: validate it in your own code, exactly as you would a request body from the internet, because a model that has read an injected instruction will produce a well-formed, entirely wrong call.

Failure modeWhat it looks likeWhat contains it
The loop that will not endThe same two tools called in turn, each result read as a reason to try again.A hard step limit and a token budget, both enforced by your code, not requested in the prompt.
Context exhaustionTool results accumulate until the window fills and the earliest instructions fall out.Summarise or drop old results deliberately; return references to large data rather than the data.
Confident wrong actionA well-formed call that does the wrong thing, often after reading injected text.Validation in your code, least privilege per user, and confirmation before anything irreversible.
Duplicate side effectsA retried or repeated call sends the same email or creates the same record twice.Idempotency keys on every tool that changes state, checked server-side.
Runaway costOne task quietly spends fifty times what a typical one does.A per-task spend cap that aborts, plus an alert on the distribution rather than the average.

Evaluating an agent means evaluating trajectories, not answers. Two runs can reach the same result with wildly different paths, and the one that took nine steps and read a file it had no business reading is the one that will cause an incident. So record the whole trajectory — every call, argument, result and decision — and score two things separately: did it reach the correct end state, and did it get there without doing anything it should not have. Fixed scenarios in a sandbox, with seeded data and tools that record instead of acting, make that repeatable.

How this shows up in real delivery

Start every agent read-only. An agent that can search, fetch and summarise but cannot change anything is useful on its first day and cannot cause an incident, and it earns the write permissions one at a time as its trajectories prove trustworthy. Where a write is genuinely needed early, make it reversible or gate it behind a person — and make the human step meaningful, because an approval dialog that appears forty times an hour is clicked without reading and is worse than no gate at all, since it manufactures the appearance of oversight.

Multi-agent designs — a coordinator handing sub-tasks to specialised workers — are worth the complexity in one specific case: the work genuinely fans out, and reading everything in one loop would fill its context. Research across many sources and per-file work are the honest examples. Outside that, splitting one task across several agents usually multiplies the failure modes rather than dividing the work, because every handoff is a place where context is lost and the compounding arithmetic gets a new set of steps to compound over.

Finally, the interface decides how the failures land. Show the user what the agent is doing while it does it — the tool, the target, the result — because a spinner over a two-minute run gives them nothing to judge and nowhere to intervene. Let them stop it. Show the trajectory afterwards, so a wrong outcome can be understood rather than merely reported. An agent whose work is visible gets corrected early by the person best placed to notice; an agent that works in silence gets switched off after the first incident.

Where it degrades

  • An agent where a workflow would do, paying in cost, latency and debuggability for flexibility nobody needed.
  • No hard limit on steps or spend, so one task can run until somebody notices the bill.
  • Tools that mirror an internal API one call at a time, turning one job into ten chances to go wrong.
  • Tool arguments trusted because the model produced them, with no validation in your own code.
  • Non-idempotent side effects, so a retry sends the second email nobody asked for.
  • The service account’s permissions instead of the user’s, so the agent can reach what the user cannot.
  • Confirmation prompts frequent enough to be reflexive, which manufacture oversight without providing it.
  • Evaluating only the final answer, so a run that reached the right result by the wrong path looks like a success.

When to use it

Use it when

  • Tasks whose next step genuinely depends on what the previous step found — investigation, research, exploration of an unknown structure.
  • Work where the outcome is checkable — tests pass, a schema validates, a person reviews before it takes effect.
  • Read-only assistance over systems a person would otherwise click through by hand.
  • Cases where an error is recoverable and cheap relative to the time the agent saves.

Avoid it when

  • Any task whose steps you can enumerate, where a workflow is cheaper, faster and debuggable.
  • Irreversible actions with no human gate — payments, deletions, outbound messages to customers.
  • Latency-sensitive paths, since a run is many sequential model calls and cannot be made fast.
  • Environments where you cannot scope permissions per user, so the agent inherits more reach than the person asking.

Found this useful?

Share it with someone who is working on the same problem.