| name | browser |
| description | Direct browser control via CDP. Use when the user wants to automate, scrape, test, or interact with web pages. Connects to the user's already-running Chrome. |
browser-harness
Direct browser control via CDP. For task-specific edits, use agent-workspace/agent_helpers.py and agent-workspace/domain-skills/. For setup, install, or connection problems, read install.md.
Usage
PowerShell:
@'
new_tab("https://docs.browser-use.com")
wait_for_load()
print(page_info())
'@ | browser-harness
Bash/zsh:
browser-harness -c '
new_tab("https://docs.browser-use.com")
wait_for_load()
print(page_info())
'
- Invoke as browser-harness — it's on $PATH. No cd, no uv run.
- First navigation is new_tab(url), not goto_url(url) — goto runs in the user's active tab and clobbers their work.
- On Windows/PowerShell, pipe a single-quoted here-string (
@' ... '@) into browser-harness. Do not use Bash heredocs there.
- If the user explicitly asks to use browser-harness, do the browsing in Chrome through this tool path. Open and activate tabs with new_tab(url), then read page_info(), page_text(), snippets(), page_links(), or screenshots from the real browser session.
Available interaction skills:
- interaction-skills/connection.md — startup sequence, tab visibility, omnibox popup fix, Chrome for Testing CDP fallback
Available domain skills:
- tiktok/upload.md
- polymarket/scraping.md
Tool call shape
PowerShell:
@'
# any python. helpers pre-imported. daemon auto-starts.
'@ | browser-harness
Bash/zsh:
browser-harness -c '
# any python. helpers pre-imported. daemon auto-starts.
'
run.py calls ensure_daemon() before exec — you never start/stop manually unless you want to.
Bitter-lesson workflow
Keep the harness thin and visible:
- Prefer raw
cdp("Domain.method", ...) when a helper is not enough.
- If a useful primitive is missing, read/edit
helpers.py directly and rerun. Do not build a manager layer, retry framework, selector abstraction, or hidden browser framework.
- Use
browser-harness --paths to find the editable harness files, daemon log, and endpoint.
- Use
browser_state() for a compact target/session/page/event dump before inventing recovery code.
- Use
browser_pressure() when tabs/RAM may be getting high; it is a quiet data helper, so only mention it to the user when it reports warn/critical or changes your next action.
- Use
reuse_or_new_tab(url) instead of new_tab(url) when repeated searches or source checks might create tab pressure.
- Use
close_harness_tabs() only for clearly technical tabs (about:blank, chrome://inspect); do not close arbitrary user pages without asking.
- Use
current_session(), use_session(session_id), and attach_target(target_id) when CDP target/session issues surface. Let Chrome/CDP errors stay visible so the agent can repair the exact failing path.
Remote browsers
Use remote for parallel sub-agents (each gets its own isolated browser via a distinct BU_NAME) or on a headless server. BROWSER_USE_API_KEY must be set. start_remote_daemon, list_cloud_profiles, list_local_profiles, sync_local_profile are pre-imported.
browser-harness -c '
start_remote_daemon("work") # default — clean browser, no profile
# start_remote_daemon("work", profileName="my-work") # reuse a cloud profile (already logged in)
# start_remote_daemon("work", profileId="<uuid>") # same, but by UUID
# start_remote_daemon("work", proxyCountryCode="de", timeout=120) # DE proxy, 2-hour timeout
# start_remote_daemon("work", proxyCountryCode=None) # disable the Browser Use proxy
'
BU_NAME=work browser-harness -c '
new_tab("https://example.com")
print(page_info())
'
start_remote_daemon prints liveUrl and auto-opens it in the local browser (if a GUI is detected) so the user can watch along. Headless servers print only — share the URL with the user. The daemon PATCHes the cloud browser to stop on shutdown, which persists profile state. Running remote daemons bill until timeout.
Profiles (cookies-only login state) live in interaction-skills/profile-sync.md — covers list_cloud_profiles(), the chat-driven "which profile?" pattern, and sync_local_profile() for uploading a local Chrome profile.
Search first
Search agent-workspace/domain-skills/ first for the domain you are working on before inventing a new approach.
Only if you start struggling with a specific mechanic while navigating, look in interaction-skills/ for helpers. The available interaction skills are:
- cookies.md
- cross-origin-iframes.md
- dialogs.md
- downloads.md
- drag-and-drop.md
- dropdowns.md
- iframes.md
- network-requests.md
- print-as-pdf.md
- profile-sync.md
- screenshots.md
- scrolling.md
- shadow-dom.md
- tabs.md
- uploads.md
- viewport.md
Useful commands:
rg --files agent-workspace/domain-skills
rg -n "tiktok|upload" agent-workspace/domain-skills
Always contribute back
If you learned anything non-obvious about how a site works, open a PR to agent-workspace/domain-skills/<site>/ before you finish. Default to contributing. The harness gets better only because agents file what they learn. If figuring something out cost you a few steps, the next run should not pay the same tax.
Examples of what's worth a PR:
- A private API the page calls (XHR/fetch endpoint, request shape, auth) — often 10× faster than DOM scraping.
- A stable selector that beats the obvious one, or an obfuscated CSS-module class to avoid.
- A framework quirk — "the dropdown is a React combobox that only commits on Escape", "this Vue list only renders rows inside its own scroll container, so scrollIntoView on the row doesn't work — you have to scroll the container".
- A URL pattern — direct route, required query params (?lang=en, ?th=1), a variant that skips a loader.
- A wait that wait_for_load() misses, with the reason.
- A trap — stale drafts, legacy IDs that now return null, unicode quirks, beforeunload dialogs, CAPTCHA surfaces.
What a domain skill should capture
The durable shape of the site — the map, not the diary. Focus on what the next agent on this site needs to know before it starts:
- URL patterns and query params.
- Private APIs and their payload shape.
- Stable selectors (data-, aria-, role, semantic classes).
- Site structure — containers, items per page, framework, where state lives.
- Framework/interaction quirks unique to this site.
- Waits and the reasons they're needed.
- Traps and the selectors that don't work.
Do not write
- Raw pixel coordinates. They break on viewport, zoom, and layout changes. Describe how to locate the target (selector, scrollIntoView, aria-label, visible text) — never where it happened to be on your screen.
- Run narration or step-by-step of the specific task you just did.
- Secrets, cookies, session tokens, user-specific state.
agent-workspace/domain-skills/ is shared and public.
What actually works
- Screenshots first: use capture_screenshot() to understand the current page quickly, find visible targets, and decide whether you need a click, a selector, or more navigation.
- Clicking: capture_screenshot() → read the pixel off the image → click_at_xy(x, y) → capture_screenshot() to verify. Suppress the Playwright-habit reflex of "locate first, then click" — no getBoundingClientRect, no selector hunt. Drop to DOM only when the target has no visible geometry (hidden input, 0×0 node). Hit-testing happens in Chrome's browser process, so clicks go through iframes / shadow DOM / cross-origin without extra work.
- Bulk HTTP: http_get(url) + ThreadPoolExecutor. No browser for static pages (249 Netflix pages in 2.8s).
- After goto: wait_for_load().
- Tab/RAM guardrails:
browser_pressure() returns {tabs, real_tabs, internal_tabs, chrome_processes, rss_mb, pressure, suggestion} without printing. reuse_or_new_tab(url) reuses exact/same-host tabs before opening another tab. close_harness_tabs() closes only technical harness tabs by default.
- Wrong/stale tab: ensure_real_tab(). Use it when the current tab is stale or internal; the daemon also auto-recovers from stale sessions on the next call.
- Verification: print(page_info()) is the simplest "is this alive?" check, but screenshots are the default way to verify whether a visible action actually worked.
- DOM reads: use page_text(), snippets([...]), page_links(), or js(...) for inspection and extraction when the screenshot shows that coordinates are the wrong tool.
- JS data: pass Python values to
js() as positional args and read them with JS arguments; pass iframe/target ids only as target_id=....
- Target/session debugging: use browser_state(), current_session(), attach_target(target_id), and raw Target.* CDP calls.
- Iframe sites (Azure blades, Salesforce): click_at_xy(x, y) passes through; only drop to iframe DOM work when coordinate clicks are the wrong tool.
- Auth wall: redirected to login → stop and ask the user. Don't type credentials from screenshots.
- Raw CDP for anything helpers don't cover: cdp("Domain.method", params).
Design constraints
- Coordinate clicks default. Input.dispatchMouseEvent goes through iframes/shadow/cross-origin at the compositor level.
- Connect to the user's running Chrome. Don't launch your own browser.
- cdp-use is only for CDPClient.send_raw. Prefer raw CDP strings over typed wrappers.
- run.py stays tiny. No argparse, subcommands, or extra control layer.
- Core helpers stay short. Put task-specific helper additions in
agent-workspace/agent_helpers.py; daemon/bootstrap and remote session admin live in the core package.
- Don't add a manager layer. No retries framework, session manager, daemon supervisor, config system, or logging framework.
Gotchas (field-tested)
- Omnibox popups are fake page targets. Filter chrome://omnibox-popup... and other internals when you need a real tab.
- CDP target order != Chrome's visible tab-strip order. Use UI automation when the user means "the first/second tab I can see"; Target.activateTarget only shows a known target.
- Default daemon sessions can go stale. ensure_real_tab() re-attaches to a real page.
- Browser Use API is camelCase on the wire. cdpUrl, proxyCountryCode, etc.
- Remote cdpUrl is HTTPS, not ws. Resolve the websocket URL via /json/version.
- Stop cloud browsers with PATCH /browsers/{id} + {"action":"stop"}.
- After every meaningful action, re-screenshot before assuming it worked. Use the image to verify changed state, open menus, navigation, visible errors, and whether the page is in the state you expected.
- Use screenshots to drive exploration. They are often the fastest way to find the next click target, notice hidden blockers, and decide if a selector is even worth writing.
- Prefer compositor-level actions over framework hacks. Try screenshots, coordinate clicks, and raw key input before adding DOM-specific workarounds.
- If you need framework-specific DOM tricks, check interaction-skills/ first. That is where dropdown, dialog, iframe, shadow DOM, and form-specific guidance belongs.
Interaction notes
- interaction-skills/ holds reusable UI mechanics such as dialogs, tabs, dropdowns, iframes, and uploads.
agent-workspace/domain-skills/ holds site-specific workflows and should be updated when you discover reusable patterns for a website.