Skip to content

Your AI Agent Works 70% of the Time. Here Is What the Other 30% Is Made Of.

August 30, 2026 · 13 min read · by Harshit Luthra

A 70% agent is not 70% of a working product, because the 30% is concentrated in the cases users care most about. Fix it by measuring where it fails first, then hardening tool calls, bounding the loop, grounding the retrieval, and escalating to a human on the cases you cannot make safe.

70% is worse than it sounds

The demo works. Someone shows it in a meeting, it does something genuinely impressive, and the team commits to shipping it. Then it goes in front of real users and the reliability number sits stubbornly around seven in ten, and every attempt to push it higher moves it sideways instead.

The reason a 70% agent feels so much worse than 70% is that the failures are not randomly distributed. They cluster on the ambiguous, unusual, high-stakes requests — which are exactly the requests where a user notices, remembers, and stops trusting the feature. A user who gets a wrong answer on the interesting question does not average it against the nine boring ones it got right. And an agent that fails silently, confidently, and plausibly is worse than one that fails loudly, because nobody catches it.

The way out is not a better prompt. It is finding out what the 30% is actually made of, which almost nobody has done at the point where they are stuck.

Step one: stop guessing about the failures

Before changing anything, build the thing that tells you whether a change helped.

Pull 100–200 real interactions from your logs — weighted toward the ones that went wrong, not a random sample — and label each with what should have happened. Then categorize the failures. In practice they land in a small number of buckets:

  • Retrieval miss — the agent answered from nothing, or from the wrong document.
  • Tool call malformed — wrong arguments, wrong types, hallucinated parameters, a tool that does not exist.
  • Tool call correct, result misread — the tool returned the right data and the agent summarized it wrong.
  • Loop failure — the agent went in circles, or stopped early, or exhausted its step budget.
  • Scope failure — the request was outside what the agent should attempt and it attempted it anyway.
  • Grounding failure — the retrieval was correct and the agent asserted something the context does not support.

Run this categorization once, honestly, and the priority order stops being a debate. Almost every team I have done this with is surprised by the distribution. The failure they were fixing is rarely the failure that dominates.

@dataclass
class Case:
    id: str
    input: str
    expected_tools: list[str]     # which tools should fire, in order
    expected_facts: list[str]     # claims the answer must contain
    forbidden: list[str]          # claims it must not make
    should_escalate: bool         # or is a human the correct outcome

def score(case, trace):
    return {
        "tools":     trace.tool_names == case.expected_tools,
        "grounded":  all(f in trace.answer for f in case.expected_facts),
        "safe":      not any(f in trace.answer for f in case.forbidden),
        "escalated": trace.escalated == case.should_escalate,
        "steps":     trace.step_count,
    }

Four booleans and a step count per case, run in CI on every prompt, model, or tool change. That is the whole harness. It does not need a framework. What it needs is to be fixed — if you keep editing the eval set to match what the agent currently does, you have built a mirror instead of a test.

On a support bot we took from 72% to 96% answer accuracy, building this harness was the first week’s work and it was the reason the remaining weeks were productive rather than circular.

Fix 1: make tool calls fail loudly and locally

Tool calling is where most agents actually break, and it is the most fixable layer because it is ordinary software.

The model produces arguments; treat them as untrusted input from a user, because functionally that is what they are. Validate against a schema before executing anything:

def call_tool(name, raw_args, ctx):
    tool = REGISTRY.get(name)
    if tool is None:
        return err(f"No tool named {name!r}. Available: {list(REGISTRY)}")
    try:
        args = tool.schema.model_validate(raw_args)      # pydantic
    except ValidationError as e:
        return err(f"Invalid arguments: {e.errors()}")   # goes back to the model
    if tool.destructive and not ctx.approved:
        return escalate(name, args)
    return tool.run(args, ctx)

Two details carry most of the value here. First, the error goes back to the model as a tool result, phrased as something it can act on — the model repairs its own call far more often than people expect, and a repaired call on the second attempt is invisible to the user. Second, an error that says which tool names exist beats an error that says the name was wrong.

Then narrow what can go wrong in the first place. Enums instead of free-text strings wherever the set of valid values is known. Required parameters rather than optional ones the model will omit. Tool descriptions that state when not to use the tool, not just what it does — a large share of wrong-tool selections come from two tools whose descriptions do not distinguish them at the boundary. And fewer tools: an agent with six well-separated tools picks correctly far more often than one with twenty overlapping ones.

Fix 2: bound the loop

An agent loop with no explicit termination conditions will find a way to not terminate. The two failure modes are opposites and both need handling: spinning forever, and stopping before the job is done.

MAX_STEPS = 8
seen = set()
for step in range(MAX_STEPS):
    action = model.next_action(state)
    if action.is_final:
        break
    key = (action.tool, canonical(action.args))
    if key in seen:                       # identical call, second time
        state.append(sys_msg("You already ran that and got the result above. "
                             "Use it, try a different approach, or hand off."))
        continue
    seen.add(key)
    state.append(call_tool(action.tool, action.args, ctx))
