Skip to content

feat(website): live website envs, real sites a multi env's computers reach at their own names (FD-4945) - #579

Merged
earakely-scale merged 5 commits into
mainfrom
edgararakelyan/fd-4945-live-website-envs
Nov 10, 2026
Merged

earakely-scale merged 5 commits into
mainfrom
edgararakelyan/fd-4945-live-website-envs

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

Summary

M5.16 (FD-4945): a task can put a real website, such as github.com, in a multi env beside synthetic sites. The computer's browser reaches it at its real names, signed in as the task's account, and the run is marked live. The design and its decisions are in the note Live websites (M5.16, FD-4945). The outcome was agreed and every decision made on Nov 9. The note is settled once you review it.

A live website env (live = true) is a real site whose server serves none of its pages. It holds:

What changes

The protocol SDK (agentenv_protocol.website)

  • @website(domains=[...], live=True, allow_hosts=[...]) declares a live site. @website refuses a live site with no domains or with a static folder, and allow_hosts on a site that isn't live.
  • The card's website/v1 params say live and allow_hosts (WEBSITE_LIVE_PARAM, WEBSITE_ALLOW_HOSTS_PARAM) and name no routes. A live site serves no /api/session page route.
  • @session_state marks the method that answers session. A live site must mark one; a synthetic site that marks none signs its own cookie, as before.
  • Any site may declare Settings. A deploy hands them to the server in WEBSITE_SETTINGS_ENV, and the site reads them as self.settings, unless it already has a member of that name of its own.

The stored env, and put (decisions 1, 1.1 and 3)

  • WebsiteEnv stores live, allow_hosts, settings and secret_settings beside domains, in env.toml and in the stored record.
  • When none are given, website put and a bundle's env.toml read live, allow_hosts and, for a live site, domains from the server's @website(...). The source is parsed with utils/card_naming.py WebsiteSource, without running it, the way the card name is already read. A @website(...) that leaves live or allow_hosts out declares their defaults, so a flag can't contradict it; they're unknown only when written as other than a literal or when the call unpacks ** keywords. The Settings fields typed SecretStr become secret_settings.
  • A value given that the source contradicts is refused.
  • website put gains --live/--no-live, --allow-host and --setting KEY=VALUE. A registry image has no source, so it states these with flags.
  • A secret setting's value must be a bare secret: or env: reference, with no ?default, never the value itself (config.is_bare_reference, the check computer settings already used).

The deploy (decisions 2 and 4)

  • Gateway: a live site keeps its /svc/<name>/ route, for its card and data plane, but gets no WEBSITE_URLS entry, so it has no /website/<name>/ route and no browse_url.
  • Settings: its server's settings go into its container as one quoted AGENTENV_WEBSITE_SETTINGS line. Each reference is resolved only when the compose file is written, each $ is doubled so Compose interpolates none, and that line is masked in the logged compose.
  • Site table: a computer's site table leaves a live site out (deployed_sites), so the computer reaches the real site over its own network. A browser alone, whose only way out is its forward port, has that port take the live sites' hosts straight to the real site (site_proxy.py direct: a CONNECT tunnelled, a URL sent on as its path, a synthetic name still winning, never an address or a .localhost name). It is installed even when every site is live.
  • Computer network: a computer whose network is an allowlist may also reach the live sites' domains and allow_hosts (MultiEnv.live_hosts, then _reaching). One that allows every host is left as it is.
  • Synthetic wins: a name that a synthetic site's names cover is that site's, and is left off with a warning. A wider live name stays, since the synthetic name is still routed within it. At put, a live site's names are claimed at the allowlist rank, so a synthetic site may claim one, and two live sites may not claim the same name.
  • Start tabs: a computer that routes sites may open start tabs only at names a website of the run claims. This is checked at save (start_tab_problems), as decided Nov 7 for M5.16, and again at deploy against the names the sites' cards declare, before the computer routes them; a save leaves the check to the deploy while a routed site stores no names.
  • The live mark: a run that deploys a live site gets live: true on its deploy record, and its live sites listed in context.metadata["live_websites"] with reproducible: false. Nothing acts on the mark yet.

