An agent that works in a demo and an agent that works for customers are different products that happen to share a codebase. The demo is run by the person who built it, on the application it was built against, with the prompt phrased the way the builder phrases things. Production is none of those. Nine versions of a testing agent in front of real customers taught me that the interesting failures are never the ones the test suite catches. They are the ones that only exist once someone else is holding the thing.

This is the catalogue, one failure mode per section. The examples come from an agent that turns plain-English intent into executable tests for web and mobile applications, but almost none of it is specific to testing. If your agent takes a request, looks at some state, and produces actions, it will meet most of these.

1. The output reads right and does not run

The first version produced steps that looked like exactly what you would want and failed the moment a runner touched them. A model that has read a great deal of code produces things that look like code. Looking right and being executable are different properties, and the gap between them is invisible in a demo because the demo never runs the output against anything strict.

What worked was treating “executable on this platform” as a target to train towards, not a prompt to write. Fine-tuning on structured output that the runner could execute directly moved the needle more than any instruction did, and extending that to long, multi-step sequences dealt with the second half of the problem: small drift early in a sequence compounds by the end. Then the check: every generated step is validated against what the platform can actually run before it is shown to anyone.

2. It is confidently wrong about what is on the screen

Feed a model a raw page and you have fed it mostly noise. Wrappers, hidden nodes, duplicated attributes, three copies of a navigation menu. The model does not complain. It picks something plausible from the noise and commits. The user sees a confident agent clicking the wrong thing.

The fix lived almost entirely on the input side. Dedicated parsers for web, Android and iOS, each stripping what its platform is known to produce and nobody needs. Curated elements for a screen taking priority during generation, so what matters on that page outranks everything that merely exists on it. The model itself was rarely the bottleneck. What reached it was.

3. The screen has nothing to anchor to

Some targets have no stable property at all. Canvas-drawn interfaces, generated ids that change per build, accessibility labels nobody filled in. The markup is honest about this: it simply has nothing to say. An agent that only reads markup fails here every time, and the failure message is the least useful one available: element not found.

Vision closes most of the gap. Giving the agent vision-language input and inline images meant it could reason from what the screen looks like when the markup was silent. The same idea reached backwards into the design stage, where a design file can stand in for a build that does not exist yet. The detail is in Finding UI elements when the DOM won’t cooperate. The lesson that generalises: when your primary signal goes quiet, the agent needs a second one, and it needs to know which one it is using.

4. Users say less than they mean

“Log in and check the order page” is a complete instruction to the person who wrote it and an underspecified one to everyone else. Which credentials. Which order. Check for what. A person on the team fills the gaps from context. An agent fills them from nothing, and the result is a test of something the user did not ask about.

Two things helped. A knowledge base per application, so the agent has the same context a colleague would have, and conversational memory, so the second request in a session can lean on the first. And when a gap cannot be filled, the agent should say what it assumed rather than assume silently. An assumption on screen is a correction away from being right. A hidden one is a bug report.

5. It never stops

Agents loop. They retry a failed action with the same inputs, they re-read a page that has not changed, they open a session and forget to close it. In a demo you stop them by hand. In production the bill for the loop arrives before anyone notices it happened.

Bounds are part of the design, not an afterthought. Steps per task, retries per step, wall-clock time per session, and a hard stop when any of them is exceeded, with a message that says which bound and why. Anything an agent can start, it must be able to stop, and something must stop it if it does not. The same rule shaped an MCP server that lets an agent drive real cloud browsers: sessions have a lifetime, and the server ends them even when the agent forgets.

6. The application changed underneath it

Nobody breaks a feature. Someone renames a class, moves a button into a new container, changes “Sign in” to “Log in”. Each one fails an agent that was anchored to the old version, and after a month of small moves the agent is wrong often enough that people stop reading its output.

The healing layer treats the old anchor and the new page as sequences and scores candidates with a weighted longest-common-subsequence match, where stable attributes like visible text and role count for more than churn like generated class names and positional indices. Above a threshold it re-anchors and carries on. The design choice that mattered was not the algorithm but the policy: heal when confidence is high and the action then succeeds, fail loudly when it is low, and always show what was healed. The full write-up is Self-healing locators.

7. It hides its own errors

A silent failure is worse than a loud one, and agents are excellent at silent failure. A step that half-worked, a target matched at low confidence, a retry that happened to pass. Each of these produces a green result that nobody should trust.

The single change customers noticed most was the agent checking its own work before anything ran, and repairing what it could rather than handing the failure on. Self-validation against what the platform can execute and what the application actually exposes; auto-correction of the steps that fail that check. Broken and flaky output reaching people dropped sharply once that was in place. An agent’s error rate is half the story. What it does with its own errors is the half users see.

8. Every customer is a different world

The agent that works for one customer is a demo. The second customer has a different application, different vocabulary, different rules about what a test is allowed to touch, and a fix for them can quietly break the first.

Multi-tenancy forces the discipline early. Behaviour configured per client and per application, with that configuration encrypted and versioned so a change is a change and not an accident. Authentication and request-level controls so one tenant’s context never leaks into another’s. And staged rollout: a new version reaches live customers gradually, because real users find the case the suite did not, and you want a small blast radius when they do.

9. It costs more per answer than anyone budgeted

Retries, long contexts, the biggest model for every step because it was the one that worked in the demo. None of this shows up until the usage report, by which point the architecture has hardened around the expensive choices.

Instrument every interaction for usage, latency and cost, at the unit the business cares about, which for us was the conversation. Then attribute cost to steps. Once you can see that one step accounts for most of the spend and does not need the largest model, the decision makes itself. That story has its own post: Cutting cost per conversation in a production LLM agent.

10. The model changes and nothing else does

Providers ship new versions. Behaviour that held for months shifts a little, and “a little” on a generation step becomes a visible regression on the runner. The unsettling part is that no line of your code changed.

Evaluations tied to real runs, not synthetic prompts, are the only defence that held. A set of representative tasks, executed end to end, run before a model change reaches anyone. The same staged rollout that protects against your own changes protects against the provider’s.

11. Trust arrives wrong-sized

Some users trust the agent completely after one good run and stop reading its output. Others abandon it after one bad run and never come back. Both are rational responses to a system that does not say how sure it is.

The agent has to show its work: what it checked, what it repaired, where it was unsure. Confidence that is visible can be calibrated by the person reading it. And the first run has to be boring and correct, because that is the run that sets the trust level for everything after it.

12. The content it reads is not on its side

An agent that reads pages reads whatever is on them, including text written to be read by an agent. Instructions in a form field, a hidden node that says to ignore the task. This is an old problem with a new surface, and it is not exotic once your agent runs against applications you did not build.

Treat everything the agent observes as data, never as instruction. Keep the instruction channel separate from the observation channel, and keep the action space narrow enough that a bad instruction cannot do much. The authentication and request-level controls from the multi-tenant work do double duty here.

What holds across all twelve

  • Ship the check before the capability. A narrower agent that verifies itself beats a broader one that impresses sometimes.
  • Prepare the input. Most reliability came from what reached the model.
  • Bound everything. Steps, retries, time, sessions. An agent without limits will find them for you.
  • Make failure legible. To the user, in words; to the operator, in telemetry.
  • Roll out in stages. Real users find the case the suite did not.
  • Feedback is the product. The agent that exists now is the one nine rounds of it taught us to build.

The longer version of how the agent itself came together is the CoTester case study.