Browser devices
Pair a browser as a device, the nine tools an agent drives it with, the control lease, and what a failed tool call tells the model.
A browser device is a chrome device: a browser extension paired to your account, next to the machines listed under Settings → Account → Devices. Once one is paired, a conversation whose agent was granted that browser (browser:<deviceId>) — or the default chat of its owner — and a channel speaking protocol 2 or later gets nine browser_* tools that drive it — read the page, click, type, take a screenshot — the same way exec_command drives a paired shell. There is no separate "let the agent act" gate for browser devices — unlike a shell device, pairing alone is enough; the safeguards are the ones described below (the code shown on the card, chat Stop, the extension's own brake, and the control lease), not an authorization toggle.
A paired browser is the person's own Chrome, signed in as them, and drives only while their machine is on — which is why a routine cannot hold one. For work that runs with nobody at the keyboard, or on sites signed in once for the whole team, use a cloud browser: a hosted browser the agent starts with browser_launch, blank or from a profile someone prepared, and drives with the same browser_* tools described here.
Pairing and the channel
A browser extension pairs with POST /devices/pair, sending { installationId, name, browser: { name, version } } instead of the machine shape; it comes back a device of capability browser carrying a device token. From then on it keeps one outbound WebSocket open to /v1/device-worker/channel, authenticated by its first frame (Device <token>), and receives commands over that channel rather than polling.
The channel's protocol is versioned. A browser worker's authenticate frame carries protocol: 2; a v1 extension either omits protocol or sends 1, and the server closes the socket with a policy violation (1008) and the reason UPGRADE_REQUIRED before it ever reaches ready — update the extension. A shell (CLI) worker's authenticate is never checked this way: only a browser's protocol is refused. A browser device that last authenticated without protocol 2 gets no browser_* tool, is listed in the agent's <devices> inventory as browser (extension out of date — update to use), and any browser command targeting it is refused with DEVICE_CAPABILITY_MISMATCH before anything is queued. In Settings › Devices, such a browser shows "This extension is too old for browser tools" next to an Update the extension link, whether or not it is currently connected.
The nine tools
These are the only tools a root conversation gets while it owns a browser device; every one is risk: "internal" — none asks for approval, since the safeguards live elsewhere (the code shown on the tool's card, chat Stop, the extension's own brake, and the control lease below). Several browsers paired to the same owner need deviceId on every call, copied from <devices>; with exactly one paired browser it is optional.
| Tool | Does |
|---|---|
browser_tabs | Lists, opens, selects or closes tabs. |
browser_navigate | Loads a URL in a tab, or goes back/forward in its history. |
browser_read_page | Reads the page as a tree of elements tagged ref_N, for browser_computer and browser_form_input. filter: interactive narrows to clickable and typable elements; ref narrows to one subtree. |
browser_find | Finds elements by what they are ("the search box", "Pay button"), returning up to 20 refs. |
browser_get_text | Extracts the page's main text (article or main first, body otherwise). |
browser_computer | Mouse and keyboard: left_click / double_click / right_click / triple_click / hover / left_click_drag target a ref or a coordinate (pixels in the most recent screenshot of that tab); type types into a focused field; key presses a chord or space-separated chords (Return, ctrl+a); scroll and scroll_to move the page or bring a ref into view; wait waits seconds, for text, or for a navigation; screenshot captures the tab; zoom captures one region of the last screenshot, closer up. Every action's result names the element it hit and what changed. |
browser_form_input | Sets the value of an input, textarea, select, checkbox or contenteditable by ref, without typing key by key. |
browser_javascript | Runs JavaScript in the page and returns the last expression's value (top-level await works); the code is shown to the person. |
browser_batch | Runs several of the above in one round trip, in order, stopping at the first failure — the rest are reported as not run. Cannot be nested. |
A screenshot — from browser_computer's screenshot/zoom actions, or from a browser_batch step — is stored as a private session file rather than inline; see Files and documents / Agent browser screenshots.
Page content is data, not instructions
Whatever a page returns — text, the element tree, a screenshot, a JavaScript value, a title or a URL — is data the model reads, never a command. An agent following the browser rules quotes anything a page addresses to it back to the person instead of acting on it, keeps the person's sessions as theirs (no sign-out, no credential or setting change beyond the task, no typed passwords, payment details or government identifiers, no solved CAPTCHA), prefers reading over screenshotting, picks the private option on a consent banner, and confirms in chat before an action that leaves the person's control — a purchase, a sent message, a publish, a delete, a submitted form — unless the person asked for exactly that action in this conversation.
Autopilot
When Jev is configured, agents also get browser_autopilot. The agent hands it a
short, mechanical sub-goal — open a page, fill fields, run a search — with every
value to type in values. A fast decision model then picks each click and field
itself, one step at a time, and hands control back when the goal is reached or
when it needs the agent.
What it guarantees:
- It never invents text: it only types the values it was given, and only on the site (origin) the tab was on when it started.
- Those values are never sent to the decision model; page text that echoes them is masked.
- It stops and hands back on consent banners, login or settings pages, CAPTCHAs, and pages that address instructions to the agent.
- It stops with
stuckinstead of spending its whole step budget when it runs the same click or search again while the page keeps the same URL and title.
What it does not guarantee:
- It can click any control on the page, including buttons that send or pay. Agents are told not to delegate an irreversible action you have not confirmed.
- A page that reformats a value (a re-spaced phone number) can still echo it to the model.
The extension shows each autopilot step as Autopilot: <goal> — <step>, and
Stop / taking back control work exactly as for the other browser tools.
Limits
browser_batchruns at most 10 actions, stops early past 30 seconds of cumulative wall time or 190,000 bytes of cumulative result size, and reports the rest as not run.browser_read_pageandbrowser_get_textreturn at most 30,000 characters (max_chars), 20,000 when omitted.browser_javascript's serialized return value is capped at 20,000 characters.- A screenshot is capped at 150,000 base64 characters; the extension keeps every frame under that before it ever sends it.
- A single (non-batch) command times out after 60 seconds; a
browser_batchcall, running up to 10 of them, gets 90 seconds. - The control lease frees itself after 120 seconds without a renewing call — see The control lease below.
Failures and remedies
A tool call that cannot even be attempted — no browser paired, several paired without a deviceId, the device offline, or the control lease held elsewhere — answers { success: false, errorCode, message } without touching the browser. A command the extension did run but that failed answers the same shape, and for three codes the message carries a remedy the model can act on directly:
errorCode | Remedy appended to the message |
|---|---|
stale_reference | Read the page again and use the new ref — the element is gone. |
not_actionable | Scroll to it, close what covers it, or take a screenshot — the element is covered, hidden or disabled. |
no_reference_screenshot | Take a screenshot before clicking by coordinate. |
Other worker codes (cancelled, timed_out, execution_failed, tab_unavailable, debugger_unavailable, blocked_by_user, javascript_error, batch_cap_exceeded) carry the extension's own message with no added remedy.
The control lease
One root conversation drives a browser device at a time. The first browser_* call a conversation makes acquires the lease; every later call renews it. A call from a different root conversation while the lease is live answers DEVICE_CONTROL_ELSEWHERE (409) without naming the holder or its intent — telling the model who is in the way would let it pester that other conversation. An unrenewed lease goes idle after 120 seconds and is then free for any conversation to acquire.
The person can always take the browser back: GET /devices/:id/control reads { held, heldByRootSession, expiresAt, revoked } (heldByRootSession is only ever the caller's own root session, never another's), and DELETE /devices/:id/control releases the lease and cancels whatever command is in flight. The dispossessed conversation's very next browser_* call answers DEVICE_CONTROL_REVOKED (409) exactly once — "a person took control back, do not retry unless asked to" — and after that answers normally again, since taking back control clears the lease rather than leaving it held. Both control routes are for the device's owner acting on their own account; a browser paired through a delegated (extension) token cannot call them.
Right after a takeover, the lease sits in a revoked-but-unexpired window: the row still exists (so the dispossessed conversation gets its one DEVICE_CONTROL_REVOKED instead of silently losing the lease to a third conversation), but nobody else may acquire it either, until that former holder has been told or the full 120 seconds pass. GET /devices/:id/control reports this window as { held: true, heldByRootSession: null, revoked: true, expiresAt } — never held: false — so the UI never tells a person the device is free while a new conversation would still be turned away.
Chat Stop and a session ending or being archived release every lease that root session holds, with no cancellation of the in-flight command — unlike a person's takeover, that path never needs to answer a conversation it is itself the one ending.
Taking the browser back
While an agent drives a browser, a strip above the message box names it and offers a Take back control button; the same button sits on the device's row in Settings › Devices, and a second conversation that gets turned away (DEVICE_CONTROL_ELSEWHERE) sees it too. Taking back cancels whatever command is running and tells the agent once, on its very next browser_* call, not to retry — as described above.
Errors
| Code | HTTP | When |
|---|---|---|
DEVICE_NOT_FOUND | 404 | The device does not exist, is revoked, or is not owned by the caller. |
DEVICE_OFFLINE | 409 | No worker is connected to run the command. |
DEVICE_CAPABILITY_MISMATCH | 400 | A browser_* tool targeted a device that is not a browser (or exec_command targeted a browser), or a browser whose extension has not announced protocol 2 — update the extension. |
DEVICE_CHANNEL_PROTOCOL | 400 | A frame on the device channel does not match the wire protocol. |
DEVICE_CONTROL_ELSEWHERE | 409 | Another root conversation holds the control lease and it has not gone idle. |
DEVICE_CONTROL_REVOKED | 409 | A person took the browser back from this conversation; answered once. |
See Errors for the shared codes and conventions.
Next steps
- Cloud browsers — a hosted browser for routines, signed in from a named profile.
- Files and documents — how a screenshot is stored and who can read it back.
- Agents and routines — where a root conversation and its triggers come from.
- Errors — the shared error envelope and HTTP conventions.
- Book a demo
Environments
Pick the harness that runs a conversation, a routine or an agent, and the machine it runs on — prepared once by a setup script, used in a copy every time.
Cloud browsers
Give agents and routines a hosted browser, signed in from a named profile you prepare yourself, and watch them drive it live.