else:
    return escalate("step_budget_exhausted", state)

Repeat-call detection is the single highest-value guard, because the classic infinite loop is the agent calling the same search with the same arguments and re-reading the same unhelpful result. Telling it plainly that it already did that breaks the cycle more reliably than raising the step limit.

Note what happens when the budget runs out: it escalates rather than returning whatever it had. An agent that hits its limit and then produces a confident final answer from an incomplete investigation is manufacturing the exact failure you are trying to prevent.

Fix 3: most “hallucination” is a retrieval problem

When an agent asserts something false, the instinct is to blame the model. Check the retrieval first. In the systems I have debugged, the large majority of confident wrong answers had the right answer missing from the context entirely — the model was doing its best with material that did not contain the answer.

Log the retrieved chunks alongside every answer and go through the failures. If the correct passage was not retrieved, it is a retrieval problem and no amount of prompt engineering will fix it. Chunking that splits a fact from its heading, embeddings that miss keyword-shaped queries like error codes and part numbers, and no re-ranking step are the usual causes, and they are covered in more depth in self-hosted RAG production issues.

If the passage was retrieved and the answer still contradicts it, that is genuinely a grounding problem, and it responds to different treatment: require citations to specific retrieved chunk IDs, and make “the provided context does not cover this” an explicit, rewarded output rather than a failure state. An agent that has never been given permission to not know will always invent something.

Fix 4: design the handoff, do not treat it as the failure case

There is a set of requests your agent should not attempt. Adversarial inputs, requests that need judgment about an individual case, anything where being wrong costs real money or trust. The goal is not to shrink that set to zero. It is to detect membership in it reliably and hand off cleanly.

Escalate on low retrieval confidence, on step-budget exhaustion, on repeated tool failure, on any destructive action, on explicit user request, and on sentiment that suggests the user is already frustrated. Then make the handoff good: pass the full conversation and everything the agent found, so the human starts informed rather than asking the user to repeat themselves. A handoff that loses context is worse for the user than no agent at all.

This is what makes a deflection number honest. On a support agent that deflects around 60% of tickets, the 40% it does not handle is not a failure rate — it is a routing decision, made deliberately, on the categories where a human is the correct answer. Chasing that last 40% with a cleverer prompt would have traded a reliable 60% for an unreliable 85%.

For anything irreversible, the pattern that ships is proposal plus approval: the agent does the analysis and drafts the action, a human clicks yes. On an ops workflow that removed about 20 hours a week of manual work, the agent does the triage and routing — the reading, classifying, and drafting that consumed the hours — and a person approves the batch. The value was never in removing the human. It was in removing the reading.

What good actually looks like

An agent that is ready to ship is not one that never fails. It is one where you know its failure rate per request class because you measured it, the failures are recoverable because it escalates instead of guessing, no single failure can do something irreversible, and a regression shows up in CI rather than in a support ticket.

That is a different target than “make it smarter,” and it is reachable with ordinary engineering. Reach for a bigger model after the plumbing is right, not as a substitute for fixing it.

If you have an agent stuck at a number you cannot ship, that is the work I do under AI agents and workflow automation — usually starting with the eval harness, because until that exists, every other change is a guess.

Written by Harshit Luthra, an independent infrastructure and AI engineering consultant. Stuck on something similar? →

related

If this is live for you right now

Questions people ask about this

Why do AI agents work in demos and fail in production?+

Demos run the happy path on well-formed inputs the builder chose. Production runs ambiguous, truncated, multilingual, adversarial, and out-of-scope inputs the builder never saw. The agent's reasoning is usually not what breaks — the tool calls, the retrieval, and the unbounded loop break, and they break on exactly the inputs that were never in anyone's test.

What reliability number does an agent need before shipping?+

It depends entirely on what a failure costs and whether it is recoverable. A support agent that can say 'let me get a human' and hand off cleanly can ship in the high eighties, because its failure mode is a slightly slower ticket. An agent that takes an irreversible action — issuing a refund, modifying infrastructure, sending an external email — needs either near-perfect accuracy on that specific action or a human approving it. Pick the number per action, not per agent.

How do I actually measure whether my agent is getting better?+

Build a fixed eval set of real failures — 100 to 200 traces pulled from logs, labelled with what should have happened — and run it on every prompt, model, or tool change. Without it, every fix is a guess and every regression is invisible until a user reports it. The eval set is what converts agent work from opinion into engineering.

Is the fix usually a better model or a better prompt?+

Usually neither. In the agents I have debugged, most failures trace to retrieval returning the wrong context, tool arguments that do not validate, or a loop with no termination condition. Those are engineering problems that a bigger model papers over inconsistently and expensively. Upgrade the model after you have fixed the plumbing, not instead of it.

Want a second pair of eyes on this?

Book a free 30-minute call. We diagnose it together, and you walk away with a plan you can act on. You’ll get a straight read either way.