[STG-2850] Add Browser Use to v4 migration guide - #2756
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
There was a problem hiding this comment.
cubic analysis
4 issues found across 2 files
Confidence score: 3/5
packages/docs/v4/migrations/browser-use.mdxshows the Python Models example passingModelConfig(...)toStagehand.create(), although Python expects a model string and separatemodel_api_key; following the migration can prevent users from creating Stagehand. Update the example to the supported Python signature.packages/docs/v4/migrations/browser-use.mdxcombines Python and TypeScript call shapes in the connection mapping, showing Python readers an invalidlocal_browser.connect({ cdpUrl })example; split the language-specific mappings and usecdp_urlfor Python.packages/docs/v4/migrations/browser-use.mdxuses TypeScript option spelling in the Python quick-reference row, making the documented migration target invalid; change the row to Python keyword syntax withcdp_url.packages/docs/v4/migrations/browser-use.mdxdocuments the non-existent hybrid calllocal_browser.launch({ headless: false }), which may leave Python users with an unusable launch example; keep the call consistently in Python syntax or switch it fully to TypeScript.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/docs/v4/migrations/browser-use.mdx">
<violation number="1" location="packages/docs/v4/migrations/browser-use.mdx:385">
P2: This migration sentence combines Python and TypeScript APIs in one call shape, so Python readers get an invalid `local_browser.connect({ cdpUrl })` example. Split the mapping by language and use `cdp_url` for Python.
(Based on your team's feedback about SDK-native field spellings.) .</violation>
<violation number="2" location="packages/docs/v4/migrations/browser-use.mdx:402">
P1: The Python example in the Models section passes a `ModelConfig(...)` object to `Stagehand.create()`, but the Python SDK's `create()` takes a `model: str` plus a separate `model_api_key` argument; `ModelConfig` is the Go/TypeScript form. As written this example won't run (a `ModelConfig` is neither a model-name string nor a callable LLM). Use `model="anthropic/claude-sonnet-4-6"` with `model_api_key=os.environ["ANTHROPIC_API_KEY"]` to match the actual Python API documented in configuration/models.mdx.</violation>
<violation number="3" location="packages/docs/v4/migrations/browser-use.mdx:510">
P2: This row shows a non-existent hybrid API: `local_browser.launch({ headless: false })`. Keep Python names with Python argument syntax, or switch entirely to TypeScript naming.
(Based on your team's feedback about SDK-native field spellings.) .</violation>
<violation number="4" location="packages/docs/v4/migrations/browser-use.mdx:543">
P2: This quick-reference mapping uses TypeScript option spelling inside the Python SDK call, so the documented migration target is invalid for Python. Use `cdp_url` keyword syntax in this row.</violation>
</file>
Linked issue analysis
Linked issue: STG-2850: Add Browser Use to v4 migration guide
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Create a Browser Use → Stagehand v4 migration guide | The new browser-use.mdx guide provides migration guidance, examples, breaking-change mappings, troubleshooting, and references for both Python and TypeScript. |
| ✅ | Make the migration guide discoverable in the v4 documentation | The new guide is added to the v4 Migration guide group in the documentation navigation. |
Architecture diagram
sequenceDiagram
participant Dev as Developer/Migration Reader
participant Docs as v4 Docs (Mintlify)
participant DocsJSON as docs.json Config
participant BrowserUse as Browser Use Agent
participant Stagehand as Stagehand v4 SDK
participant Browser as Browser Context
participant LLM as LLM (e.g., GPT-4)
Note over Dev,Docs: NEW: v4 Migration Guide for Browser Use users
Dev->>Docs: Navigates to v4/migrations/browser-use
Docs->>DocsJSON: CHANGED: Resolves page in nav config
DocsJSON-->>Docs: Returns page reference
Docs-->>Dev: Renders migration guide
rect rgb(240, 240, 240)
Note over Dev,LLM: Scenario A: Original Browser Use Pattern
Dev->>BrowserUse: Agent(task="...", llm=...)
BrowserUse->>Browser: Auto-manages browser session
BrowserUse->>LLM: Per-step inference calls
LLM-->>BrowserUse: Action decisions
BrowserUse-->>Dev: Task result
end
rect rgb(240, 240, 240)
Note over Dev,LLM: Scenario B: Stagehand v4 Pattern (Code Mode)
Dev->>Stagehand: browserbase.launch(api_key)
Stagehand->>Browser: Creates browser instance
Dev->>Stagehand: Stagehand.create(browser)
Stagehand->>Browser: Attaches runtime
Dev->>Browser: page.locator() / page.goto()
Browser-->>Dev: Deterministic results
opt Model needed
Dev->>Stagehand: stagehand.act("...")
Stagehand->>LLM: Single inference call
LLM-->>Stagehand: Action
Stagehand-->>Dev: Result
end
end
rect rgb(240, 240, 240)
Note over Dev,LLM: Scenario C: Tool Calling Pattern
Dev->>Stagehand: Exposes API as tools
Stagehand->>Browser: page.snapshot() returns xpathMap
Browser-->>Stagehand: Accessibility tree + selectors
Stagehand->>LLM: Model reads page structure
LLM-->>Stagehand: Selects tool + selector
Stagehand->>Browser: Executes locator action
Browser-->>Stagehand: Result
Stagehand-->>Dev: Final response
end
Note over Dev,Docs: Includes: code mode, tool calling, migration diffs,<br/>env vars, custom tools, troubleshooting
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
Re-trigger cubic
| + model=ModelConfig( | ||
| + model_name="anthropic/claude-sonnet-4-6", | ||
| + api_key=os.environ["ANTHROPIC_API_KEY"], | ||
| + ), | ||
| + ) |
There was a problem hiding this comment.
P1: The Python example in the Models section passes a ModelConfig(...) object to Stagehand.create(), but the Python SDK's create() takes a model: str plus a separate model_api_key argument; ModelConfig is the Go/TypeScript form. As written this example won't run (a ModelConfig is neither a model-name string nor a callable LLM). Use model="anthropic/claude-sonnet-4-6" with model_api_key=os.environ["ANTHROPIC_API_KEY"] to match the actual Python API documented in configuration/models.mdx.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/docs/v4/migrations/browser-use.mdx, line 402:
<comment>The Python example in the Models section passes a `ModelConfig(...)` object to `Stagehand.create()`, but the Python SDK's `create()` takes a `model: str` plus a separate `model_api_key` argument; `ModelConfig` is the Go/TypeScript form. As written this example won't run (a `ModelConfig` is neither a model-name string nor a callable LLM). Use `model="anthropic/claude-sonnet-4-6"` with `model_api_key=os.environ["ANTHROPIC_API_KEY"]` to match the actual Python API documented in configuration/models.mdx.</comment>
<file context>
@@ -0,0 +1,578 @@
+- agent = Agent(task="...", llm=ChatAnthropic(model="claude-sonnet-4-0"))
++ stagehand = await Stagehand.create(
++ browser=browser,
++ model=ModelConfig(
++ model_name="anthropic/claude-sonnet-4-6",
++ api_key=os.environ["ANTHROPIC_API_KEY"],
</file context>
| + model=ModelConfig( | |
| + model_name="anthropic/claude-sonnet-4-6", | |
| + api_key=os.environ["ANTHROPIC_API_KEY"], | |
| + ), | |
| + ) | |
| stagehand = await Stagehand.create( | |
| browser=browser, | |
| model="anthropic/claude-sonnet-4-6", | |
| model_api_key=os.environ["ANTHROPIC_API_KEY"], | |
| ) |
| | `use_vision=True` | `screenshot: true` on an `extract()` call | | ||
| | `generate_gif=True` | Browserbase [session recording](/v4/configuration/observability) | | ||
| | `Browser(...)` / `BrowserSession(...)` | `local_browser.launch()` or `browserbase.launch()` | | ||
| | `Browser(cdp_url=...)` | `local_browser.connect({ cdpUrl })` | |
There was a problem hiding this comment.
P2: This quick-reference mapping uses TypeScript option spelling inside the Python SDK call, so the documented migration target is invalid for Python. Use cdp_url keyword syntax in this row.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/docs/v4/migrations/browser-use.mdx, line 543:
<comment>This quick-reference mapping uses TypeScript option spelling inside the Python SDK call, so the documented migration target is invalid for Python. Use `cdp_url` keyword syntax in this row.</comment>
<file context>
@@ -0,0 +1,578 @@
+| `use_vision=True` | `screenshot: true` on an `extract()` call |
+| `generate_gif=True` | Browserbase [session recording](/v4/configuration/observability) |
+| `Browser(...)` / `BrowserSession(...)` | `local_browser.launch()` or `browserbase.launch()` |
+| `Browser(cdp_url=...)` | `local_browser.connect({ cdpUrl })` |
+| `Browser(allowed_domains=...)` | `browser.context.setDomainPolicy({ allowedDomains })` |
+| `Browser(storage_state=...)` | Cookie API, or a Browserbase context |
</file context>
| | `Browser(cdp_url=...)` | `local_browser.connect({ cdpUrl })` | | |
| | `Browser(cdp_url=...)` | `local_browser.connect(cdp_url="...")` | |
|
|
||
| | Browser Use | Stagehand v4 | | ||
| | --- | --- | | ||
| | `Browser(headless=False)` | `local_browser.launch({ headless: false })` | |
There was a problem hiding this comment.
P2: This row shows a non-existent hybrid API: local_browser.launch({ headless: false }). Keep Python names with Python argument syntax, or switch entirely to TypeScript naming.
(Based on your team's feedback about SDK-native field spellings.) .
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/docs/v4/migrations/browser-use.mdx, line 510:
<comment>This row shows a non-existent hybrid API: `local_browser.launch({ headless: false })`. Keep Python names with Python argument syntax, or switch entirely to TypeScript naming.
(Based on your team's feedback about SDK-native field spellings.) .</comment>
<file context>
@@ -0,0 +1,578 @@
+
+| Browser Use | Stagehand v4 |
+| --- | --- |
+| `Browser(headless=False)` | `local_browser.launch({ headless: false })` |
+| `Browser(cdp_url="http://localhost:9222")` | `local_browser.connect({ cdpUrl: "http://localhost:9222" })` |
+| `Browser(proxy=ProxySettings(...))` | `proxy` on `local_browser.launch()`, or Browserbase proxies |
</file context>
| | `Browser(headless=False)` | `local_browser.launch({ headless: false })` | | |
| | `Browser(headless=False)` | `local_browser.launch(headless=False)` | |
There was a problem hiding this comment.
I'd make all of the table into python syntax because that's what the migration guide mentions at the top (and what BU is)
| </Tab> | ||
| </Tabs> | ||
|
|
||
| Use `local_browser.launch()` for a browser on your machine, `browserbase.launch({ apiKey })` for a hosted one, and `local_browser.connect({ cdpUrl })` or `browserbase.connect({ apiKey, sessionId })` to attach to one that's already running. `browserbase.launch()` is what enables [server-side caching](/v4/best-practices/caching) and the [Model Gateway](/v4/configuration/models#model-gateway). Stagehand closes only the browsers it launched, so `stagehand.close()` leaves the browser running and you call `browser.close()` yourself. See [browser configuration](/v4/configuration/browser). |
There was a problem hiding this comment.
P2: This migration sentence combines Python and TypeScript APIs in one call shape, so Python readers get an invalid local_browser.connect({ cdpUrl }) example. Split the mapping by language and use cdp_url for Python.
(Based on your team's feedback about SDK-native field spellings.) .
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/docs/v4/migrations/browser-use.mdx, line 385:
<comment>This migration sentence combines Python and TypeScript APIs in one call shape, so Python readers get an invalid `local_browser.connect({ cdpUrl })` example. Split the mapping by language and use `cdp_url` for Python.
(Based on your team's feedback about SDK-native field spellings.) .</comment>
<file context>
@@ -0,0 +1,578 @@
+</Tab>
+</Tabs>
+
+Use `local_browser.launch()` for a browser on your machine, `browserbase.launch({ apiKey })` for a hosted one, and `local_browser.connect({ cdpUrl })` or `browserbase.connect({ apiKey, sessionId })` to attach to one that's already running. `browserbase.launch()` is what enables [server-side caching](/v4/best-practices/caching) and the [Model Gateway](/v4/configuration/models#model-gateway). Stagehand closes only the browsers it launched, so `stagehand.close()` leaves the browser running and you call `browser.close()` yourself. See [browser configuration](/v4/configuration/browser).
+
+### The task string becomes explicit steps
</file context>
| - **[Code mode](#code-mode)** puts the model in front of the run, not inside it. A coding assistant writes a Stagehand script once; you run that script every time after. Browserbase recommends starting here. | ||
| - **[Tool calling](#tool-calling)** keeps a model in the loop at runtime, the way Browser Use does, but drives the browser through the full Stagehand API as its tools instead of one broad task string. | ||
|
|
||
| Either way, `act()`, `extract()`, and `observe()` stay in your toolbox for the steps that genuinely need a model. You just stop handing a model the entire task. |
There was a problem hiding this comment.
| Either way, `act()`, `extract()`, and `observe()` stay in your toolbox for the steps that genuinely need a model. You just stop handing a model the entire task. | |
| `act()`, `extract()`, and `observe()` stay available for the steps where a natural-language instruction beats a selector. |
|
|
||
| - A browser factory (`browserbase.launch()` or `local_browser.launch()`) replaces the implicit browser inside `Agent`, and `Stagehand.create()` attaches the runtime to it. | ||
| - The task string is gone. You write the steps. | ||
| - Where Browser Use would have spent a model call reading the page, a `page.locator()` does it for free. Spend `act()` and `extract()` only where the page needs judgement. |
There was a problem hiding this comment.
| - Where Browser Use would have spent a model call reading the page, a `page.locator()` does it for free. Spend `act()` and `extract()` only where the page needs judgement. | |
| - Where Browser Use would have spent a model call reading the page, a `page.locator()` reads it directly. Spend `act()` and `extract()` only where the page needs judgement. |
| - Where Browser Use would have spent a model call reading the page, a `page.locator()` does it for free. Spend `act()` and `extract()` only where the page needs judgement. | ||
|
|
||
| <Warning> | ||
| Stagehand reads no environment variables of its own. Browser Use auto-loads `.env` and picks up `OPENAI_API_KEY`, `BROWSER_USE_API_KEY`, and friends. In v4 you pass every key explicitly: the Browserbase API key to the factory, and any model key in the `model` option. `load_dotenv()` still works to get values into `os.environ`; nothing reads them for you. |
There was a problem hiding this comment.
| Stagehand reads no environment variables of its own. Browser Use auto-loads `.env` and picks up `OPENAI_API_KEY`, `BROWSER_USE_API_KEY`, and friends. In v4 you pass every key explicitly: the Browserbase API key to the factory, and any model key in the `model` option. `load_dotenv()` still works to get values into `os.environ`; nothing reads them for you. | |
| Stagehand reads no environment variables of its own. Browser Use auto-loads `.env` and picks up `OPENAI_API_KEY` and `BROWSER_USE_API_KEY`. In v4 you pass every key explicitly: the Browserbase API key to the factory, and any model key in the `model` option. `load_dotenv()` still populates `os.environ`, but you pass in the values yourself. |
|
|
||
| **`ImportError: cannot import name 'Agent'`.** There is no `Agent` in v4. Replace `Agent(task=...).run()` with [code mode](#code-mode) or [tool calling](#tool-calling). | ||
|
|
||
| **Nothing reads my API key.** Stagehand reads no environment variables. Pass the Browserbase key to `browserbase.launch()` and any model key in the `model` option. `load_dotenv()` only populates `os.environ`; you still pass the values in. |
There was a problem hiding this comment.
| **Nothing reads my API key.** Stagehand reads no environment variables. Pass the Browserbase key to `browserbase.launch()` and any model key in the `model` option. `load_dotenv()` only populates `os.environ`; you still pass the values in. | |
| **`KeyError: 'BROWSERBASE_API_KEY'`.** Stagehand reads no environment variables. Pass the Browserbase key to `browserbase.launch()` and any model key in the `model` option. `load_dotenv()` only populates `os.environ`; you still pass the values in. |
|
|
||
| **Nothing reads my API key.** Stagehand reads no environment variables. Pass the Browserbase key to `browserbase.launch()` and any model key in the `model` option. `load_dotenv()` only populates `os.environ`; you still pass the values in. | ||
|
|
||
| **My script has no `history` object to read results from.** There's no run history. The value you'd have read from `history.final_result()` or `history.structured_output` is the return of your last `extract()` call, on `.data`. |
There was a problem hiding this comment.
| **My script has no `history` object to read results from.** There's no run history. The value you'd have read from `history.final_result()` or `history.structured_output` is the return of your last `extract()` call, on `.data`. | |
| **`AttributeError` on `history` or `final_result()`.** There's no run history. The value you'd have read from `history.final_result()` or `history.structured_output` is the return of your last `extract()` call, on `.data`. |
|
|
||
| **My script has no `history` object to read results from.** There's no run history. The value you'd have read from `history.final_result()` or `history.structured_output` is the return of your last `extract()` call, on `.data`. | ||
|
|
||
| **My custom `@tools.action` function has nowhere to register.** Call it directly in code mode, or add it to your tool set for [tool calling](#tool-calling). Only the decorator goes away; the function body is unchanged. |
There was a problem hiding this comment.
| **My custom `@tools.action` function has nowhere to register.** Call it directly in code mode, or add it to your tool set for [tool calling](#tool-calling). Only the decorator goes away; the function body is unchanged. | |
| **A `@tools.action` function has nowhere to register.** Call it directly in code mode, or add it to your tool set for [tool calling](#tool-calling). Only the decorator goes away; the function body is unchanged. |
|
|
||
| **My custom `@tools.action` function has nowhere to register.** Call it directly in code mode, or add it to your tool set for [tool calling](#tool-calling). Only the decorator goes away; the function body is unchanged. | ||
|
|
||
| **A retried step repeats a side effect.** You're retrying `act()`, the same hazard Browser Use's `max_failures` loop had. Retry `observe()` instead and pass the resulting action to `act()` once. |
There was a problem hiding this comment.
| **A retried step repeats a side effect.** You're retrying `act()`, the same hazard Browser Use's `max_failures` loop had. Retry `observe()` instead and pass the resulting action to `act()` once. | |
| **A retried step repeats a side effect.** You're retrying `act()`. Retry `observe()` instead and pass the resulting action to `act()` once. |
|
|
||
| **A retried step repeats a side effect.** You're retrying `act()`, the same hazard Browser Use's `max_failures` loop had. Retry `observe()` instead and pass the resulting action to `act()` once. | ||
|
|
||
| **My generated script uses `Agent` or old Stagehand APIs.** The assistant is drawing on Browser Use and older Stagehand patterns in its training data. Install the rule files from [AI rules](/v4/first-steps/ai-rules). |
There was a problem hiding this comment.
| **My generated script uses `Agent` or old Stagehand APIs.** The assistant is drawing on Browser Use and older Stagehand patterns in its training data. Install the rule files from [AI rules](/v4/first-steps/ai-rules). | |
| **Your generated script uses `Agent` or old Stagehand APIs.** The assistant is drawing on Browser Use and older Stagehand patterns in its training data. Install the rule files from [AI rules](/v4/first-steps/ai-rules). |
Co-authored-by: Alyssa Keimach <7604716+akeimach@users.noreply.github.com>
|
Co-authored-by: Alyssa Keimach <7604716+akeimach@users.noreply.github.com>
Summary
Adds a "Migrate Browser Use to v4" guide under
packages/docs/v4/migrations/, modeled on the existing v3 and Playwright migration guides in that directory.The guide frames the core migration story: Browser Use is an autonomous-agent framework (
Agent(task=..., llm=...).run()), and Stagehand v4 has no agent object — so, like the v3 guide's "why agent() is gone" narrative, it steers users to code mode (an assistant writes a script once) and tool calling (a model drives the full Stagehand API as tools). It leads with Python (Browser Use is Python-first) and includes TypeScript tabs.Covered:
ChatOpenAI/ChatBrowserUse→model), structured output (output_model_schema→extract()), sensitive data (sensitive_data→variables), custom tools (@tools.action), andBrowser(...)/@sandboxconfigdocs.jsonunder the v4 Migration guide groupBrowser Use API details were grounded against the browser-use/browser-use repo's own reference docs; Stagehand v4 API against the existing v4 reference pages. All cross-links and deep anchors were verified to resolve, and
docs.jsonvalidates as JSON.Test plan
jqconfirmsdocs.jsonis valid JSONmodels#model-gateway/act#secure-your-automationsanchors resolvebrowserbase.launch,local_browser.launch,new_page,wait_for_load_state,ModelConfig,data.extraction, etc.) match usage elsewhere in the v4 docsjust check(Mint config + link validation) in CIRequested by: sophie@browserbase.com
Linear: https://linear.app/browserbase/issue/STG-2850/add-browser-use-to-v4-migration-guide
Slack thread: https://browserbase.slack.com/archives/D0AH75TNSF9/p1786985811934049