Docs: a "Live websites" section in the protocol README, and the website env entry in AGENTS.md.

Test plan

  • Unit, all on this branch:

    • SDK (test_website_live.py): the card's live params, no pages, @session_state, Settings, and each refused declaration.
    • Framework (test_website_live.py):
      • the stored env's fields and the checks that refuse a bad one;
      • reading from source, and refusing a contradiction;
      • env.toml, through accept_toml;
      • the compose file: no site route, settings resolved, the line masked in logs;
      • deployed_sites leaving the live site out;
      • live_hosts, where a synthetic name that covers a host wins and a wider live name stays;
      • claim ranks at put;
      • start-tab names;
      • _reaching with an allowlist and with allow-all.
    • CLI (website_live_cli_test.py): website put reading from source, a contradicting flag, a secret's value refused, and a registry image stated with flags.
    • deploy_env_test: the live mark.
    • Review fixes: the browser alone's forward port (test_computer_site_proxy.py, test_computer_sites.py), the deploy's start-tab check (test_multi_env_computers.py), the frontend list with only live sites, $ in settings, ?default secrets and omitted defaults (test_website_live.py).
    • Unit tiers: tst/unit and the protocol tests pass, apart from the Mac-only Windows disk-recipe test and test_docker_image_github_clone, which fail on main on this Mac too.
  • Integration (CI, slow tier, green): test_a_live_site_beside_a_synthetic_one_loads_at_its_real_names_signed_in_and_marks_the_run_live. A multi env holds the website-browser desktop, the synthetic shop.test and a live github.com fixture (tst/data/live_github_website). The test checks:

    • the site table routes only shop;
    • Chromium loads https://github.com/ with its GitHub title;
    • browser_sign_in puts the test cookie on github.com, and signs shop.test in as another user;
    • the live site's data plane returns octocat/Hello-World from the public API;
    • the run is marked live.

    And test_routing_sites_beside_live_ones_its_forward_port_takes_the_live_hosts_straight_to_the_real_site (browser_computer_test.py): a browser alone loads github.com, http://github.com/ and githubassets through its forward port beside a synthetic site, and still gets a 403 for example.com.

  • VM e2e (c6i.4xlarge, amd64, local Docker): that test and feat(browser): browser_sign_in, and browser steps reach a multi env's computers (FD-4944) #556's sign-in test passed, 2 tests in 7m21s.

  • VM chaos, one multi env, passed in 2m20s:

    Phase Result
    Save a start tab at example.com with site routing refused, naming the host
    Save and deploy a start tab at https://github.com/ accepted; the tab loads with its GitHub title
    A synthetic site at gist.github.com beside the live *.github.com the computer's gist.github.com answers from the synthetic site
    Load data into the live site, sign in data held, cookie alex on github.com
    reset_env the live site's data emptied, the browser signed out
    browser_sign_in again signed in again
    docker restart the live site ready again, data/get answers
    Teardown clean
  • e2e on Modal, against dev's stores under a scratch prefix, for decision 5's allowlist check. The website-browser desktop was registered with its image built in a Modal VM and pushed to dev ECR. The gateway ran on modal_vm and the computer, whose network is an allowlist, on Modal containers. Passed in 2.5 min:

    Check Result
    Save a start tab at example.com / at https://github.com/ refused, naming the host / accepted
    The computer's live hosts github.com, *.github.com, *.githubassets.com, *.githubusercontent.com
    Its start tab at github.com loaded, 200
    Inside it: github.com, github.githubassets.com, avatars.githubusercontent.com 200 each
    Inside it: example.com, pypi.org connection reset: off the allowlist
    gist.github.com, a synthetic site within *.github.com answered by the synthetic site, through the gateway
    The browser at example.com Chrome's error page only
    browser_sign_in on the live site cookie alex on github.com
    The run marked live, reproducible: false; teardown clean

    Its dev Mongo records are deleted. Its S3 objects and ECR images stay: the developer role can't delete there.

    Found on the way, not from this PR: on modal_vm, a computer whose network is an allowlist can't pull its own image, since the VM's Docker pulls it under that allowlist and the registry isn't on it. Modal containers pull outside the sandbox's network, so they're unaffected.

