Chapter 05 · Browser Use To Stagehand
Subchapter 5.5
references/trace-assisted.mdMarkdown6 KBView on GitHub
A static read of a browser-use script tells you what it was asked to do. Running it once on Browserbase tells you what it actually did — the real URLs, network calls, and page transitions. For opaque or flaky scripts, that observed behavior makes the Stagehand rewrite far more accurate.
This is the optional path. Skip it for simple scripts; reach for it when the source is a large
agent(task="…") blob whose actual flow is unclear, or when a migration’s first rewrite doesn’t
reproduce the original behavior.
Sibling skill: this repo’s
browser-traceproductizes CDP trace capture — the full DevTools firehose, screenshots, and DOM dumps, bisected into per-page searchable buckets, and able to attach to a live Browserbase session. Use it to capture the browser-use run, then feed the per-page summaries into the rewrite. The Session Logs API below is the lighter-weight alternative when you just need navigations + network calls.
The original idea was “attach a browser-trace as a CDP listener.” On Browserbase, the machine-readable trace is the Session Logs API, not the session recording:
bb.sessions.logs.list(sessionId) returns CDP events: network
activity, console logs, and page lifecycle (navigations). This is the structured trace you
parse to reconstruct the flow.So: logs to drive the rewrite, video to eyeball it. Don’t promise a DOM-event stream from the recording — it isn’t that anymore.
browser-use connects to any remote browser via cdp_url, and Browserbase is officially supported.
Point the existing script at a Browserbase session; record it and tag it for later retrieval.
import os
from browserbase import Browserbase
from browser_use import Agent, Browser, BrowserProfile, ChatAnthropic
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create(
project_id=os.environ["BROWSERBASE_PROJECT_ID"],
browser_settings={"record_session": True}, # keep recording on (default)
user_metadata={"framework": "browser-use", "migration": "true"},
)
print(f"Session: https://www.browserbase.com/sessions/{session.id}")
# The ONLY change to the original script: hand it the Browserbase CDP endpoint
browser = Browser(browser_profile=BrowserProfile(cdp_url=session.connect_url))
agent = Agent(task="<the original task, unchanged>", llm=ChatAnthropic(model="claude-sonnet-4-6"), browser=browser)
await agent.run()
await browser.stop()Notes:
session.id — it’s how you pull logs and the recording next.logs = bb.sessions.logs.list(session.id) # CDP network / console / lifecycle events
recording = bb.sessions.recording.retrieve(session.id) # video (for human QA)TypeScript equivalent with @browserbasehq/sdk:
const logs = await bb.sessions.logs.list(sessionId);From the logs, extract the spine of the flow:
page.goto(...) calls in the rewrite, and the URLs
to anchor each phase.You can also open the Session Inspector in the dashboard for an Events/Pages timeline and, if the run used Stagehand, act/extract results and token usage.
Map what you observed onto the determinism spectrum (see determinism.md):
page.goto(...).act("…"), or observe()→act() for repeatable steps.extract("…", schema).browserbaseSessionCreateParams (region/proxies/context) so behavior matches the
baseline run.Run the Stagehand rewrite under env: "BROWSERBASE" and compare its logs/recording to the
browser-use baseline: same navigations, same end state, same extracted data. Reuse the same
Context so auth carries over and you’re comparing like with like.
| Use trace-assisted | Skip (static rewrite is enough) |
|---|---|
One giant agent(task="…") whose real flow is unclear | Script already reads as clear, ordered steps |
| Script is flaky / behavior varies run to run | Deterministic, well-understood script |
| First static rewrite doesn’t reproduce the original | Simple task / extraction |
| Auth, redirects, or heavy network make the path opaque | No login, single domain |
browser-trace skill) over wiring up
live listeners.