Most agent failures are not reasoning failures. They are interface failures: a tool that does three things, a description that never says when to use it, an error message written for a log and not for the caller. The model routes on what you wrote, so the fix is usually in the text and not in the model.

The description is the routing logic

An agent choosing between tools is reading descriptions. Not names, not implementations — descriptions. Two tools whose descriptions overlap produce routing that looks random, and the instinct is to blame the model.

A description that works says three things: what the tool does, when to use it, and when not to. The third is the one almost always missing, and it is the one that separates two tools that otherwise sound alike.

Before changing the model, changing the prompt, or adding a routing layer, read your tool descriptions as if you were the one choosing. If you cannot tell them apart, neither can the agent.

One tool, one job

Instead ofSplit intoWhy
createAndNotifyCustomercreateCustomer · sendNotificationOne is reversible, one reaches a human. Different approval needs.
updateOrder with a mode flagSeparate tools per operationA flag the agent picks wrongly is a silent wrong action.
queryDatabase taking raw SQLNamed queries with typed parametersAn arbitrary query surface is an arbitrary blast radius.

Typed, validated, and validated again

Declare parameter types and constraints in the schema, and validate them in the tool anyway. The schema steers the model; it does not bind it. An agent will pass a date as a string, a quantity as text, or an identifier it inferred from context.

Validation inside the tool is what turns those into handled errors rather than corrupted records.

Write errors for the caller

An agent reads the error and decides what to do next. That makes error text part of the interface, and it is usually the part written last and for a human.

  • Unhelpful: Error 400: Bad Request — the agent retries identically.
  • Useful: customerId must be 8 digits; received 6 — the agent can correct.
  • Useful: No customer matches that name. Try searchCustomers first. — it can route.
  • Terminal: Period is closed. This cannot be posted. — it stops instead of looping.

Assume every call happens twice

Agents retry: on timeouts, on ambiguous responses, on their own uncertainty about whether the last call landed. A create operation that runs twice produces two records, and nothing in the agent will notice.

Key writes on a correlation identifier so a repeat updates instead of duplicating. This is ordinary distributed-systems hygiene, and it becomes urgent the moment the caller is a model rather than a deterministic client.

Budgets and terminal failures

  1. Cap steps per task. Without a budget, a loop runs until something else stops it.
  2. Make terminal failures unmistakable. Most loops are a retryable-looking error that will never succeed.
  3. Route the stuck case to a person. Silent give-up is worse than a visible error, because nobody learns the work did not happen.
  4. Alert on call-volume anomalies. It is the earliest signal that something is spinning.

Test the tools, not just the agent

Agent behaviour is hard to test end to end and easy to test at the boundary. Every tool should have ordinary tests: valid input, invalid input, boundary values, duplicate call, downstream failure.

A tool suite that passes tells you the agent's failures are routing or reasoning. Without it, every investigation starts by working out which layer broke — and that is most of the time spent debugging agents.