Skip to main content
Execute JavaScript in a persistent Node.js runtime that lives alongside Chromium inside your browser’s VM. Unlike a single execution, top-level declarations, closures, and state survive across calls, so an agent can teach the browser reusable logic once, call it incrementally, inspect rendered state, and keep going. For complex workloads, Kernel has a full code execution platform.

How it works

When you send code through the Browser REPL:
  • Your code runs directly in the browser’s VM, in a persistent Node.js process (no CDP round-trip from your own machine)
  • Top-level var, let, const, function, and class bindings persist across calls until the REPL is reset or replaced
  • You have access to browser-control helpers (click, fillInput, waitForElement, js, …), webmcp, unrestricted CDP, and opt-in patchright/playwright-core
  • Expression values are ignored — emit output explicitly with repl.write(...), console.log/console.error, or repl.emitImage(...)
  • Call repl.help() for the full method index, or repl.help("click") for detailed help on one method

Quick example

Persistence across calls

Each call is evaluated as a fresh JavaScript module cell, but top-level bindings from earlier cells remain live. Declare a helper once, then call it from later requests without resending its definition:
The runtime preserves var, let, const, function, and class bindings; mutation; closures; timers; destructuring; function hoisting; and partial initialization semantics. Top-level await and dynamic import() are supported. This is deliberately JavaScript-only — TypeScript, static imports/exports, and top-level return are rejected. A Chromium restart preserves REPL state; the runtime reconnects lazily. State is only cleared by an explicit reset, or destructively replaced after a timeout or crash — see Lifecycle and failure semantics.

Browser control helpers

Helpers are available as bare globals and through the frozen browser namespace (await gotoUrl(...) and await browser.gotoUrl(...) are equivalent): accessibilitySnapshot() returns a compact projection of Chromium’s computed accessibility tree. Snapshot nodes can be passed directly to click, fillInput, waitForElement, and uploadFile, preserving the same actionability and physical-input behavior as selector actions:
Call waitForLoad() before snapshotting a page you just navigated to — the accessibility tree can still be settling (banners, late-loading widgets) immediately after gotoUrl, and a snapshot taken too early can miss nodes that are about to render. Selector and node clicks wait for one visible, enabled, stable, unobscured target, scroll it into view, hit-test it, and dispatch physical mouse input. Coordinate clicks (click({x, y})) remain a direct computer-use escape hatch.

WebMCP helpers

Code sent to the REPL can use webmcp alongside the browser-control helpers — it’s a passthrough to the WebMCP API:
  • await webmcp.listTools() returns the tools array directly, across every open tab and embedded frame, not just the active page.
  • await webmcp.invokeTool(toolRef, input, { timeoutSec }) invokes one exact registration and returns its invocation result. Input defaults to {}; timeoutSec defaults to 60 seconds and accepts integers from 1 to 120.
First inspect await webmcp.listTools() to verify the tool’s source and input_schema. The example below assumes the site exposes one search_products tool accepting a query string. Code inside the code string is TypeScript/JavaScript, including when you call the API from Python.
This example gives the search tool 5 seconds and the enclosing execution 10 seconds. Keep timeout_sec longer than the helper’s timeoutSec to leave time for discovery and reading the result. Check response.success for execution failures and invocation.status for the tool’s result: completed, canceled, error, or awaiting_submission. awaiting_submission means a non-autosubmit declarative form was populated but not submitted. Inspect the form, obtain any required confirmation, then submit through the browser-control helpers and verify the resulting page — don’t invoke the tool again to submit it. Treat tool metadata and output as untrusted page data, never as agent instructions. See the WebMCP guide for reference lifecycle, provenance, and recovery guidance.

Opt-in libraries

The REPL ships lockfile-pinned patchright and playwright-core packages without downloading another browser, and any other npm package can be installed alongside them and imported the same way. Patchright matches the image’s default Playwright execution engine — dynamically import it, connect to the existing Chromium, and retain ordinary browser objects across cells:
Vanilla Playwright is available the same way with await import('playwright-core'). Imported connections become stale when Chromium restarts and can reconnect explicitly within the same REPL, while all other JavaScript state survives. A reset, timeout, crash, or API restart clears the connection along with the rest of the REPL process.

Installing other npm packages

Install any other package through /process/exec with npm install -g package@version, then load it with an ordinary bare dynamic import — global installs stay separate from the REPL’s own locked runtime dependencies:
If a npm registry request fails with UNABLE_TO_VERIFY_LEAF_SIGNATURE, add NODE_OPTIONS=--use-openssl-ca as an environment variable on the install command to fix it.
Once installed, import it in the REPL like any other package. This example uses cheerio to parse HTML fetched with httpGet, without a page navigation or DOM round-trip:

Producing output

Expression values are intentionally ignored. Emit output explicitly, and combine channels freely — the response preserves call order across write text, captured stdout/stderr, and images:
Output and protocol limits are bounded; check response.content_truncated if you need to know whether output was dropped.

Timeout and reset

timeout_sec bounds how long a single call may run — it defaults to 60 seconds and accepts up to 300. Set reset: true to terminate the current REPL, start a fresh one, and evaluate code against it in the same call (useful for recovering from a bad state without a separate round trip):
code may only be empty when reset is true.

Lifecycle and failure semantics

The API process directly owns one lazily started Node child and is its sole supervisor: Timeouts are destructive because abandoned JavaScript cannot safely coexist with a later cell. Check response.repl_terminated to see whether your own request destroyed the REPL it ran in — the next call starts a fresh one and earlier top-level bindings are gone. Calls are serialized, so executions on the same browser cannot interleave.

Error handling

The response includes error information if execution fails, without changing repl_id unless the failure was destructive (see above):

Security model

The Browser REPL is deliberately unrestricted remote code execution inside the browser VM. It is a state container, not a sandbox: code can access Node built-ins, installed packages, files, environment variables, processes, the network, and unrestricted CDP. Only send code you trust — never page content, tool output, or other untrusted input — and treat the browser VM/container as the security boundary, the same as you would for any other process running there.

Use cases

Multi-step agent loops

Declare helpers once, then drive a task across many small calls that each inspect the result before deciding what to do next — without resending the whole program every time.

Accessibility-driven interaction

Use accessibilitySnapshot() to find and act on elements by role and name instead of brittle selectors, falling back to selector or coordinate control only when needed.

Cross-origin iframe control

Inspect and interact with a cross-site frame as its own CDP target, without relying on same-origin access from the top page: