Overview

BrowserSession in browser_use/browser/session.py does not call browser helpers directly. It dispatches typed events onto a bus, watchdogs subscribe to the event types they understand, perform the work over CDP or local browser state, and return typed results that the caller awaits. That model keeps browser responsibilities split apart, applies event-specific timeouts from browser_use/browser/events.py, and gives the session one recovery path when the browser connection drops.

The bus also keeps event history and parent IDs. That lineage turns a browser interaction into a tree of cause and effect instead of a flat log stream.

flowchart TB
  Tools[Tools layer\nbrowser_use/tools/service.py]
  Agent[Agent\nbrowser_use/agent/service.py]
  Bus((BrowserSession bus\nbrowser_use/browser/session.py))

  subgraph Perception[Perception]
    DOM[DOMWatchdog]
  end

  subgraph Actions[Action execution]
    ACT[DefaultActionWatchdog]
  end

  subgraph Lifecycle[Lifecycle]
    Local[LocalBrowserWatchdog]
    Blank[AboutBlankWatchdog]
  end

  subgraph Monitors[Environment monitors]
    Download[DownloadsWatchdog]
    Popups[PopupsWatchdog]
    Security[SecurityWatchdog]
    Perms[PermissionsWatchdog]
  end

  subgraph State[State and capture]
    Storage[StorageStateWatchdog]
    Shot[ScreenshotWatchdog]
    Record[RecordingWatchdog]
    Har[HarRecordingWatchdog]
  end

  subgraph Extras[Conditional extras]
    Captcha[CaptchaWatchdog]
  end

  Tools -->|dispatch| Bus
  Agent -->|dispatch| Bus

  Bus -->|BrowserStateRequestEvent| Req[BrowserStateRequestEvent]
  Req --> DOM
  DOM -->|BrowserStateSummary| Result[BrowserStateSummary]

  Bus --> ACT
  Bus --> Local
  Bus --> Blank
  Bus --> Download
  Bus --> Popups
  Bus --> Security
  Bus --> Perms
  Bus --> Storage
  Bus --> Shot
  Bus --> Record
  Bus --> Har
  Bus --> Captcha

  DOM --> CDP[CDP / Chrome]
  ACT --> CDP

  classDef highlight fill:#ffe08a,stroke:#b26a00,stroke-width:2px;
  class Req,DOM,Result highlight;

The bus itself

The browser layer uses bubus.EventBus as its event engine. BrowserSession wraps that bus with ResilientEventBus in browser_use/browser/session.py so shutdown and reconnect paths can keep stepping the bus without falling over a torn-down instance. dispatch() returns an awaitable event object, so the caller waits for the handlers to finish and then reads the typed result from event.event_result().

Two buses live in the repository, and they solve different problems. browser_use/agent/service.py owns a separate bus for agent lifecycle, step, and cloud-sync events. BrowserSession owns the browser bus for page work, tab work, and CDP work. Keeping those buses separate keeps browser interaction out of the agent control plane.

browser_use/browser/events.py defines the browser events as typed BaseEvent[...] models. Each event carries typed fields, and many events also expose an event_timeout that reads a TIMEOUT_*Event environment variable through _get_timeout().

The watchdog contract

browser_use/browser/watchdog_base.py defines the contract for every watchdog. Each watchdog declares LISTENS_TO and EMITS, then implements handlers named on_<EventName>. BaseWatchdog.attach_to_session() scans those methods, matches them against the event classes in browser_use/browser/events.py, and registers them on the browser bus. BrowserSession.attach_all_watchdogs() in browser_use/browser/session.py uses that contract to wire in the full fleet.

The wrapper around each handler acts like a circuit breaker. It skips non lifecycle events while browser_session.is_cdp_connected is false, waits for browser_session._reconnect_event when browser_session.is_reconnecting is true, and retries session recovery through get_or_create_cdp_session() when a handler fails but the browser still looks recoverable. Lifecycle events such as BrowserStartEvent, BrowserStopEvent, BrowserStoppedEvent, BrowserLaunchEvent, BrowserKillEvent, BrowserReconnectingEvent, BrowserReconnectedEvent, and BrowserErrorEvent still run during disconnects.

The same wrapper also walks event_bus.event_history to print parent and grandparent lineage. That gives each handler a short history of what triggered it, which matters when one event dispatches another event from inside a handler.

The watchdog fleet

browser_use/browser/session.py attaches the fleet so the session stays thin. Each watchdog owns one concern and stays out of the others.

Perception

browser_use/browser/watchdogs/dom_watchdog.py acts as the perception hub. It listens for BrowserStateRequestEvent, builds BrowserStateSummary, coordinates screenshot capture by dispatching ScreenshotEvent, and keeps the cached DOM state and selector map ready for the rest of the browser layer. That path is the core of How the Agent Sees the Page .

Action execution

browser_use/browser/watchdogs/default_action_watchdog.py handles the main browser actions: click, type, scroll, upload, and related navigation work. It receives resolved EnhancedDOMTreeNode values from browser_use/browser/events.py, turns them into CDP calls, and keeps downloads, focus changes, and field behavior separate from the session object. That path underpins The CDP Execution Layer .

