Abstract: Browser Use gives models short-lived action capabilities, not durable element identity.

Snapshot: browser-use/browser-use at 96ec5dc, version 0.13.6, inspected on 2026-07-23. The package’s default from browser_use import Agent still resolves to the Python implementation analyzed here. The Rust-backed 0.13 beta is an explicit from browser_use.beta import Agent import and has a different runtime.

Browser Use contains hashes, XPath, and accessibility-name matching, but these mechanisms do not provide persistent element identity across a live React render.

That code exists. It is not in the live agent loop.

The five-stage ladder belongs to rerun_history(). A normal step is stricter: the model returns an integer resolved against that observation's selector map. If the entry is gone, the action reports page change rather than choosing a similar control.

That changes the architectural model. Browser Use is not primarily a self-healing selector engine. It is a protocol for issuing expiring element capabilities to an LLM and fencing action batches when the observed world changes.

Scope and element-identity model

Browser Use is a Python browser-agent runtime. It connects:

natural-language task
→ browser observation
→ structured LLM decision
→ typed browser action
→ action result
→ history and next observation

It is not a drop-in replacement for Playwright or Selenium: those assume developer-owned control flow and locators; Browser Use delegates discovery of the next action to a model.

Nor is every Browser Use product the same layer. The CLI handles one-off tasks, the library embeds automation, and Cloud adds managed browser infrastructure. Cloud claims are not properties of the open-source loop.

The element-identity problem.

A deterministic test can say:

page.get_by_role("button", name="Submit").click()

In Playwright, that locator is a query, not a stored DOM object. It is resolved again when the action executes. Playwright then waits for a unique target that is visible, stable, enabled, and able to receive events.

An LLM entering an unknown website does not already possess that query. Giving it raw HTML is not a solution:

  • a large application can overflow the useful context window;
  • wrappers, scripts, style nodes, and invisible controls create noise;
  • HTML alone does not describe paint order or occlusion;
  • cross-frame coordinates and target ownership are missing;
  • a clickable div may have no native interactive tag.

Screenshots solve a different subset of the problem. A vision model can recognize a blue “Submit” button, but the image does not inherently identify its frame, DOM object, event semantics, or whether a transparent overlay receives the click.

Browser Use therefore needs to answer three runtime questions:

  1. Which objects are worth exposing to the model in this observation?
  2. How does an integer in model output become an action on a real browser object?
  3. When the first action changes the page, which remaining actions must be revoked?

Observe-decide-act-fence protocol.

The main Python path can be summarized as:

OBSERVE
  DOM tree + Accessibility trees + DOMSnapshot + viewport
  → EnhancedDOMTreeNode
  → action-oriented SerializedDOMState
  → selector_map[backend_node_id] = node

DECIDE
  task + history + agent state + browser state + optional image
  → LLM with a Pydantic output schema
  → AgentOutput[action...]

ACT
  index → cached node → BrowserEvent
  → frame-aware CDP resolve / scroll / hit-test / input
  → ActionResult

FENCE
  terminal action metadata OR URL/focused-target change
  → discard remaining actions
  → observe again

Agent owns the loop and messages. BrowserSession owns browser lifecycle, CDP, and the event bus. DOMWatchdog constructs observations. Tools turns Pydantic actions into events; DefaultActionWatchdog executes them. Other watchdogs handle security, downloads, storage, dialogs, and recording.

Request lifecycle and mutation control

Consider a concrete task:

Open a job application form, enter a name and email address, submit it, and verify that the success page appears.

run() establishes the runtime.

Agent.run() starts the browser session, attaches watchdogs, registers skills as actions, executes configured initial actions, and enters a step loop. Defaults in this snapshot include:

  • max_actions_per_step=5;
  • max_failures=5;
  • step_timeout=180;
  • use_vision=True;
  • planning, loop detection, message compaction, and a completion judge enabled.

If no LLM is supplied, configuration may select DEFAULT_LLM; otherwise the fallback is ChatBrowserUse().

Each step has three phases:

_prepare_context()
→ _get_next_action() + _execute_actions()
→ _post_process()
→ finally: _finalize()

Observation builds the DOM and screenshot in parallel.

_prepare_context() asks the BrowserSession for a BrowserStateSummary(include_screenshot=True). The request is dispatched to the DOMWatchdog.

For an HTTP(S) page, the watchdog starts two tasks:

  • build the serialized DOM state;
  • capture a clean screenshot.

The screenshot is taken without injected element highlights. A DOM failure degrades to an empty SerializedDOMState; a screenshot failure degrades to None. The watchdog then adds tab data, title, viewport and scroll information, pending network requests, PDF state, and popup messages before caching the summary.

