Skip to content

Browser agents: how a language model drives a real browser

· 10 min read · Lobster Browser team

agents
browsers
automation

A browser agent is a language model placed in a loop with a browser: it is shown the state of a page, it chooses an action, the action is performed, and it is shown the result. Described that way it sounds simple, and the first prototype usually is. The difficulty is entirely in the details — what exactly the model is shown, what exactly it is allowed to do, how the system knows the page has finished changing, and how it knows whether the action worked.

This post walks through those details in order: perception, the action space, settling, verification, the recurring failure modes, the safety boundaries such an agent needs, and why driving a real browser is often right even when a plain HTTP client would be cheaper.

The shape of the system

Three components, and it is worth keeping them mentally distinct because they fail differently. There is the browser, which renders pages and runs their scripts. There is a controller, which speaks to the browser over an automation protocol and exposes a small set of operations. And there is the model, which sees a description of the page and emits one operation at a time.

The controller is where nearly all of the engineering lives. It decides what the model sees, translates the model's chosen action into concrete input events, waits for the page to settle, and decides what to report back. A good controller makes a mediocre model useful; a bad controller makes an excellent model look unreliable.

Perception: what the model is actually shown

There are three practical channels, and most working systems use a combination rather than one.

The DOM

The document is the most precise description available: exact text, attributes and structure. It is also far too large — a modern application page serializes to hundreds of thousands of characters, most of it framework wrappers and generated class names that carry no meaning. Every serious implementation prunes: drop invisible nodes and presentational containers, keep interactive elements and text, and give each survivor a short stable identifier the model can refer to.

The accessibility tree

The browser already computes a semantic summary for assistive technology: a tree of roles, accessible names, states and values. It is far smaller than the DOM and closer to how a person describes a page — a button named “Save” rather than a div with six utility classes — which makes it the best default for most tasks. Its weakness is that it reflects what the page declares about itself, and purely visual information such as layout and relative position is largely absent.

Screenshots

A rendered image carries what the other channels lose: what is actually visible, what overlaps, what a dialog is covering. Vision-capable models work from it directly, often with numbered overlays drawn on candidate elements so a target can be named unambiguously. The costs are real — images consume context, fine text can be misread, and pixel coordinates are fragile across window sizes and zoom levels.

ChannelStrengthWeaknessGood default for
DOMExact text, attributes and structureEnormous; mostly noise; brittle selectorsExtraction and verification
Accessibility treeCompact, semantic, matches human descriptionOnly as good as the page's own semantics; no layoutNavigation and form filling
ScreenshotGround truth about what is visibleToken-hungry; coordinates are fragile; small text misreadCanvas-heavy pages, overlays, visual checks
The three perception channels compared

The action space

The set of operations the model may emit is a design decision with large consequences. Too small and ordinary tasks become impossible; too large and the model spends its reliability choosing between near-duplicates. A workable core is small.

json
{ "action": "click",    "target": "e42" }
{ "action": "type",     "target": "e17", "text": "ACME Ltd", "submit": false }
{ "action": "select",   "target": "e08", "option": "Germany" }
{ "action": "scroll",   "direction": "down", "amount": "page" }
{ "action": "navigate", "url": "https://example.com/orders" }
{ "action": "wait_for", "condition": "text", "value": "Order confirmed" }
{ "action": "extract",  "fields": ["order_id", "status"] }
{ "action": "ask_user", "question": "Which of the two addresses should I use?" }
{ "action": "done",     "summary": "Order 4471 is marked shipped." }

Several things there are deliberate. Targets are opaque identifiers assigned during perception, not CSS selectors invented by the model — a model asked for a selector will write a plausible one that matches nothing. Typing carries an explicit submit flag. Waiting is an action, so the model can state what it expects to happen. Asking the user is an action, because an agent with no way to ask will guess. And finishing carries a summary, so there is something to verify against.

  • Prefer semantic actions over input primitives. “Click element 42” survives a layout change; “click at coordinates” does not.
  • Make destructive operations distinct in the schema, so a policy layer can require confirmation without parsing intent from prose.
  • Return a structured result for every action, including failures. “Element 42 is no longer in the document” is a recoverable observation; a timeout with no explanation is not.
  • Keep an explicit escape action. An agent that cannot say “I am stuck” will loop.

Settling: knowing when the page is ready

This is the largest single source of flakiness, and it is a systems problem rather than a model problem. After a click a page may fetch data, animate a transition, re-render a list, and only then show the element the agent needs. Observe too early and the model sees a spinner; observe too late and every step costs seconds it did not need.

  • Fixed sleeps are the worst option available. They are simultaneously too short on a slow network and too long on a fast one, and they hide the real condition.
  • Wait for a condition the task defines: a specific element present, a specific text visible, a URL change, a request completed. The controller should support this, and the model should be able to request it.
  • Quiescence heuristics — no DOM mutations and no in-flight network requests for a short window — are a reasonable default when no specific condition is known. They fail on pages that poll, so cap them.
  • Detect and skip the furniture: cookie banners, consent dialogs and interstitials. These are not part of the task, and an agent that reasons about each one from scratch wastes most of its steps on them.
  • Set a ceiling: if settling has not happened within a bounded time, that is an observation to report, not a condition to wait out.

