Why Your Browser Agent Works in the Demo and Fails in Production
7 min read
Why Your Browser Agent Works in the Demo and Fails in Production
A browsing agent that completes a twelve step task flawlessly on your laptop will often fall apart the first week it runs on a server. The traces look strange. The agent logs in, navigates two pages, then behaves as though it was never authenticated. It retries, burns tokens, and eventually reports that the element it needed was not on the page.
The instinct is to blame the model, then the selectors, then the framework. Quite often none of those changed. What changed is where the requests come from, and it is the one layer most agent stacks never instrument.
What a website decides before your agent renders anything
Every request carries a source address, and bot management systems categorise that address before a single byte of your page arrives. Cloud provider ranges are published and well known, so traffic from a hosted runner is categorised as infrastructure by default. That category does not usually mean a hard block. It means a stricter path: more challenges, more interstitials, sometimes a stripped down version of the page served without the components your agent was trained to click.
That last case is the expensive one, because nothing errors. The page loads, the DOM is valid, and the element genuinely is not there. Your agent reasons correctly about a page that no human user would have received, and your evaluation records a model failure.
Why the demo passed
The laptop run succeeded for reasons that have nothing to do with your code. You were on a home connection with ordinary consumer reputation, running one session at a time, with a browser profile that had months of history behind it. Every signal a bot management system reads said routine visitor.
The production run reversed all four. The address belongs to a published cloud range, twenty sessions start within the same second, the browser profile is minutes old, and the timing between actions is machine regular. None of that is detectable from your traces, which is why the demo felt like evidence and was not.
This is worth saying plainly because it changes how you read a failed deployment. The gap between laptop and server is not a hardening problem to be solved with stealth plugins. It is a difference in how the traffic is categorised, and it is addressed at the network layer or not at all.
Agents break differently from scrapers
Most published advice about proxies was written for scraping, where requests are independent and rotating the address on every request is the correct default. Agents invert that assumption. A single task holds state across dozens of requests over several minutes: authenticate, navigate, filter, fill a form, confirm. That entire chain is bound to a session, and many sites bind the session partly to the address it started from.
Rotate mid task and the site does exactly what it should. It treats the new address as a new visitor, drops the session, and returns a logged out page. The agent has no way to distinguish that from a navigation error, so it retries the whole flow and fails the same way, more expensively.
This is why the network profile that suits an agent is close to the opposite of the one that suits a crawler. What an agent wants is a stable address with ordinary consumer reputation, held for the life of the task. That combination is what an ISP proxy server provides: the address is registered to a consumer internet provider, so it carries residential reputation, but it sits on stable hosted infrastructure rather than on someone's home router, so latency is predictable and the address does not disappear when a stranger closes their laptop. Providers such as ProxyWing sell these as static residential or ISP addresses, and for agent workloads the static part is the point.
Matching the address type to the workload
| Address type | How sites categorise it | Right for | Wrong for |
|---|---|---|---|
| Cloud runner default | Infrastructure | Your own APIs, internal tools | Anything with bot management |
| Datacenter proxy | Infrastructure, distributed | Tolerant public sources at volume | Stateful sessions |
| Rotating residential | Ordinary visitor, address changes | Wide collection, geo sampling | Multi step tasks with login |
| ISP or static residential | Ordinary visitor, address stable | Agents holding a session | High volume rotation |
| Mobile | Carrier subscriber | App and store related work | Anything cost sensitive |
A useful rule when designing a fleet: one stable address per agent identity, not per request. If your agent represents an account, that account should look like it lives somewhere consistent. Accounts that appear in three countries in ten minutes get challenged, and no amount of prompt engineering recovers from that.
The failure modes worth recognising
- Silent content substitution. The page renders, the element is missing, the agent reasons correctly about the wrong page. Detect it by asserting on a known element rather than trusting a successful load.
- Session loss on rotation. Multi step tasks fail at roughly the same step every time. If your failures cluster at step four, look at the connection before the prompt.
- Challenge loops. The agent solves nothing, retries, and consumes its budget. Cap retries per task and treat a repeated challenge as a terminal state that raises an alert.
- Locale defaults. The site infers a country from the address and serves another language or currency. Your extraction schema quietly stops matching.
- Rate limits keyed to the address rather than the API key. Several agents on one runner share one limit, so adding parallelism slows everything down instead of speeding it up.
Instrument the egress or you cannot reproduce anything
Agent debugging is already hard because the model is stochastic. Running it over an uninstrumented network adds a second source of nondeterminism, and the two are almost impossible to separate after the fact.
The fix is cheap. Record the egress address, the resolved country and the session identifier on every trace, alongside the model and prompt version you already log. When a task that passed on Tuesday fails on Thursday, you can immediately see whether the connection changed. In practice a meaningful share of what teams log as regressions turn out to be a different address, or the same address after it picked up a reputation problem.
The same applies to benchmarks. If you are comparing two agent frameworks, or the same framework across model versions, hold the egress constant. Otherwise you are partly measuring which run happened to get a clean address, and the difference can be larger than the difference between the models.
Separate the lanes
One architectural decision prevents most of these problems. Keep bulk collection and interactive action on separate connections.
Collection is high volume, stateless and tolerant of rotation. Action is low volume, stateful and intolerant of it. Running both through one pool means a research job that trips a rate limit takes down the agent that was halfway through a checkout flow. Two pools, two budgets, two sets of alerts, and the failures stop being mysterious.
The boundaries worth writing into the system prompt
Changing where your traffic originates does not change what you are permitted to do. Two rules keep an agent programme out of trouble, and both are easier to enforce in the harness than in the prompt.
- Do not use a different address to reach anything behind authentication, a paywall, or a block that was applied to you specifically. Legitimate uses are about seeing what an ordinary user in a given place sees, not about getting past a decision a site already made.
- Respect published access policies and stop on a specific request to stop. Continuing after notice is the point at which a defensible research activity becomes something else, and the logs you kept for debugging become evidence.
Neither rule costs you anything on the workloads that matter. Price comparison, availability checks, localisation testing, ad verification and evaluation harnesses all operate on public pages, which is where the value is anyway.
A short checklist before you scale a fleet
Run these five checks once and most production surprises disappear.
- Log the egress address and resolved country on every trace.
- Assign one stable address per agent identity, and hold it for the task.
- Assert on a known element, not on a successful page load.
- Split collection traffic from action traffic at the network layer.
- Benchmark with the egress held constant, and record it in the results table.
None of this makes an agent smarter. It removes a category of failure that looks exactly like the agent being stupid, which is worth more than it sounds when you are trying to decide whether a model upgrade actually helped.
FAQ
Can I not just run the agent from a residential connection at the office?
For a handful of tasks, yes, and many teams start there. It stops working when you need concurrency, a specific country, or an address that is not also carrying your staff's everyday traffic.
Does a headless browser flag get me blocked on its own?
It contributes, but address category usually matters more. A well configured headless browser on a consumer address is treated far better than a perfectly humanised one on a cloud range.
How many addresses does a fleet of agents need?
Roughly one per persistent identity, plus a rotating pool for stateless collection. Teams usually over provision the second and under provision the first.
Will this fix agents that fail on complex reasoning?
No. It removes the failures that are not about reasoning, which makes the remaining ones legible. That is the entire benefit and it is worth the afternoon it takes.
Is any of this necessary for agents that only call APIs?
Much less. If every action is an authenticated API call to a service you have an agreement with, the browser layer and its problems do not apply. The issues start when an agent has to use a website built for humans.