Skip to content

Sessions and lifecycle

Every browser-touching tool accepts an optional session argument (default "default"). Each session id is a fully isolated browser context: its own cookie jar and storage, its own ref registry, its own console and network buffers. Concurrency between agents rests on that isolation. For persistent sessions the cookie jar is the profile, so concurrent persistent sessions need distinct profile names (see Session modes below), or use incognito.

  • Many agents, one server. Give each agent its own session id and they cannot stomp each other. There is no server-global “active session”.
  • One agent, many sessions. Drive several windows or flows in parallel by id.
  • Multi-user. Two sessions logged in as different users of the same app do not bleed, because they are different browser contexts with different cookie jars.

Omitting session resolves to a lazily created "default" session, so simple single-session callers need to know none of this. A session id and a profile name are each one plain name: letters, digits, ., _ and -, starting with a letter or digit, at most 128 characters. Anything else is refused with invalid-name.

  • open_session({ session, mode?, engine?, profile?, channel?, backgroundThrottling?, device?, viewport?, har?, hars? }) eagerly creates an id. Re-opening a live id is an error.
  • close_session({ session }) tears one down.
  • close_sessions({ prefix?, all?, idleMs? }) is the bulk reap primitive for multi-agent cleanup. Selectors AND together, and at least one is required so you cannot accidentally close nothing or everything.
  • list_sessions() returns { sessions: [{ id, mode, engine, url, pages, openedAt }] }.
ModeIsolationPersistenceUse it for
persistentthe workspace default profile, or profilecookies and storage survive across runslogged-in flows you want to resume
incognitoown ephemeral context and browsernothing persisted, all state discarded on closeone-off driving with no profile trace
attachedthe externally launched Chrome (not owned)that Chrome’s own profileBYOB, attach to a Chrome you already control

incognito ids are always isolated contexts. With persistent, a session that names no profile runs on the workspace’s default profile, whatever its id, and profile: "name" uses <workspace>/profiles/name. A profile is open in one browser at a time, so a second live session on the same profile is refused with profile-in-use, naming the profile and the session that holds it. Give each session its own profile, or use incognito, when two sessions need two identities.

In persistent and incognito modes, browxai spawns Chrome as a child process of the MCP server. When the MCP client restarts the server, for a config edit, a code reload, or any other reason, that Chrome child dies with it and any live page state is gone. A stored ref that pointed at a now-dead page resolves to about:blank or a fresh document.

If you need page state to survive server restarts, run Chrome yourself and attach to it. browxai chrome start opens the installed Chrome on the workspace’s default profile with a debugging port (9222 unless --port says otherwise):

Terminal window
browxai chrome start

Then point the browxai server at it. The server reads its environment once at start, so set these in the env of the MCP config entry, with byob-attach added to the capability list next to the defaults:

{
"mcpServers": {
"browxai-attached": {
"command": "browxai",
"env": {
"BROWX_ATTACH_CDP": "http://127.0.0.1:9222",
"BROWX_CAPABILITIES": "read,navigation,action,human,byob-attach"
}
}
}
}

Without byob-attach every attached session is refused, naming the capability. browxai init <workspace> writes an MCP config with a browxai-attached entry that already sets both. A profile is open in one Chrome at a time, so quit any other Chrome on it first, or pass --profile NAME. Stop the Chrome with browxai chrome stop.

With the default confirmation hooks, byob_action holds every action tool on an attached browser until a person answers: __browx.confirm(true) in the DevTools browxai console context of that Chrome. An unanswered prompt times out after five minutes and the action does not run. For an unattended run, the operator drops byob_action from BROWX_CONFIRM_REQUIRED, or enables self-approval and lets the agent call approve_actions. See Capabilities and safety.

An attached Chrome is not owned by browxai: it is never closed on shutdown and it survives browxai restarts cleanly.

Two launch-time knobs on a chromium session:

  • A chromium session with no channel launches the installed Google Chrome, without Playwright’s --enable-automation switch, and falls back to the bundled Chromium with a logged warning when Chrome is not installed. open_session({ channel: "chromium" }) pins the bundled build, and any other channel ("msedge", "chrome-beta") launches that installed browser. The browser version follows the install. Server-wide default via the channel config key or BROWX_CHANNEL. See Sites behind bot protection.
  • open_session({ backgroundThrottling: "disabled" }) launches with Chrome’s three background-lifecycle flags off, so a backgrounded session keeps timers and requestAnimationFrame running at full rate. Worth turning on for pooled multi-agent work, where an agent polling a backgrounded tab otherwise stalls to its deadline and the stall looks like a page bug. The default keeps Chrome’s own throttling, because reproducing a background-lifecycle bug needs a genuinely throttled tab.

Both are launch-time, so an attached session ignores them. Pass --disable-background-throttling to browxai chrome start instead.

  • open_session({ device }) takes any Playwright device-preset name (for example "iPhone 14", "Pixel 7", "Desktop Chrome") and applies its viewport, scale factor, touch, and user agent.
  • open_session({ viewport: { width, height } }) overrides a preset’s size while keeping its mobile, touch, and UA traits.
  • set_viewport({ session, width, height }) resizes mid-session for responsive testing and returns an ActionResult, since a re-layout often triggers responsive re-render or lazy loading. Only the size changes live; full device traits are fixed at session creation.

Beyond isolation, each session carries policies for browser events that would otherwise deadlock a headless context or silently change app state: dialogs (alert / confirm / prompt), permissions (camera, geolocation, and more), the Notification constructor, and File System Access pickers. Each defaults to a raise posture, meaning the event is handled so the page does not hang, and the next result reports it. The tool reference documents every policy mode.