Cookbook: remote browser
02 / Connect from outside
A browser sandbox runs a headless Chromium with a CDP endpoint on :9222. The
Live browser recipe drives that browser from inside
the guest — the fast path for RL. This page does the opposite: it hands you the CDP
endpoint so you can drive the live browser from your own machine, using the
Playwright script you already have.
This is the connect-from-outside access model: the service runs in the sandbox; the driver runs on your laptop.
Create the sandbox and get a CDP URL
Section titled “Create the sandbox and get a CDP URL”Create a browser sandbox and ask the SDK for a connect URL to the CDP port:
from collimate_rl import connect
client = connect(api_key="col_...")
with client.session("browser", egress={"allow": ["example.com"]}) as sb: cdp = sb.cdp_url() # == connect_url(9222, protocol="cdp") print(cdp) # wss://api.collimate.ai/v1/sandboxes/<id>/connect?port=9222&protocol=cdp&t=<token>cdp_url() mints connect_url(9222, protocol="cdp") — a signed
wss://…/connect?port=…&protocol=cdp&t=<token> URL. The token scopes the
connection to this one sandbox and port; the cdp protocol makes the platform
speak the DevTools Protocol end to end, so a CDP client attaches directly (a plain
tcp tunnel can't carry CDP). Nothing about the underlying host is exposed — the
address is a platform-managed endpoint on api.collimate.ai.
Drive it from your own machine
Section titled “Drive it from your own machine”Point a local Playwright at that URL with connect_over_cdp. The script runs on
your machine; every navigation and selector is executed by the browser inside
the sandbox:
from collimate_rl import connectfrom playwright.sync_api import sync_playwright
client = connect(api_key="col_...")
with client.session("browser", egress={"allow": ["example.com"]}) as sb, \ sync_playwright() as pw: browser = pw.chromium.connect_over_cdp(sb.cdp_url()) page = browser.new_context().new_page() # isolated context page.goto("https://example.com") print(page.title()) # -> "Example Domain" page.screenshot(path="shot.png") # saved locally, on your machine browser.close() # closes the connection, not the in-guest browser# leaving the `with` deletes the sandboxThe turnkey helper connect_browser does the mint + connect_over_cdp for you:
from collimate_rl.browser import connect_browser
with client.session("browser") as sb: browser = connect_browser(sb) # Playwright browser, on your machine try: page = browser.new_context().new_page() page.goto("https://example.com") print(page.title()) finally: browser.close()Because the driver is local, you get the full local Playwright ergonomics —
screenshot(path=...) writes to your disk, breakpoints stop in your debugger, and
you can iterate on the script without redeploying anything. The browser state (open
pages, cookies within a context) lives in the sandbox and persists across
reconnects for the sandbox's lifetime.
In-guest vs from-outside: which path
Section titled “In-guest vs from-outside: which path”Both talk to the same live browser on :9222. They differ in where your driver
runs and, therefore, in latency and ergonomics.
| In-guest (exec) | From outside (connect) | |
|---|---|---|
| Where the driver runs | In the sandbox | On your machine |
| How you invoke it | client.exec(id, commands=[["cdp-nav", url]]) | connect_over_cdp(sb.cdp_url()) |
| Round-trips | None leave the guest | Every CDP call crosses the network |
| Latency | Lowest — see below | Adds a network hop per call |
| Best for | RL rollouts, batch grading, fork fan-out | Interactive sessions, local scripts, debugging |
| Plan | Any (demo = no egress) | Pro |
The in-guest fast path (cdp-nav)
Section titled “The in-guest fast path (cdp-nav)”For RL and any latency-sensitive fan-out, drive the browser inside the guest
instead. The browser image bakes cdp-nav, a dependency-free (Python-stdlib)
raw-CDP client that connects to the resident browser, navigates, and prints the
page title and final URL — with no Node driver, so it is markedly faster than
Playwright's connect_over_cdp:
result = client.exec( sb["id"], commands=[["cdp-nav", "https://example.com"]], timeout_seconds=60,)print(result["stdout"]) # -> "Example Domain\thttps://example.com/"See Live browser → Fast path: raw CDP for cdp-nav's
options (--expr, --port, --timeout) and the measured latency comparison. As a
rule: reach for connect-from-outside when you want to drive the browser
interactively with your own client; reach for in-guest exec when you want a
scalar result and the lowest per-step latency.
Fan out with fork
Section titled “Fan out with fork”fork fans a warmed browser out into many live children. Each child keeps the same
resident browser and its own connect URL — so you can drive a whole fleet remotely,
one CDP URL per child:
children = client.fork(sb["id"], count=8)["children"]for child in children: # each child is its own sandbox; mint its own connect URL by id url = client.cdp_url(child["id"]) # ... connect_over_cdp(url) from your driver ...For batch rollouts, prefer the in-guest cdp-nav step per child — it avoids a
network hop per navigation across the fan-out. See Live fork.
Real navigation needs egress
Section titled “Real navigation needs egress”The public web is a Pro capability. Demo sandboxes have no outbound network, so
a goto("https://…") — over CDP from outside or via cdp-nav in-guest — reaches
nothing. Grant the destinations you need at create time:
sb = client.create_sandbox("browser", egress={"allow": ["example.com"]})See Per-fork egress for the full policy model, and Live browser → Pro browser-RL for the auth-and-secrets patterns (never bake credentials into the shared ready state).
Next steps
Section titled “Next steps”- Two ways to use a sandbox — exec-in-guest vs connect-from-outside.
- Live browser — bake CDP into the template; the in-guest
cdp-navfast path. - Per-fork egress — control what the browser can reach.
- Live fork — fan one warmed browser out into N live children.