“Capture an image” and “send an image to the model” are deliberately separate decisions. More on that shortly.

Four CDP views become one enhanced tree.

DomService._get_all_trees() launches four required observations concurrently:

DOM.getDocument(depth=-1, pierce=True)
Accessibility.getFullAXTree(frameId=...) for every frame
DOMSnapshot.captureSnapshot(
  computedStyles=...,
  includePaintOrder=True,
  includeDOMRects=True
)
devicePixelRatio

It also collects same-origin iframe scroll positions. On pages with at most 10,000 elements it attempts to detect click-related JavaScript listeners by using the DevTools command-line API and resolving those objects back to backend node IDs.

The required calls get a ten-second wait and one shorter retry. If a required result still fails, the DOM build fails as a unit.

The join key is mostly backendNodeId:

  • the DOM tree contributes hierarchy, attributes, frames, content documents, and shadow roots;
  • the accessibility trees contribute role, name, and state;
  • the snapshot contributes bounds, computed style, paint order, and scroll rectangles;
  • target and frame metadata identify the CDP session in which an action must run.

DOM, accessibility, geometry, and vision are not rival techniques here. They answer different questions about the same control.

Serialization is an action compiler.

The LLM does not receive the enhanced tree verbatim. DOMTreeSerializer:

  1. removes scripts, styles, metadata, and most decorative SVG descendants;
  2. retains visible, scrollable, structurally meaningful, or shadow-host nodes;
  3. infers interactivity from native tags, ARIA/AX evidence, listeners, and style;
  4. uses paint order to suppress obscured candidates;
  5. uses bounding-box propagation to collapse redundant wrappers;
  6. adds compound-control details for inputs and selects;
  7. creates the selector map for actionable nodes.

One current-version detail invalidates older explanations of Browser Use:

self._selector_map[node.original_node.backend_node_id] = node.original_node

The “index” shown to the model is now the CDP backend node ID. It is not a newly allocated consecutive counter. A state fragment may look like:

[458]<input type=text placeholder="Name" />
[460]<input type=email placeholder="Email" />
[463]<button type=submit /> Submit

The ID has more browser meaning than a display-only ordinal, but it is still not a permanent locator. Replacing a DOM node can replace its backend ID.

The state message is more than the page.

AgentMessagePrompt composes a single per-step state message containing:

  • the user request;
  • prior goals, memories, and action results;
  • file-system and plan state;
  • sensitive-data placeholders;
  • tabs, page statistics, scroll hints, and serialized interactive elements;
  • page-specific actions;
  • one-time read results and step metadata.

The interactive DOM representation is capped at 40,000 characters. Large tool output can appear once in read_state while a short memory survives. Default compaction uses another LLM after a step cadence and 40,000-character floor, retaining the first and latest six history items plus a bounded summary.

Vision has three modes:

| Setting | Screenshot in the LLM input | |---|---| | True (default) | Included on every normal step | | "auto" | Included only when an action result explicitly requests it | | False | Never included |

The screenshot is still captured for step history even when it is not sent to the decision model. That distinction matters for privacy, storage, and cloud synchronization.

The model returns a bounded action program.

The LLM is called with AgentOutput as its structured output format. The schema includes evaluation of the previous goal, memory, the next goal, optional plan updates, and a non-empty action list.

If the model returns no usable action, Browser Use appends a corrective user message and tries once more. A second empty response is converted into a safe done(success=False) result. Provider or rate-limit errors can switch to one configured fallback LLM; Pydantic validation errors remain step failures.

For the form, the model might produce:

[
  {"input": {"index": 458, "text": "Alice"}},
  {"input": {"index": 460, "text": "alice@example.com"}},
  {"click": {"index": 463}}
]

Output longer than five actions is truncated before execution.

An index becomes a frame-aware CDP action.

The built-in input and click tools resolve the index from the cached selector map. A missing entry produces a result saying that the page may have changed and that the browser state should be refreshed. It does not invoke the historical matching ladder.

The tool dispatches a TypeTextEvent or ClickElementEvent containing the enhanced node. DefaultActionWatchdog can then:

  • select the CDP session associated with that node’s target and frame;
  • scroll the backend node into view;
  • calculate its visible rectangle;
  • resolve the node and compare it with elementFromPoint for occlusion;
  • dispatch mouse events at a visible point;
  • fall back to a JavaScript click when geometry is unavailable or occluded;
  • verify checkbox and radio state transitions;
  • route native selects and file inputs to specialized actions.

This is why “model returns 463” is only the middle of the interaction, not the end.

Revoking the remainder of a stale plan.