Verification: did the action do what you asked?

An agent that assumes its actions succeeded will eventually report that it filed a form it did not file. The fix is a loop invariant: after every state-changing action, observe again and compare the new state against the intent that produced the action.

  1. Before acting, state the expected observable consequence: a field contains this value, a confirmation appears, the row count increases.
  2. Act.
  3. Settle, then re-observe.
  4. Compare. If the expectation is unmet, that is an observation to reason about — not a reason to repeat the same action.
  5. At the end of the task, verify the goal independently: re-read the record, reload the page, or check from a different view rather than trusting the confirmation banner.

Independent final verification catches the most damaging failures, where every step looked fine and the outcome is wrong — a form submitted into a validation error, a save that required a second confirmation nobody saw.

Failure modes you will meet

  • Stale references. The page re-rendered between perception and action, and element 42 is now a different element. Re-resolve targets at action time and fail loudly when the identity does not match.
  • Off-screen and occluded targets. The element exists in the tree and is covered by a sticky header or a modal. Clicking it does nothing, or worse, clicks the overlay.
  • Infinite scroll and virtualized lists. The item is not absent, it is not rendered yet. An agent that concludes “not found” from one viewport is wrong most of the time.

Safety boundaries

A browser agent is one of the highest-privilege agents in common use: it runs inside a session that is already signed in, so it can do anything the user can do. That is what makes it useful, and why it needs boundaries enforced by the program rather than the prompt.

  • Allow-list the origins the agent may act on, and treat navigation away from them as an event that ends the task.
  • Require explicit confirmation before purchases, deletions, message sending, and anything that changes access for another person.
  • Separate reading from acting where the task allows it. Many useful browser tasks are pure extraction and need no write permission at all.
  • Bound the run: maximum steps, maximum time, maximum navigations. Stopping is a valid outcome.
  • Respect the site's terms and its robots directives, and rate-limit yourself. “The automation could” is not the same as “the automation may”.

Why a real browser beats a headless scraper

If a page's data is available from an HTTP request, fetch it: a scraper is faster, cheaper and easier to test, and dressing up a simple fetch as an agent is a common and expensive mistake. But there is a large class of tasks where the browser is not an implementation detail — it is the only place the task exists.

  • The page is the application. The content is assembled by client-side code from several requests, and there is no single endpoint that returns it.
  • The work is interaction, not retrieval: filling a multi-step form, reconciling two views, confirming a change.
  • Session and state are held by the browser — cookies, storage, tokens refreshed by scripts — and reproducing them outside it is fragile and stays fragile.
  • The task is visual: reading a chart, judging a layout, noticing that a control is disabled.
  • The page actively depends on a real rendering environment, so a stripped-down client simply gets a different, emptier page.

There is also a consistency argument that is easy to overlook. A real browser presents one coherent environment — rendering, fonts, media handling, timing, storage — and pages are built and tested against exactly that. A minimal client assembles a partial imitation, and every gap becomes a behavior difference you debug later.

Summary

A browser agent is a perception problem, a settling problem and a verification problem wearing a model as a hat. Choose the perception channel that makes the next action determinable from the least text, usually the accessibility tree with screenshots where vision is needed. Keep the action space small, semantic and explicit about destructive operations. Never sleep when you can wait for a condition. Verify after every state change and again at the end. Assume everything on the page is untrusted, and put the agent's limits in code rather than in the prompt. Do that and the choice of model becomes a tuning decision rather than the whole project.

Further reading

Named rather than linked, on purpose: specifications move and URLs rot, while a title and an author survive a search.

  • W3C WebDriver and WebDriver BiDi specifications — the standard automation protocols, and the vocabulary most tooling borrows.
  • Chrome DevTools Protocol documentation — the lower-level interface behind most browser automation libraries.
  • W3C Accessible Name and Description Computation, and the ARIA specification — how the accessibility tree a browser agent reads is actually derived.
  • WebArena, Mind2Web and MiniWoB++ — research environments for measuring web agents end to end.
  • OWASP Top 10 for Large Language Model Applications — in particular the entries on indirect prompt injection and excessive agency.

Browser fingerprinting, explained

· 10 min read

What a browser fingerprint is, which surfaces it is built from, why entropy and stability both matter, and why an incoherent disguise is worse than none.

fingerprinting
privacy
browsers

Agentic AI in 2026: what has actually changed

· 9 min read

What separates an agent from a chatbot: tool use, the planning loop, memory, evaluation and guardrails — and the places agents still reliably fail.

agents
llm
evaluation