standards
Browser devicesAgents

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.

ToolDoes
browser_tabsLists, opens, selects or closes tabs.
browser_navigateLoads a URL in a tab, or goes back/forward in its history.
browser_read_pageReads 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_findFinds elements by what they are ("the search box", "Pay button"), returning up to 20 refs.
browser_get_textExtracts the page's main text (article or main first, body otherwise).
browser_computerMouse 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_inputSets the value of an input, textarea, select, checkbox or contenteditable by ref, without typing key by key.
browser_javascriptRuns JavaScript in the page and returns the last expression's value (top-level await works); the code is shown to the person.
browser_batchRuns 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 stuck instead 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_batch runs 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_page and browser_get_text return 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_batch call, 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:

errorCodeRemedy appended to the message
stale_referenceRead the page again and use the new ref — the element is gone.
not_actionableScroll to it, close what covers it, or take a screenshot — the element is covered, hidden or disabled.
no_reference_screenshotTake 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

CodeHTTPWhen
DEVICE_NOT_FOUND404The device does not exist, is revoked, or is not owned by the caller.
DEVICE_OFFLINE409No worker is connected to run the command.
DEVICE_CAPABILITY_MISMATCH400A 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_PROTOCOL400A frame on the device channel does not match the wire protocol.
DEVICE_CONTROL_ELSEWHERE409Another root conversation holds the control lease and it has not gone idle.
DEVICE_CONTROL_REVOKED409A person took the browser back from this conversation; answered once.

See Errors for the shared codes and conventions.

Next steps