Skip to content

feat(multi): a multi env names its website browser (browser_envs), a browser computer on a sandbox of its own - #598

Merged
earakely-scale merged 15 commits into
mainfrom
edgararakelyan/fd-4960-m813-the-browser-alone-in-a-multi-env
Nov 11, 2026
Merged

earakely-scale merged 15 commits into
mainfrom
edgararakelyan/fd-4960-m813-the-browser-alone-in-a-multi-env

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

What

A multi env can now name its website browser: browser_envs = ["chromium"], a list of env ids. Named chromium, the browser computer, deploys on a sandbox of its own as one more of the multi env's computer children, fronted by its gateway. This is M8.13 (FD-4960), built to the settled design note: https://claude.ai/artifact/PgPaL5AvTaSmbFcAdGKaoq

  • Left out, the run's selector picks the browser as today (website_browser_env_id: config, user_overrides, --website-browser), for a multi env with websites. [] means no browser. One entry deploys; two are refused at put, as is an entry that isn't a website browser env (an MCP server env, or a computer env with a website browser app).
  • Whichever env results deploys by its kind. An MCP server env such as playwright-mcp runs among the gateway's MCP servers, as today. A browser computer takes the computer-child path. So M8.27's switch of the default stays a config change for multi envs.
  • Its own sandbox. The browser computer deploys with the multi env's computers (M5.6), sized by the deploy step's child_envs.chromium (sandbox_type, cpu, memory_mb, disk_size_gb, env_params). It stays out of computer_envs, so a stored multi env reads and writes as before.
  • Tools carry its name: chromium_computer_screenshot, chromium_computer_navigate, and so on.
  • Sites. It routes the multi env's synthetic sites through the gateway (site_routing = true) unless its entry's env_params say whether it does. Its start tabs load them by name.
  • Network. Its network is its own env's, as any computer child's (M5.6): when that is an allowlist, it is widened to the gateway's host and the live sites' domains and allow_hosts; otherwise it allows every host. Holding it to the gateway alone was built and then taken out on review (below).
  • Lifecycle. Reset, load, snapshot, reattach and close reach it as any computer child, from its record in the multi env's.
  • The crash goes. The gateway no longer reads docker_image_artifact off whatever the selector names. A lone website env whose run names a browser computer is refused with a message pointing at a multi env.

The gateway keeps the scheme of the agent's origin. browser_connect_cdp names the agent's origin, where its sandbox reaches the multi env's gateway, on the cdp call it asks through the gateway, as #608 (FD-5470) has it name it of a browser it asks directly. The browser mints the address under it, and the gateway keeps that address's https or wss when it puts its own host and /browser/<key> before /cdp/<token>/, in the cdp answer and in discovery alike. Modal's tunnel ends TLS and sends no X-Forwarded-Proto, so the gateway alone would say http://, which doesn't connect. This works on a VM and on Modal containers alike, where a container gateway's tunnel URL isn't known until it runs. It takes effect where the gateway image of this release runs, for a browser composed with #608's origin; one composed before it gets http behind Modal, which the step refuses. It is the gateway's half of what #608 left for a follow-up.

browser_connect_cdp reaches a multi env's browser. It used to refuse a browser in one of a multi env's computers. It now asks that browser's cdp through the multi env's gateway, on its /svc/<computer>/ route with the computer's control token, so the address it hands the agent is the gateway's CDP relay, rebased for the agent's sandbox as deploy_agent rebases the gateway's MCP URL. It names the agent's origin there too, as above. The env's tools are hidden at the gateway the agent connects to, by the step's existing rule.

One place picks the browser. MultiEnv.website_browser() gives the browser a deploy adds and where it runs, a WebsiteBrowser: a browser computer on a sandbox of its own, an MCP server env among the gateway's MCP servers, or the run's, which the gateway looks up as it deploys when the store doesn't hold it yet. The gateway's topology reads it rather than choosing again (Edgar, on review Nov 11).

Also: agent-env env multi put --browser ID and --no-browser; the bundle plan tells a browser computer apart from the computers by name, as it does them; the browser task steps count a named browser computer among a multi env's browsers; AGENTS.md and the browsers README.

Fixed on the way: a computer app deployed alone, such as chromium, lists itself as its one app, so the multi env's app-name check would have refused it for "sharing" its own name.