Multiple actions save model round trips. They also enlarge the stale-observation window: all actions were planned against one browser state.

The core of multi_act() can be represented by this simplified pseudocode:

# Simplified pseudocode, not a verbatim source excerpt.
for action in model_actions[:5]:
    before = (current_url(), focused_target())
    result = await tools.act(action)

    if result.error or result.is_done:
        break

    if registry[action.name].terminates_sequence:
        break

    after = (current_url(), focused_target())
    if after != before:
        break

Navigation, search, back, tab switch, and arbitrary JavaScript evaluation are statically marked terminates_sequence=True. A click is not. That permits efficient chains such as entering several fields or toggling controls. If a click navigates or changes the focused tab, the runtime guard discards later actions.

The reusable design pattern is:

A model may batch proposals, but the runtime must retain the authority to shorten the batch.

There is a precise hole. Runtime detection compares only the URL and focused target. A same-route React render, modal opening, client-side list replacement, validation message, or iframe-internal mutation may replace nodes without changing either value. A later queued action can therefore use a stale node until CDP resolution fails—or, more dangerously, use a still-valid node whose meaning changed.

The repository already has PageFingerprint, combining URL, element count, and a DOM text hash, for loop and stagnation detection. Open Issue #5137 proposes structured before/after action evidence. Neither currently participates in the immediate sequence fence.

The trade-off is explicit: larger batches reduce LLM latency and increase stale risk; smaller batches re-observe more often; unpredictable custom mutations should terminate the sequence.

Replay identity as a weaker protocol.

At finalization, Browser Use stores the action results and a DOMInteractedElement for each indexed action. That historical record includes:

node and backend IDs
frame ID
node type, name, value, and attributes
bounds
XPath
element hash
stable hash
AX name

When rerun_history() maps an old action to a newly observed page, _update_action_indices() tries:

  1. an exact element hash;
  2. a stable hash with focus, hover, animation, and similar dynamic classes filtered;
  3. XPath;
  4. node type plus accessibility name;
  5. a matching name, id, or aria-label.

The order moves from stronger structural identity toward weaker semantic approximation. If every level fails, replay raises a matching error and follows its retry policy. Dropdown histories even contain special recovery logic that can re-run the previous menu-opening action.

It is not equivalence proof:

  • ancestor-path changes break hashes;
  • insertion and reordering break XPath;
  • accessibility names can collide;
  • generated IDs are unstable;
  • a page can present the same control over different business data;
  • repeating a transaction may be semantically invalid even if the element matches.

The source calls DOMInteractedElement “a bit of a hack.” That comment is more accurate than the phrase “self-healing selector.” Replay preserves intent well enough to attempt another run; it does not provide exactly-once execution.

Event-driven policy hooks and complexity.

BrowserSession connects typed events to watchdogs using bubus. Browser-state construction, screenshots, security, downloads, storage state, permissions, popups, recording, and action execution are separate handlers.

This lets navigation policy run before dispatch and after redirects, wraps clicks with download detection, bounds event timeouts, and shares upper layers across local and remote CDP. It also spreads one action across Agent, Tools, Registry, EventBus, a watchdog, and CDP. “The event was dispatched” is still not “the business state advanced.”

The current ActionResult.metadata can hold click coordinates, but there is no uniform causal verdict such as changed, no_change, navigation, or attention. Issue #5137 asks for exactly that. Today, repeated no-op behavior is often detected only when a later step updates the loop detector.

Safety limits and deployment fit

| Dimension | Browser Use Python Agent | Playwright | Stagehand | Visual computer use | |---|---|---|---|---| | Control flow | LLM proposes action batches; runtime fences them | Developer code | Developer composes observe, act, extract, or Agent | Model emits mouse and keyboard operations | | Target identity | Snapshot-local backend node ID | Locator re-evaluated at action time | observe returns selector, method, and arguments | Screenshot coordinates and visual description | | Mutation handling | Re-observe by step; URL/focus batch fence | Locator retry plus actionability checks | Hybrid AI observation and browser execution | New screenshot and model judgment | | Replay | Best-effort historical reattachment | Deterministic when fixtures and locators are stable | Can cache or reuse observed actions | Layout-sensitive | | Best fit | Unknown sites and long-tail tasks | Stable tests and production workflows | AI flexibility with explicit program orchestration | Canvas, desktop, and interfaces without useful DOM |

Browser Use and Playwright assign responsibility differently: a Playwright developer authors the locator and postcondition; Browser Use asks a model to discover both. Stagehand sits between them: observe() returns structured actions that code can inspect or pass to act().