Not in this PR

  • A browser deployed alone, which needs M8.21's open-web tunnel.
  • Recording live traffic (M8.20).
  • Account pools.
  • macOS and Windows computers (M6.6).
  • Anything that acts on the live mark.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge.

Summary

Adds live websites beside synthetic websites, including account settings, browser sessions, network access, and run metadata.

  • Website definitions can describe real sites and their browser sessions.
  • Website setup stores live-site hosts and server settings.
  • The gateway serves live-site data without standing in for its pages.
  • Computers reach live sites directly while synthetic sites keep their routes.
  • Start tabs for routed computers must use a website name claimed by the run.
  • Runs that deploy live websites are marked non-reproducible.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Computer[Computer browser] -->|Synthetic website names| Proxy[Site proxy]
  Proxy --> Gateway[Gateway]
  Gateway --> Synthetic[Synthetic website]
  Computer -->|Live website names| Real[Real website]
  Gateway -->|Account and data calls| Live[Live website server]
Loading

Reviews (7) · Last reviewed commit: "fix(website): a Settings field annotated..." · Reviewed by Greptile

…reach at their own names (FD-4945)

A website env with live = true is a real site, such as github.com, whose server serves none of its pages: only its
card, a data plane over the real site's API, ready (the account works) and session (@session_state), which
browser_sign_in loads into the browser.

- The env stores live, allow_hosts and settings beside domains; put reads them from the server's @website(...) when
  they aren't given, and refuses a value the source contradicts. A secret setting (a SecretStr field) is only a
  secret: or env: reference, which the gateway resolves as it writes the site's container.
- The gateway gives a live site no site route, and a computer's site table leaves it out.
- A computer whose network is an allowlist may reach the live sites' names; one that allows every host is left as it
  is. A name a synthetic site's names cover is that site's.
- A computer routing sites may open start tabs only at names a website of the run claims, checked at save.
- A run that deploys a live site is marked live, reproducible: false.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 9, 2026 22:26
Comment thread src/agent_env/env/envs/multi_env.py Resolved
Comment thread src/agent_env/utils/card_naming.py Resolved
Comment thread src/agent_env/env/envs/website.py Resolved
…em through its forward port

- A browser alone's forward port refused every host but its sites, so a live site's hosts never loaded there. It now
  takes them straight to the real site (CONNECT tunnelled, a URL sent on as its path), a synthetic name still winning
  and never an address or a .localhost name; with only live sites it is installed all the same.
- A VM gateway whose sites were all live crashed listing their frontends.
- Compose interpolated a $ in a site's settings; each is doubled.
- A secret setting with a ?default held its value; it must be a bare reference, as a computer's secret settings are.
- A source leaving live and allow_hosts out declares their defaults, so a flag can't contradict it.
- The save leaves a start tab to the deploy while a routed site stores no names, since its card may declare any.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/agent_env/env/envs/multi_env.py Resolved
earakely-scale and others added 2 commits November 9, 2026 15:20
…' card names at deploy

The save leaves a computer's start tabs to the deploy while a routed site stores no names, and the deploy checked
none, so a tab at a host no website claims opened. Once the gateway is up, before a computer routes its sites, its
start tabs are checked against the names their cards declare and the live hosts; one no website claims fails the
deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	AGENTS.md
#	src/agent_env/providers/env_providers/env_computer_provider.py
Comment thread src/agent_env/utils/card_naming.py Resolved
… secret setting too

The source reader matched SecretStr only as a name, so a quoted annotation, "SecretStr" or Optional["SecretStr"],
left the field out of secret_settings and its value could be saved as given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale enabled auto-merge (squash) November 10, 2026 01:06
@earakely-scale
earakely-scale merged commit c276c51 into main Nov 10, 2026
35 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-4945-live-website-envs branch November 10, 2026 01:07
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