Decisions (FD-4960, Nov 10)

  1. Revised. The multi env names its browser in browser_envs. Left out, the run's selector picks it; [] is none; one entry for now.
  2. Revised. On a sandbox of its own, as one more computer child (M5.6's path).
  3. Revised, then taken out on review. Its network was to be an allowlist of the gateway's host and the live sites' hosts. Taken out for now (Edgar, Nov 10): it was in force only on Modal containers, since local Docker can't enforce an allowlist and every VM-path provider's image pull is cut off by one, so the browser's network is its env's, as any computer child's.
  4. a. Tools prefixed chromium_.
  5. Revised. Sized by child_envs.chromium; the gateway's VM is unchanged.

Decided on review (Nov 10): the browser's CDP address takes its scheme from the agent's origin (above; first the deploy told the gateway its gateway_url, which only a VM deploy could, so on Nov 11 it moved to #608's origin), and the browser's network is its env's, as any computer child's, so it deploys, resets and reloads on modal_vm as anywhere else.

Decided on review (Nov 11), no change: until this release's gateway is promoted, a browser composed with #608 behind an older gateway image is refused with the generic "didn't put the CDP address at its relay" message (left as is); the gateway keeps the https or wss the browser's answer names, its host and path always its own; and real-Modal coverage stays the runs below, CI covering the tunnel with a local front that ends TLS.

Fixed in a follow-up commit: reset_env reads the multi env from the store, whose browser slot was empty, so the browser computer was skipped and stayed down. The reset now finds it in the record, as reattaching does.

Tests

  • Unit:
    • browser_envs at put: left out, [], one entry, two (refused), not a browser env (refused), a name a computer has (refused);
    • at deploy: a named browser computer joins the computer children (chromium_, default site_routing, child_envs sizing);
    • the run's browser computer takes the same path, and one the store doesn't hold yet is left for the gateway as before;
    • [] gives no child and no gateway browser; an MCP server browser runs among the gateway's servers, in container mode too;
    • the browser is reattached, reset (also from the multi env as stored) and closed from its record, and a redeploy keeps the earlier one for close;
    • CLI --browser / --no-browser, the bundle plan's names, stored_browsers.
  • Integration (in tst/integration/computer/browser_computer_test.py, sharing its composed chromium; local Docker on an agent-environments-admin amd64 VM): 4/4, beside the module's site-routing tests, 2/2.
    • A multi env with the shop site and chromium: chromium on a sandbox of its own, no playwright-mcp; its start tab loads https://shop.test/ by name through the gateway; an agent's chromium_computer_* tools drive it through /mcp; its CDP address and discovery answer, asked through /svc/chromium/, name the gateway; a CDP agent there loads the site by name; a later process reattaches it from the instance store; a universe loaded into the multi env sets its cookies; reset_env brings it back after its container is killed and after the gateway restarts.
    • A multi env naming no browser, with the run's browser chromium: chromium deploys as a computer child, no playwright-mcp, its start tab loading the site.
    • browser_envs = [] deploys no browser.
    • browser_connect_cdp on the multi env: the echo test agent opens https://shop.test/ over the address it was handed, through the gateway's relay (its trajectory records the connection), with the multi env's tools hidden from its role; the module's own browser_connect_cdp test still passes beside it.
  • After the gateway fix, on the same VM: the browser tests 2/2 and gateway_test.py -k website 4/4 again; 1,113 gateway unit tests.
  • After merging main (chromium now on ubuntu-24.04-browser, headful), on a fresh agent-environments-admin amd64 VM: the browser tests 2/2, multi_env_computers_test.py 4/4, gateway_test.py -k website 4/4, browser_computer_test.py -k routing_sites 2/2; unit tier 14,440 pass, the same 3 failures unrelated to this change.
  • Regressions on the same VM: multi_env_computers_test.py 4/4, gateway_test.py -k website (playwright-mcp) green, browser_computer_test.py -k routing_sites green. Unit tier 14,368 pass, 3 failures unrelated to this change (two in test_mcp_cli_builder, one version lookup that needs .git).
  • The agent's origin through the gateway (Nov 11, 5b0a38a): unit tiers 3,870 pass, with new gateway-route and step tests that fail on the old code. On an agent-environments-admin amd64 VM, browser_computer_test.py -k 'multi_env or browser_connect_cdp or front or routing_sites' passed 9/9 (Node 22). That includes a new multi env test behind a front that ends TLS, as Modal's tunnel does, which fails on the old gateway line.
  • One place picks the browser (Nov 11, 767ed3e): unit tiers 6,687 pass (plus one macOS-only failure unrelated to this change). On an agent-environments-admin amd64 VM: browser_computer_test.py 9/9 (multi env, browser_connect_cdp, both fronts that end TLS, site routing), multi_env_computers_test.py 4/4, and gateway_test.py -k website 4/4, which takes the run's playwright-mcp.
  • Modal, on dev's stores (m813-office-mcp: the mail MCP server and m813-chromium, recomposed with fix(browser): CDP addresses through a TLS-ending tunnel are https and wss (FD-5470) #608, no websites; this branch's gateway m813-gateway): passed 9/9 with the gateway on Modal containers (Greptile's case) and 9/9 with it on modal_vm, chromium on modal_vm in both. Asked as browser_connect_cdp asks, through the gateway under the agent's origin, the address is https/wss at the gateway's relay, and discovery answers the same wss. A CDP client connects there and runs a script in a tab. Asked without an origin, the gateway says http, which the step refuses. Both deploys were closed.
  • Rebased on main with fix(browser): CDP addresses through a TLS-ending tunnel are https and wss (FD-5470) #608 (Nov 11), on an agent-environments-admin amd64 VM: browser_computer_test.py -k 'multi_env or browser_connect_cdp or routing_sites' 7/7, and fix(browser): CDP addresses through a TLS-ending tunnel are https and wss (FD-5470) #608's TLS-front test 1/1 (Node 22). Affected unit tiers (tst/unit/task_step, tst/unit/env/gateway, the multi env computers, the gateway provider, the protocol's CDP tests): 3,771 pass.
  • modal_vm on dev's stores (test ids m813-): chromium composed on a modal_vm builder and made ready by gate 3 there.
    • chromium on modal_vm (the default beside a modal_vm gateway), on this branch's gateway image (registered as m813-gateway, through the run's gateway_env_id), 13/14: it deploys; its start tab loads the site by name through the gateway; the gateway runs no playwright-mcp; an agent drives it under chromium_; its CDP address is https:///wss:// at the gateway, the discovery answer names the gateway, and a CDP client connects and the browser answers; it allows every host, its env declaring no network; after a snapshot reload's recreate of every gateway service but the database it still drives; reset_env brings it back. Its VM has the 4 GiB asked for; the one check not passing is its CPU count, 18 vCPUs, since Modal's cpu is a reservation, not a cap.
    • The same on dev's released gateway, before the gateway fix: 10/13, its CDP address http:// and not connecting.
    • chromium on modal (child_envs.chromium.sandbox_type), beside a modal_vm gateway: it deploys; its start tab loads the site by name; an agent drives it; from inside it the gateway's host answers and the internet is refused; 2 CPUs. Not passing: the CDP address, the gateway VM's other address reachable, and reset (open items 2 to 4).
    • chromium on modal_vm held to the gateway, before the decision: FD-5430, its image pull cut off in 21 s, closed cleanly.

On Modal (decided Nov 10)

  1. FD-5430: no longer bites, since the browser's network isn't held to the gateway.
  2. Reset on modal: decided, left as is. A computer on Modal containers can't be reset in place, so reset_env fails for chromium there, with an error saying why; chromium lands on modal only when child_envs.chromium.sandbox_type puts it there, and every computer on modal has the same limit.
  3. The gateway's CDP address on Modal is http://: decided, fixed here (the agent's origin's scheme, above).
  4. "Nothing else" on Modal, left as is: every allowlist also takes each platform's own hosts (SandboxProvider.effective_network_policy), *.modal.host for Modal, so a computer held to an allowlist there reaches any Modal tunnel, the gateway VM's pgweb tunnel included. With item 1, chromium on modal_vm allows every host anyway; this bites chromium on modal and any computer with its own allowlist, as it did before this PR.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new blocking issue was found.

Summary

Adds browser_envs so a multi env can choose its website browser, deploy a browser computer separately, and reach it through the gateway.

  • Multi envs can choose a website browser and deploy browser computers as children.
  • Agents can connect to a multi env's browser through its gateway.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  M[Multi env] --> S{Selected browser}
  S -->|Computer| C[Separate computer sandbox]
  S -->|MCP server| P[Gateway MCP service]
  S -->|Empty list| N[No added browser]
  A[Agent] --> G[Gateway]
  G -->|Tools and CDP relay| C
  G --> P
Loading

Reviews (15) · Last reviewed commit: "refactor(multi): one place picks a multi..." · Reviewed by Greptile

@earakely-scale
earakely-scale marked this pull request as ready for review November 10, 2026 21:04
@earakely-scale
earakely-scale requested a review from a team as a code owner November 10, 2026 21:04
Comment thread src/agent_env/env/envs/multi_env.py Resolved
earakely-scale and others added 12 commits November 10, 2026 17:49
…puter runs on a sandbox of its own

browser_envs on a multi env names its website browser: left out, the run's selector picks one as before; [] is
none; one entry deploys, two and a non-browser env are refused at put. An MCP server env such as playwright-mcp
runs among the gateway's MCP servers; a browser computer such as chromium deploys as one more computer child,
sized by child_envs, its tools under its name, routing the synthetic sites unless its entry says, and its network
held to the gateway's host and the live sites' hosts where the provider enforces an allowlist (reach_only). The
gateway no longer reads an image off whatever the selector names, and a lone website env refuses a browser
computer as the run's browser. CLI multi put --browser/--no-browser; the bundle plan and browser steps see it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wser its record holds

reset_env reads the multi env from the store, whose browser slot is empty, so the browser computer was skipped and
stayed down. The reset now finds it in the record, the record of a name none of the multi env's own computers has,
as reattaching does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…host until its image can be pulled under an allowlist

A Modal VM's Docker pulls a container's image through the sandbox's own allowlist, which leaves the registry out, so
chromium held to the gateway couldn't pull itself there. SandboxProvider.PULLS_UNDER_ALLOWLIST says which providers
can; modal_vm can't, and a reach_only network falls back to allowing every host there, as logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the scheme the deploy reaches the gateway at

Modal's tunnel terminates TLS and sends no X-Forwarded-Proto, so the gateway handed out http:// CDP addresses there,
which don't connect. The deploy now tells the gateway its tunnel URL (AGENTENV_GATEWAY_PUBLIC_URL), and a request to
that host takes its scheme; X-Forwarded-Proto still wins, and any other host keeps the connection's own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CP browser's name is checked, and only Modal containers pull under an allowlist

Review fixes: stored_browsers counts the browser computer a multi env resolves, the run's too, so browser steps
pass preflight when the run names chromium; an MCP server browser's name is checked beside the computers and MCP
servers, so a clash refuses rather than stopping the gateway; and PULLS_UNDER_ALLOWLIST is off by default, since the
default VM path pulls under the allowlist, on only for Modal containers, whose platform pulls outside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts own process made, as main's tests now do

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd leaves the earlier one for close

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ny computer child's

Edgar's call on review: holding chromium's network to the gateway was in force only on Modal containers (local
Docker can't enforce an allowlist, and on every VM-path provider the image pull is cut off by it), so it is taken out
for now: reach_only, the fallback and SandboxProvider.PULLS_UNDER_ALLOWLIST go, and a browser computer's network is
its env's, widened to the gateway's host and the live hosts only when that is an allowlist, as M5.6 does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…with a reattach, a load and the run's browser

They share its composed chromium, so CI composes it once. The deploy test now also reattaches the browser from the
instance store and loads a universe that sets its cookies through the multi env; a new test deploys a multi env naming
no browser while the run names chromium.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ti env's browser at its gateway

A browser in one of a multi env's computers was refused. The step now asks its cdp through the multi env's gateway, on
its /svc route to that computer with the computer's control token, so the address it hands the agent is the
gateway's relay, rebased for the agent's sandbox as deploy_agent rebases the gateway's MCP URL; the env's tools are
hidden at the gateway the agent connects to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ss the gateway didn't relay

The gateway's CDP relay tells apart one website browser per computer; for a computer with several, its cdp answer came
back unrewritten, naming the computer's own address, which the step would have handed the agent. The step now refuses
an address that isn't at the gateway's relay, before the agent gets it or any tool is hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…name for its address

public_url was the same value as the deploy record's gateway_url (the gateway's tunnel URL), so it takes that name:
the compose parameter, AGENTENV_GATEWAY_URL, Gateway(gateway_url=...) and the CDP relay's argument. No change in
behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4960-m813-the-browser-alone-in-a-multi-env branch from 6688cb3 to 6b2495c Compare November 11, 2026 02:34
earakely-scale and others added 3 commits November 10, 2026 19:02
…gin the agent's named, on containers too

browser_connect_cdp names the agent's origin on the cdp call it asks through
the multi env's gateway too, as #608 has it name it of a browser asked
directly, and the gateway keeps the https or wss of an address the browser
minted under it. So behind a tunnel that ends TLS, as Modal's, the address
and its discovery are https and wss on a VM and on Modal containers alike,
where a container gateway's tunnel URL isn't known until it runs.

The deploy no longer tells the gateway its gateway_url
(AGENTENV_GATEWAY_URL), which only a VM deploy could.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, is refused with word to compose it again

browser_connect_cdp refused such a browser's address as one the gateway
didn't relay, blaming a computer with several browsers. It is at the relay,
in http where the gateway is reached by https: the browser, composed before
agentenv-framework 0.9.1505, takes no origin. The step now says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ere it runs

MultiEnv.website_browser() gives the browser a deploy adds and where it
runs, a WebsiteBrowser: a browser computer on a sandbox of its own, an MCP
server env among the gateway's MCP servers, or the run's, which the gateway
looks up as it deploys when the store doesn't hold it yet. It replaces
browser_env, browser_computer and _browser_computer, and the gateway's
topology reads it instead of choosing again (_named_browser_server goes).
A run's MCP server browser the store holds is now handed to the gateway
rather than looked up a second time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit 728c869 into main Nov 11, 2026
35 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-4960-m813-the-browser-alone-in-a-multi-env branch November 11, 2026 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant