Agents & tool use
AssumesStructured outputEvals
In one line
A loop where the model picks a tool and your code decides whether to run it, and every interesting problem is in the loop rather than in the model.
Why it exists
A model can only produce text from what is in its context. It cannot check today's inventory, call your billing API or read a file. closes that gap by giving the model a set of function signatures it may request calls to — and, crucially, by keeping the execution entirely on your side.
An is the loop built on top: the model chooses a tool, your code runs it, the result goes back into context, and the cycle repeats until something stops it. That description is complete. There is no additional machinery, and the absence of additional machinery is exactly why the engineering discipline has to come from you.
The loop, and what each turn costs#
One iteration of the :
1. model receives context, emits a tool name + arguments
2. your code validates, authorises, and executes
3. the result is appended to context
4. repeat, or stop
Two properties of that loop matter more than anything about the model.
Context only grows. Every tool call and every result is appended. Turn ten carries all nine previous exchanges, so token cost per turn rises and the total cost of a run grows superlinearly in the number of steps. A ten-step agent is not ten times a single call; it is often thirty or forty times.
Nothing bounds it by default. The model decides when it is finished. If it never decides, the loop never ends. There is no timeout in the protocol, no step limit in the model, and no error raised by a loop that is merely unproductive.
So the two controls you must add before anything else are a hard step budget and a spend cap, enforced in your code. A that trips on step count or accumulated cost is not a refinement here — it is the only thing standing between a confused model and an unbounded bill. Notably, a runaway agent raises no exception; it just keeps working.
Tool design is API design, for an unusual caller#
A tool definition is a name, a description and a for its arguments. The model reads all three as text and picks based on them, which makes the description the most load-bearing documentation you will write.
The caller has specific characteristics worth designing around. It will not read your wiki. It cannot experiment in a REPL. It will call the same tool twice if the first result was unclear. And it has strong priors from having read enormous amounts of ordinary code, so conventional naming genuinely helps.
What follows from that:
Few, well-named tools beat many. Twenty overlapping tools produce wrong selections. Five that map to distinct intents produce right ones. If two tools are easy to confuse, they should probably be one tool with an enum parameter.
Describe when not to use it. "Use for order lookups by id; do not use for
searching by customer name — use search_customers for that" removes a whole
class of misrouting for one sentence.
Return errors the model can act on. {"error": "order_not_found", "suggestion": "try search_orders with the customer email"} recovers. A 500 with
a stack trace does not.
Keep results small. A tool returning 8,000 tokens of JSON has just consumed the for the rest of the run. Paginate, summarise, or return ids the model can expand selectively.
Make everything . Retried and duplicated calls are normal here rather than exceptional, because the model may simply decide to call again. Every mutating tool needs an idempotency key, exactly as you would build a payment endpoint.
The trust boundary#
The single most important architectural point: the model never executes anything. It emits a name and arguments, as . Your code decides whether to comply.
That boundary is where every security control belongs, and the reasoning is forced. Tool arguments may have been influenced by — a retrieved document or a previous tool result containing instructions. There is no reliable way for the model to distinguish data from instruction, so treating its output as an authenticated request is a category error.
The controls that follow are ordinary ones:
Authorise against the user's identity, not the agent's. If a user cannot read another tenant's orders, neither can an agent acting on their behalf, and the check belongs in the same place it always did.
Validate arguments as untrusted input — schema, ranges, allowlists — because a valid-looking argument is not a safe one.
Require human approval for destructive or irreversible actions. Sending an email, issuing a refund, deleting a record: these want a confirmation step, and the is code, not a sentence in the system prompt.
Log every call with its arguments and outcome. This is your only audit trail, and you will need it.
Worked example
An agent that answers "why was invoice 4471 higher than last month?" with three
tools: get_invoice, get_usage, search_docs.
A healthy run:
step 1 get_invoice(4471) -> £840, 3 line items 1,100 tokens
step 2 get_invoice(4470) -> £310, 2 line items 1,600 tokens
step 3 get_usage(period=2026-08) -> 2.1M API calls 2,400 tokens
step 4 search_docs("overage") -> tier limits passage 3,100 tokens
step 5 answer 3,400 tokens
Five model calls, and note the token column: the last call costs three times the first because context accumulates. Total spend is roughly 11,600 prompt tokens, not 3,400.
Now the same agent when get_usage returns an empty result:
step 1 get_invoice(4471) -> ok
step 2 get_usage(period=2026-08) -> []
step 3 get_usage(period=2026-8) -> [] reformatting the argument
step 4 get_usage(period="August") -> [] guessing
step 5 get_usage(period=2026-08) -> [] repeating step 2
...
step 22 get_usage(...) -> []
No error, no exception, twenty-two model calls and roughly 90,000 tokens burned on a loop that made no progress after step two. Three cheap controls stop it:
max_steps = 8 hard ceiling
repeat detection same tool + same args twice -> stop
empty result guidance {"error":"no_usage_for_period",
"available":["2026-07","2026-09"]}
The third is the one that turns a loop into a correct answer, and it is a change to the tool, not to the model.
Gotchas
-
No step budget. The model decides when to stop, so without a hard ceiling there is no ceiling. Set one at the lowest number your traffic actually needs and alert when runs hit it, because hitting it usually means a tool is misbehaving.
-
Trusting tool arguments. They may be shaped by injected content in retrieved text or a previous result. Authorise against the user's permissions and validate as untrusted input, every call.
-
Tools that return too much. A verbose result crowds out the rest of the run and raises the cost of every subsequent step. Summarise at the tool boundary; the model does not need the whole row.
-
Unhelpful error strings.
500 Internal Server Errorgives the model nothing to do except retry, which is how loops start. Structured, actionable errors are the single highest-value change to most tool sets. -
Reaching for an agent when a fixed sequence would do. If you know the steps, write the steps. An agent is for genuinely variable control flow, and it costs an order of magnitude more than a chain in both tokens and debuggability.
Mental model
An agent is a while loop whose condition is evaluated by a language model, and
your job is everything a while loop normally needs and does not get here: a
bound, a progress check, an audit trail. Add so each iteration is a
you can attribute cost to, and an over whole runs rather than
single calls, and it becomes an ordinary piece of software with one unusual
component.
In practice
Problem statements that lean on this topic. Rated by the phase that makes them land.
The brilliant intern with no memory
The single most useful analogy for working with a model — someone who has read everything, remembers nothing between conversations, and sounds equally sure either way.
no prerequisites · 4 min · #mindset #framing #risk
Where would the answer come from?
Every answer a model gives has one of three sources — its training, your prompt, or a tool — and asking which one, before building, decides most of the architecture.
no prerequisites · 4 min · #framing #retrieval #architecture
The cost of being wrong decides the design
The same model sits behind an autocomplete and a legal notice; what changes is what a wrong answer costs, and that — not the difficulty — sets how much checking goes around it.
no prerequisites · 4 min · #risk #product #architecture
An agent that books travel
Sort the tools by what happens if the model is wrong, and every control in the system lands on the one row where money moves.
after phase 4 · 4 min · #agents #security #risk
The 3am bill
A loop with no bound did not error; it worked all night, expensively, on a task it had stopped making progress on at step two.
after phase 4 · 4 min · #agents #cost #observability
The poisoned PDF in the knowledge base
Text in a document reaches the model as text, indistinguishable from an instruction you wrote — so the defence is not a better prompt but a check at the point an action is taken.
after phase 4 · 4 min · #security #agents #retrieval
Done reading?
Nothing marks itself complete. Say so only when you could explain this to someone else.
This topic has 3 subtopics.