GitHub Repository
Runnable reference implementation of everything in this guide.
libretto Docs
CLI + library reference for the browser-automation tool used in the examples.
When to reach for a browser (vs. an API)
- The target has no API (flight aggregators, airline sites, most SaaS admin UIs).
- The target needs a real logged-in session (cookies + localStorage + JS-challenge cookies).
- You want AI-driven interaction — describe a task in English, let the agent figure out the clicks.
- You need screenshots / visual artefacts for verification.
Step 1: Build a snapshot with Chromium pre-installed
Browser setup is heavy (apt packages + Chromium binary is ~500MB). Bake it into a named snapshot once, launch sandboxes from it in seconds.build-snapshot.ts
Step 2: Per-boot setup that can’t be baked in
A few things need to run at sandbox startup rather than during snapshot build, because they depend on per-sandbox state.launch.ts
Networking: the secretStore requirement
OpenComputer routes all outbound traffic through a secrets-injection proxy. The proxy only accepts traffic from sandboxes that have at least one sealed secret registered, because session registration happens as part of sealing secrets.
The workaround is to create a SecretStore with a wildcard egress allowlist and at least one (possibly dummy) entry:
opensandbox-proxy.crt we trusted earlier lets Chromium validate the MITM-rewritten certs.
Step 3: Run a headless browser
With the snapshot and per-boot setup in place, driving the browser is a normal libretto session:snapshot command requires an ANTHROPIC_API_KEY (or an OpenAI / Gemini / Vertex key — configure via .libretto/config.json). It’s the AI-driven feature that makes libretto different from raw Playwright.
Step 4: Run a headed browser (for interactive login)
For workflows where the user needs to log in themselves, you can render the browser to a virtual display and expose it via VNC. This lets you embed the running browser in a web UI. Install the VNC stack at runtime (same no-rebuild pattern as per-boot setup):DISPLAY=:99 in the sandbox envs when you create it, then open with --headed:
sandbox.getPreviewDomain(6080) and embed <iframe src="https://<id>-p6080.<domain>/vnc.html?autoconnect=true"> in your UI. Users click/type directly in the real browser.
Step 5: Persist login across runs
After the user logs in, save the auth state:Resource and concurrency limits
- Memory per VM — default is ~1GB. Each headless Chromium uses ~300-500MB; headed full Chromium uses ~500-800MB. Pass
memoryMB: 16384toSandbox.createfor browser-heavy work. - File descriptors — the default
nofilelimit of 1024 is too low for Chromium (which opens hundreds of FDs). Launch the server process undersudo bash -c "ulimit -n 65535 && exec ..."to raise it. - Concurrency — with default memory, run 2-3 browsers concurrently per sandbox. With 16GB and proper limits, 5+ works cleanly.
Choosing a browser automation library
All four run inside OC sandboxes; the snapshot recipe above is compatible with all of them.
Troubleshooting
407 from proxy after CONNECT
Your sandbox isn’t registered with the secrets proxy. Attach a secretStore with at least one secret entry.
Chromium exits with status 1 on launch
Missing shared library. Check .libretto/sessions/<name>/logs.jsonl for the specific .so missing. Add it to CHROMIUM_DEPS in the snapshot or install at runtime.
net::ERR_CERT_AUTHORITY_INVALID
Chromium doesn’t trust OC’s egress-proxy CA. Run the certutil NSS-import step from “Per-boot setup”.
ENOTFOUND localhost
The guest’s /etc/hosts is missing the localhost entry. Run the grep … /etc/hosts step from “Per-boot setup”.
EAGAIN / uv_thread_create / Killed
Out of memory or leaked browser processes from prior runs. Reap stale Chromium processes between sessions (pkill -9 -f chrome-headless-shell) and reduce MAX_CONCURRENT_BROWSERS.
Preview URL shows “Waiting for server”
Your server is bound to 127.0.0.1 instead of 0.0.0.0. OC’s edge forwards from the worker interface, not loopback. Set hostname: "0.0.0.0" when calling your web server’s listen().
SSE / streaming responses return 502
OC’s preview-URL edge buffers response bodies — streaming chunks don’t reach the client. Use short-polling (POST /api/job → GET /api/job/:id) instead of SSE or WebSockets for progress updates. WebSockets (for VNC, etc.) do pass through as upgrade connections; buffering only affects regular HTTP responses.
Next steps
- Read the libretto docs for the full CLI + library reference.
- See Snapshots for how to checkpoint a warmed-up browser VM.
- See Secret Stores for scoping egress and sealing real credentials.