Pure visual computer use covers canvas and non-browser applications. It pays with weaker addressability and more dependence on image scale and layout. Browser Use can enable coordinate clicking for supported models, but index-only clicking remains the default action schema.

The practical pattern is graduation: discover an unknown flow, then harden valuable paths into an API, Playwright code, or deterministic tool.

Limitations and engineering costs.

Same-document mutations remain the sharpest correctness gap.

The URL/focus fence misses many SPA transitions. The safe response is to lower the action batch size or make mutating custom actions terminal, but both increase latency or integration work.

A click event is not causal evidence.

Browser Use can report that CDP dispatched the click and where. It does not uniformly establish that a form submitted, validation passed, or the intended business record changed. Applications still need postconditions and idempotency at the business layer.

DOM-first representations favor semantically rich pages.

Icon-only controls, canvas, images without alt text, custom components without ARIA, cross-origin frames, and closed shadow roots weaken the action table. Vision helps but adds model cost and privacy exposure. Open Issue #4913 documents these failure classes, though its six-task numbers are community evidence, not an official benchmark.

Replay is not a transaction log.

Replay may rematch elements, retry, reopen menus, and re-run extraction with an LLM. It is inappropriate as an exactly-once mechanism for payment, booking, tax, legal, or medical actions.

Domain policy is not complete agent governance.

The runtime has valuable controls:

  • allowed and prohibited browser domains;
  • IP blocking;
  • per-tool domain restrictions;
  • domain-scoped sensitive placeholders;
  • optional vision disablement;
  • upload-path containment;
  • checks around navigation and redirects.

Those controls do not solve indirect prompt injection: untrusted page content and trusted user instructions still reach the same decision model. Ordinary clicks also have no built-in risk class, spending scope, or mandatory human approval.

Two open issues make the boundary concrete. Issue #4634 proposes a scoped governance layer between model decisions and real actions. Issue #4763 notes that the current SecurityWatchdog._is_url_allowed() unconditionally accepts data: and blob: schemes before applying allowed/prohibited domain rules. I verified that code order in this snapshot; I did not independently validate every exploit claim in the issue.

The runtime rebuilds a world every step.

A step can require a multi-view DOM build, screenshot, LLM call, and judge call; compaction adds another model invocation. Many Chrome instances are memory-intensive, and the README recommends managed infrastructure for production scale.

Deployment fit.

Browser Use fits exploratory QA, operations and research across long-tail websites, workflows without complete APIs, flow discovery, and low-risk internal work with review. It is a poor default for unapproved high-risk transactions, exactly-once mutations, strict cross-browser tests, high-throughput jobs with stable APIs, or environments that cannot isolate profiles, credentials, cookies, and files.

A serious deployment should add a dedicated profile or storage state, domain and egress restrictions, placeholder-based secrets, approval for risky tools, business-level postconditions, idempotency controls, and trace retention.

Operational conclusion.

Browser Use’s most valuable idea is not that it supports many websites. It encodes a fact that demos usually hide:

Page state expires, so action authority should expire with it.

That idea will resonate first with operations automation, QA, and agent-infrastructure teams. Their constraint is often not model intelligence but a landscape of systems with missing APIs and unstable interfaces. The browser becomes a temporary integration layer for the long tail.

Production evaluation should move from “can it click?” to “can it prove why it clicked and what changed?” Target identity, before/after evidence, authorization scope, human approval, and idempotent receipts are more relevant to enterprise adoption than a small increase on an aggregate browser benchmark.

There is a healthier secondary effect. Accessible roles, names, labels, and states help assistive technology, Playwright locators, and browser agents at the same time. Semantic HTML can become a machine-operability advantage instead of being treated only as compliance work.

My additional judgment is that Browser Use is often more valuable as a flow discovery system than as a permanent flow executor. Let the agent navigate the unknown interface and collect successful paths, failure modes, and postconditions. When the workflow becomes stable and economically important, turn its critical steps into deterministic software.

Agent discovers; software hardens. That division of labor is more credible than asking a probabilistic model to own every click forever.

Source map and open questions

References.

Version and date: 0.13.6, source commit 96ec5dc1378d26f7dd155df0cef9d6c3e32752dc, analyzed 2026-07-23. Browser Use changes quickly; the Rust beta, DOM serializer, and security policy are especially likely to evolve.

Open questions.

  1. Can the existing PageFingerprint become a cheap per-action DOM mutation fence without rebuilding the complete page after every input?
  2. Can replay synthesize semantic locators with a uniqueness score and require human confirmation when identity evidence is ambiguous?
  3. If every action capability carried an origin, risk class, expiry, and before-state hash, could Browser Use produce verifiable authorization receipts rather than ordinary click logs?