Agent(task="...", llm=...), call run(), and the agent picks each action. Stagehand v4 has no equivalent object.
Most of this migration hinges on these principles:
- There is no
Agent. Nothing in v4 takes a whole task and drives the browser for you. - v4 is a browser SDK plus three model-backed steps.
act(),extract(), andobserve()take natural-language instructions. Everything else is ordinary browser control.
Browser Use is Python-first, so this guide leads with Python. Stagehand behaves the same way in TypeScript; the SDK reference carries each language’s naming.
Why there’s no Agent
Agent(task=...).run() was built for a world where a model couldn’t be trusted with the browser on its own, so the framework wrapped it in a loop, showed it the page each step, and asked it to pick one action at a time. Every step was an inference call, whether the page needed judgement or not.
Many of those steps do not need a model: navigating to a URL, clicking a button with a stable selector, reading a table. v4 exposes discrete tools and leaves the control flow to you.
Two approaches replace the agent:
- 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 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.
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.
Hello world, side by side
The smallest Browser Use program and its v4 shape:- A browser factory (
browserbase.launch()orlocal_browser.launch()) replaces the implicit browser insideAgent, andStagehand.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. Spendact()andextract()only where the page needs judgement.
Code mode
Ask a coding assistant to write the Stagehand script, then run the script. The model writes the code once, instead of driving the browser on every run. You get ordinary code: reviewable, diffable, and free of per-step inference. When a site changes, re-run the assistant on the step that broke. Start with AI rules. Those rule files keep generated code on the v4 API instead of the older Stagehand and Browser Use patterns in a model’s training data. Here’s a prompt that produces a working script:- Python
- TypeScript
page.locator() and page.goto() wherever a selector is stable, and spend a model call only where the page needs judgement. A Browser Use agent couldn’t make that split, because every step it ran was an inference call.
Tool calling
To keep a model in the loop at runtime, the way Browser Use does, give it the whole Stagehand surface rather than onetask string. Each method maps to one tool with a narrow contract, so a step names a specific browser operation instead of routing everything through one sentence of English and hoping the agent picks the right action.
Browser Use also lets you register custom tools with @tools.action(...) (formerly @controller.action(...)). Those port directly: each becomes one function in the tool set below.
Expose the real API:
page.snapshot() anchors the loop the way Browser Use’s page state did. It returns formattedTree, the accessibility tree, plus an xpathMap, so the model reads real page structure and hands back a selector you can drive deterministically.
- Python
- TypeScript
Let a coding assistant do the rest
Most of the mechanical mapping below is exactly what an assistant is good at. Point it at this page instead of retyping the rules:Recommended migration order
- Get one script constructing and closing cleanly on v4, before porting any behavior. Launch a browser, open a page, close both handles.
- Replace
Agent(task=...).run()with code mode or tool calling. This is the real work, and everything else is mechanical. - Convert the deterministic steps to
page.locator()andpage.goto(), keepingact()andextract()only where the page needs judgement. - Move
output_model_schemato anextract()call with a schema. - Move
sensitive_datatovariablesonact(). - Turn on server-side caching once the flow is stable.
Breaking changes
Initialization and teardown
Browser Use constructs a browser inside the agent and closes it for you. v4 separates the browser from the runtime, and you close both:- Python
- TypeScript
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 and the 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.
The task string becomes explicit steps
There’s no direct diff here, because atask string has no single replacement. That’s the whole migration: what the agent inferred each step, you now write out. Read code mode and tool calling, pick one, and translate the intent of the task into steps.
Models
Browser Use selects a provider by the chat class you construct (ChatOpenAI, ChatAnthropic, ChatGoogle, ChatBrowserUse). v4 takes one model object, and the model name always carries a provider prefix:
- Python
- TypeScript
model entirely and the Model Gateway picks one for you, which is the closest analogue to ChatBrowserUse(). Pass the same shape to a single act(), extract(), or observe() call to override it there. Browser Use’s page_extraction_llm (a separate model for extraction) maps to passing model on the extract() call. See models.
Structured output
Browser Use validates the final result againstoutput_model_schema on the agent. v4 puts the schema on the extract() call that reads the data, and returns it typed:
- Python
- TypeScript
extract() with no schema returns { extraction: string }. See extract.
Sensitive data
Both frameworks keep secrets out of the model’s context. Browser Use uses asensitive_data dict of placeholder-to-value; v4 uses variables with %name% placeholders in the instruction, on act() and observe():
- Python
- TypeScript
cache off for calls that carry credentials. See act. For Browser Use’s TOTP support (sensitive_data keys ending in bu_2fa_code), generate the code in your own script and pass it as a variable; v4 has no built-in 2FA step.
Custom tools
Browser Use’s@tools.action(...) / @controller.action(...) decorators register functions the agent can call. In v4 there’s no agent to register them with, so each custom action becomes an ordinary function you call directly in code mode, or one entry in your tool-calling tool set. The function body ports as-is; only the registration goes away.
Browser configuration
Browser(...) (aliased BrowserSession) options map onto the browser factory. The common ones:
Browser Use’s
@sandbox decorator runs the agent next to the browser on cloud infrastructure. The v4 equivalent is a Browserbase browser: browserbase.launch() gives you the hosted session, and the live view, recording, and network detail replace @sandbox’s on_browser_created / live_url and on_log callbacks.
Quick reference
Troubleshooting
ImportError: cannot import name 'Agent'. There is no Agent in v4. Replace Agent(task=...).run() with code mode or 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.
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. 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.
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.
Next steps
AI rules
Set your coding assistant up to write v4 code
Act
Perform one action, or replay an observed one
Extract
Pull typed data, the home for structured output
Caching
Cut inference out of a stable flow