Browser lifecycle

browser_use/browser/watchdogs/local_browser_watchdog.py launches and kills the local browser subprocess. browser_use/browser/watchdogs/aboutblank_watchdog.py keeps about:blank tabs alive and gives the browser a visible waiting state. browser_use/browser/watchdogs/crash_watchdog.py is currently commented out in browser_use/browser/session.py, so it is inactive. This separation keeps start, stop, and recovery code out of the main session body.

Environment monitors

browser_use/browser/watchdogs/downloads_watchdog.py handles safe downloads and emits FileDownloadedEvent. browser_use/browser/watchdogs/popups_watchdog.py handles JavaScript dialogs and records their messages. browser_use/browser/watchdogs/security_watchdog.py enforces allowed and prohibited domains, then blocks or redirects blocked navigation. browser_use/browser/watchdogs/permissions_watchdog.py grants configured permissions as soon as the browser connects. Each watchdog guards one boundary, so one boundary can fail without masking the others.

State and capture

browser_use/browser/watchdogs/storage_state_watchdog.py keeps cookies and storage state on disk and loads them again on connect. browser_use/browser/watchdogs/screenshot_watchdog.py captures screenshot data for ScreenshotEvent. browser_use/browser/watchdogs/recording_watchdog.py records video, and browser_use/browser/watchdogs/har_recording_watchdog.py writes network activity to HAR. Each file owns a different kind of evidence: auth state, pixels, video, or network history.

Conditional extras

browser_use/browser/watchdogs/captcha_watchdog.py watches the proxy captcha solver events and exposes wait_if_captcha_solving(). That watchdog stays optional because only some deployments surface captcha signals, but the event path still fits the same bus model.

This split keeps the session object small and keeps the browser layer from turning into a single large handler.

The event vocabulary

browser_use/browser/events.py defines the vocabulary that all browser work uses. The file models actions and lifecycle changes as typed BaseEvent classes, and ElementSelectedEvent gives element-targeted actions a resolved EnhancedDOMTreeNode instead of a raw selector index. That design lets handlers work on the actual node that the DOM service resolved.

Representative element-targeted events include ClickElementEvent, TypeTextEvent, ScrollEvent, UploadFileEvent, GetDropdownOptionsEvent, and SelectDropdownOptionEvent. Representative lifecycle events include BrowserStartEvent, BrowserStopEvent, BrowserConnectedEvent, BrowserStoppedEvent, BrowserLaunchEvent, BrowserKillEvent, TabCreatedEvent, TabClosedEvent, NavigationStartedEvent, NavigationCompleteEvent, and AgentFocusChangedEvent. The set stays broad enough for the browser layer, but it still keeps each event narrow and typed.

Watchdogs can dispatch more events from inside a handler, so cascades stay in the same lineage tree. SecurityWatchdog emits BrowserErrorEvent when it blocks navigation. StorageStateWatchdog dispatches load and save events around browser connect and stop. PopupsWatchdog records dialog handling, and CaptchaWatchdog turns proxy events into browser events. Parent lineage keeps those cascades readable.

Upward flow from Tools and Agent

The Tools class in browser_use/tools/service.py turns agent actions into browser events. The sibling page The Tools layer covers the same dispatch path. It dispatches events such as NavigateToUrlEvent, ClickElementEvent, ClickCoordinateEvent, TypeTextEvent, ScrollEvent, UploadFileEvent, SwitchTabEvent, and CloseTabEvent, then waits for each result before continuing. That keeps the action layer thin and keeps browser details inside the browser bus.

The agent uses the same round-trip for perception. Anatomy of a Step covers the perception round-trip: BrowserSession.get_browser_state_summary() dispatches BrowserStateRequestEvent, DOMWatchdog assembles the page state, and the caller receives BrowserStateSummary. The agent bus in browser_use/agent/service.py stays separate so agent lifecycle and cloud-sync events do not mix with browser interaction.

The tradeoff is indirection. Control flow moves into the bus, so reading the code requires following event names instead of a direct call stack. Timeout tuning also needs care: browser_use/browser/events.py and browser_use/browser/watchdogs/captcha_watchdog.py read TIMEOUT_*Event environment variables, which lets the system stretch or shrink one event without changing the others. The public timeout reference at all parameters documents those variables.

Where to look in the code

  • browser_use/browser/session.pyBrowserSession, ResilientEventBus, watchdog attachment, and CDP reconnect flow.
  • browser_use/browser/watchdog_base.py — watchdog registration, reconnect guard, and lineage logging.
  • browser_use/browser/events.py — typed browser events, ElementSelectedEvent, and timeout defaults.
  • browser_use/browser/watchdogs/dom_watchdog.py — browser state assembly and screenshot coordination.
  • browser_use/browser/watchdogs/default_action_watchdog.py — CDP execution for click, type, scroll, upload, and related actions.
  • browser_use/tools/service.py — the Tools dispatcher that sends browser events and waits for their results.