An AI agent is a loop. The model reads a goal, picks a tool, looks at the result, and decides what to do next until it thinks it is done. That loop is what makes agents useful for messy, multi-step work, and it is also what makes them hard to operate. A single LLM call fails once. An agent can fail on step seven after six correct steps, spend forty tool calls going in circles, or complete the task in a way nobody asked for.
We have shipped agents for internal operations, research workflows and back-office processing. The model has rarely been the hard part. The work goes into tools, permissions, limits, tracing and evaluation. Here is what that looks like.
First, decide whether you need an agent
If you can draw the process as a flowchart with a handful of branches, build the flowchart. A fixed pipeline where an LLM handles specific steps (classify this, extract that, draft a reply) is cheaper, faster, easier to test and easier to explain to an auditor. Agents earn their keep when the path genuinely depends on what the model finds along the way: investigating an alert across several systems, reconciling records where the mismatch could be anywhere, or researching a question where the next search depends on the last result.
A useful test: write down the steps a competent person follows. If the list is the same every time, you want a workflow. If it starts with "it depends", an agent might be justified. Many production systems end up as a mix, a deterministic pipeline with one bounded agentic step inside it.
Tools are an API for a confused reader
The model only knows your tools from their names, descriptions and schemas. Write them the way you would document an API for a new contractor who cannot ask questions. Say what the tool does, when to use it, when not to, and what it returns. Give parameters clear names and constrained types: an enum of three statuses beats a free-text string the model has to guess.
- Prefer fewer, task-shaped tools over many thin wrappers around raw endpoints. "find_customer_by_email" is easier to use correctly than a generic query tool.
- Return compact, relevant results. Dumping a 3,000-line JSON response into the context wastes tokens and buries the useful field.
- Make errors instructive. "Invoice not found; check the ID format INV-000000" lets the model recover. A stack trace does not.
- Make write tools idempotent, with a request key, so a retried step does not create two tickets or send two emails.
- Paginate and cap result sizes on the server side rather than trusting the model to ask for less.
Then test the tools with the model, not just with unit tests. Watch traces of the agent using them and look for repeated misuse. If the agent keeps passing a name where an ID is expected, the fix is usually in the description or the schema, not in the system prompt.
Scope permissions as if the prompt will be hijacked
Agents read untrusted text all the time: emails, web pages, tickets, documents. Any of it can contain instructions aimed at the model. Prompt injection has no complete fix today, so design on the assumption that sometimes the agent will try to do what an attacker wrote.
That makes permissions the real defense. Give each agent its own service identity with the narrowest access that the task needs. Read-only by default. Scope data access to the user or tenant the agent is acting for, and enforce that in the tool layer, not in the prompt. Keep credentials out of the context window entirely; tools hold them, the model never sees them. Be especially careful with any combination of reading untrusted content, accessing private data and sending data outward, because that trio is how exfiltration happens. If an agent reads customer emails, it should not also have a tool that fetches arbitrary URLs.
Step limits, budgets and timeouts
Every agent run needs hard ceilings enforced by your code, outside the model's control. Without them a confused agent will keep calling search with slightly different wording until something stops it, and the thing that stops it should not be the monthly invoice.
| Limit | What it prevents | How to set it |
|---|---|---|
| Max steps per run | Loops and endless retries | Look at the step count distribution on your eval set, then add headroom |
| Token or cost budget per run | One task eating a day's spend | Fail or escalate when exceeded, never silently continue |
| Wall-clock timeout | Runs that hang on slow tools | Per tool and per run, with clear error messages |
| Repeated call detection | Same tool, same arguments, again | Stop after two or three identical calls |
| Per-user and global rate limits | Runaway batch jobs | At the gateway, like any other API |
When a limit trips, end the run gracefully. Save the trace, return a partial result with an honest explanation, and route the task to a person if it matters. A run that stops at step 25 with a clear note is far more useful than one that produces a confident, wrong answer at step 60.
Human approval for side effects
Split tools into those that only read and those that change something outside the system: sending a message, updating a record, moving money, deleting data, deploying code. Read tools run freely within budget. Side-effect tools pause the run and ask a person.
The approval screen should show exactly what will happen, with the concrete arguments, not the agent's summary of its intent. "Send this email to these three addresses" with the full text, or "Change invoice INV-2207 status from open to void". Approvers who see vague descriptions approve everything, which is worse than no approval because it creates false confidence. As logs build up, you can automate the categories where approvals are always granted and the stakes are low, and keep humans on the rest.
Tracing is not optional
You cannot debug an agent from its final answer. Record every run as a trace: the input, each model call with its prompt and output, each tool call with arguments, result, latency and errors, plus token counts and cost. OpenTelemetry works fine as the transport, and several LLM observability tools build on it. Tag traces with the user, tenant, agent version and prompt version so you can compare releases.
Watch the aggregate numbers too: success rate, steps per run, cost per completed task, how often limits trip, and which tools error most. A jump in average step count after a prompt change usually shows up before anyone files a complaint. Be careful with what you store, since traces contain whatever the agent read. Apply the same retention and access rules you use for the underlying data.
Evaluating multi-step tasks
Agent evals are harder than single-call evals because there are many valid paths to a correct outcome. Grade outcomes first. Set up a sandbox with seeded data, give the agent a task, and check the end state: was the right record updated, was the right customer emailed, was nothing else touched. That last check catches agents that technically finish while causing damage on the way.
Then grade the trajectory where it matters: did it call forbidden tools, exceed a sensible number of steps, or leak data between tenants? Run each task several times, because agents are non-deterministic and a task that passes four out of five runs is a reliability problem in production. Start with 50 to 100 realistic tasks, include ones the agent should refuse or escalate, and add every production failure to the set. Run it on every prompt, model or tool change before it ships.
None of this is exotic. It is the same discipline you would apply to any service that can change data on its own: least privilege, limits, audit logs, tests and a person in the loop for risky operations. Agents just make skipping it more expensive.
Questions we get asked
- What is the difference between an AI agent and an LLM workflow?
- A workflow follows steps defined in code, with an LLM handling specific tasks. An agent lets the model choose which tools to call and in what order. Workflows are easier to test and operate; agents handle open-ended tasks.
- How do you stop an AI agent from looping?
- Enforce a maximum step count, a per-run token or cost budget, timeouts and repeated-call detection in your own code, and end the run with a partial result when a limit is hit.
- Should AI agents be allowed to take actions without approval?
- Read-only actions, yes, within budget. Actions with external side effects should require human approval until logs show a category is low risk and consistently approved.
- How do you test an AI agent?
- Run realistic tasks in a sandbox with seeded data, check the end state, check that nothing else changed, and repeat each task several times to measure reliability.