Repository navigation
feat(computer)!: compose on the put's sandbox provider, in a builder VM per arch - #414
Merged
earakely-scale merged 11 commits intoOct 30, 2026
Conversation
…M per arch A computer env composes on the put's sandbox provider, by default the env's. The local provider builds with this machine's Docker as before; a VM provider resolves and builds each form and arch in a VM of its own, which fetches the put's stored files by signed URL, takes the rest inline, registers QEMU for an arch it emulates, pushes to the image store and is torn down. Each arch goes to the first link that runs it natively, else one that emulates it. The index is written over the registry API, so a put needs no local Docker. --sandbox on env build and bundle register; run --sandbox composes there too. FD-4734 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
compose reports each pair's stages to an on_progress callback and the log: where it builds, natively or emulated, resolving, building, pushing, probing, a line every 10 min a stage goes on, and how each pair ended with its time. bundle register, run and env build print them without --verbose. on_progress replaces the on_build hook on computer env puts and AuthoringContext, whose "building with docker" line was wrong for a remote build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uild settings go to a VM inline An app's files under .agentenv/, the folder its own server keeps its socket and build settings in, are refused at put: a build settings file there reached the app unchecked. The build settings file the composer writes leaves the files a builder VM fetches from the store, so a VM no longer builds an app stored before with the app's own copy, under a key computed from the env's values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…run's provider When run or bundle register reuses an unchanged computer env, it composes the env's pending pairs (failed, stopped, or built but unprobed though the provider runs the arch natively) on its sandbox provider, as agent-env env build does, writing no new version. A dry run says what it would compose, and bundle register goes on to write when anything is left rather than stopping at "Nothing to write". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lated builds get longer limits A put can turn emulation off (--no-emulate on env build, bundle register and run; emulate=False on a put): an arch no link of the sandbox provider runs natively is recorded skipped, with each link's reason, and stays pending for a later put that runs it natively or emulates it. A builder VM gets 2 h, 6 h when it emulates the arch, and the resolver 1 h, 3 h under emulation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pair that builds emulated says, on its progress line, that --no-emulate skips it and, when this machine runs the arch and the chain has no link on its Docker, that adding local to --sandbox builds it natively here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The same pinned digest, through Google's mirror of Docker Hub, which doesn't limit anonymous pulls by IP, as provider VMs sharing IPs could hit on Docker Hub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f skips only what needs it run --dry-run passes its sandbox provider, emulation choice and progress to materialize, so it says what it would compose of a reused computer env and lists those envs. With emulation off, an arch a refused link runs natively still fails, naming the refusal; only an arch no link runs natively is skipped. The Modal-gated builder test passes emulating=True to VmBuilder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y covers builder.FORMAT The sandbox provider, the emulation choice and the progress callback travel as one Composing, through the puts, AuthoringContext, materialize, run and register, rather than as three arguments each. The build key covers builder.FORMAT, raised with a change that alters what an image holds, in place of builder.py's source, so a change to how a build gets there doesn't build every image again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
builder_in_vm.sh holds what a builder VM runs in shell, as subcommands (buildx, emulate, files, link, check, runnable, download, installed), sent to the VM once. Those that don't use Docker run on Linux and macOS alike, so a unit test runs them for real, and the fake VM runs each line in bash here, faking only Docker and uploads to the store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The builder tests (VM puts, chains, refusals, emulation, the VM's shell helper, progress and the local builder's Docker calls) move to test_computer_builder.py; the fakes and helpers both files use to _computer.py, and their fixtures to a conftest.py beside them. No test changes what it checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earakely-scale
deleted the
edgararakelyan/fd-4734-m19-compose-on-a-sandbox-provider-given-per-put-else-the
branch
October 30, 2026 22:54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A computer env now composes on the put's sandbox provider, by default the env's (
get_env_sandbox_provider()). With[sandbox] default = "local"nothing changes: the put builds with this machine's Docker as before. With a VM provider such asmodal_vm, each form and arch resolves and builds in a builder VM of its own, which fetches the put's stored files by signed URL, takes the rest inline, pushes straight to the image store, and is always torn down.Design doc: CUA Env M1.9: Compose on a Sandbox Provider (six choices, all decided: 1a 2a 3a 4a 5b 6 Modal VM). Issue: FD-4734 (absorbs FD-4364).
How
env/envs/computer/utils/builder.py). OneBuilderinterface with two sides:LocalBuilderis the code that ran before, moved out ofcomposer.pyandpackages.py.VmBuilderruns the same steps in a VM throughexec_script. Its shell steps live in one helper,builder_in_vm.sh, sent to the VM once and run by subcommand (files,link,check,runnable,download,installed,buildx,emulate).Builds). Per arch, the first link of the chain that runs it natively, else the first that emulates it.ON_THIS_MACHINEwith noREACHES_HOST_AT), or when it gives VMs that reach the stores. That means an object store that signs URLs and a registry not on this machine; a VM on this machine reaches them anyway.binfmt_misc, then registers QEMU with a pinnedtonistiigi/binfmt; the pair is recordedemulated. On Modal VM's kernel (7.2), a mount inside the privileged container gets an instance of its own, so the host mount is needed first.buildxplugin, soRUN --network=nonecouldn't build. A VM without one gets a pinned buildx release (v0.38.0), checked by sha256.--no-emulateonenv build,bundle registerandrun, andemulate=Falseon a put, leave an arch no link runs nativelyskippedinstead of emulating it. The reason names each link, the pair isn't a failure (env buildexits 0), and it stays pending, so a later put on a provider that runs it natively, or with emulation on, builds it. On Modal VM for real, arm64 was skipped in 1 s and amd64 was ready in 2 min; re-registering withmodal_vm,localthen built arm64 natively here in 52 s.--no-emulate, and addinglocalwhen this machine runs the arch. binfmt is pulled throughmirror.gcr.io, with the same pinned digest, so Docker Hub's per-IP limit on anonymous pulls can't hit it.registry_api.write_index) instead ofdocker buildx imagetools create. A put needs no local Docker at all, and the index is OCI unless every manifest is Docker's.builder). The key coverscomposer.pyandbuilder.FORMAT, raised with a builder change that alters what an image holds, instead of the builders' source. So a change to how a build gets there doesn't rebuild every image.ConflictErrorwhen it stops a sandbox, say) become aRuntimeErrornaming the VM. The pair fails alone instead of the whole compose.on_progresscallback, andbundle register,runandenv buildprint them without--verbose. This replaces the oldon_buildhook for computer envs, whose "building with docker" line was wrong for a remote build. A realbundle register --sandbox modal_vm,local:runorbundle registerreuses an unchanged computer env, it composes the env's pending pairs on its provider, asagent-env env builddoes, writing no new version. Pending means failed, stopped, or built but unprobed though the provider runs that arch natively. A dry run says what it would compose (envs/kit: would compose container/arm64).bundle registergoes on to write when anything is left, instead of stopping at "Nothing to write"..agentenv/are refused at put. That folder is the framework's: the app's own server keeps its socket and build settings there, and a build settings file there would reach the app unchecked. The build settings file the composer writes goes to a builder VM inline.Composingcarries the sandbox provider, the emulation choice and the progress callback through the puts,AuthoringContext,materialize,runandregister.compose,pending,ComputerEnv.putandComputerAppEnv.puttakesandbox_provider.AuthoringContext.sandbox_providerandmaterialize(sandbox_provider=)pass it to bundle writes.--sandboxonagent-env env buildandagent-env bundle register.agent-env run --sandboxnow composes there too.Breaking
on_buildis gone fromComputerEnv.put,ComputerAppEnv.put,composer.composeandAuthoringContext. Passon_progress, a callable that takes each progress line (on_progress=lambda line: ...), instead.Not in this PR
--no-waiton a put (from the envs design's M4.3 scope): not needed for now, since a put shows its progress.pull_imagesandput_from_githubwork the same way, so all three would change together.Testing
Unit: 11,929 passed; the one failure is the macOS-only Hermes pipe test, as on main. New tests:
VmSandboxprovider composes the test env with a VM per arch:builder_in_vm.sh's subcommands run for real in bash: plain files listed but not links, runnables marked, links, a checksum mismatch named, a download's sha256. The fake VM runs every other line in bash too, faking only Docker and uploads.write_index: a token that pushes; an image that is an index keeps its manifests; Docker vs OCI list.env build --sandbox, andmaterializepassing the provider and progress through.--no-emulateskips an arch no link runs natively, and a laterenv buildemulates it; VM and resolver limits per arch..agentenv/file is refused. An app stored before with one is built on a VM with the env's build settings; without the fix it gets the app's own copy.Integration, laptop:
computer_app_testandcomputer_env_test: 6 passed (6 min), local Docker andlocalhost:5000.builder_modal_vm_test(slow,remote_sandbox-gated): 1 passed in 7 min. On an amd64 Modal VM it resolvesjqandsixfor arm64 under QEMU, stores a file off the VM through the local store, builds, and reads back what installed.Remote end to end from the arm64 laptop, with S3, ECR and Modal VM (the CI project's keys, in-process):
env build --sandbox modal_vmenv build --sandbox modal_vm,localtask run --env-sandbox modal_vm/localbundle register --sandbox modal_vm,localhello=version, anoble-backportspackage,ghfrom a repo with a binary keyring, zeekbtestfrom a flat repo (suite="/"),netifacesbuilt from sdist with gcc,jqper arch, run scripts as root and as the session user, an install step.modal_vmalone reused both images by key, the laptop-built arm64 included; nothing builtdockeron PATH andDOCKER_HOSTunreachable. amd64 ready, arm64 emulated unverified, lock files staged from S3 by signed URL, 20.5 min.Found by running it, fixed here:
binfmt_miscmount.ConflictError(Modal stopped the first arm64 VM 11 min into its resolve) aborted the whole compose.isinstancecheck on the local provider broke the "ask the provider what it declares" rule.The stopped VM's cause is unknown: memory isn't it (the rerun sampled about 400 MB of 8 GB), and the rerun completed.
🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no new actionable issue was found.Summary
Computer envs now build on the selected sandbox provider, using local Docker or a separate builder VM for each architecture.
.agentenv/folder.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Put, run, register, or env build] --> B[Select pending pairs] B --> C{Image already stored?} C -->|Yes| G[Probe on a native provider] C -->|No| D[Choose a builder for the architecture] D --> E[Resolve packages and build locally or in a VM] E --> F[Push image and tear down builder VM] F --> G G --> H[Record each result and write image index]Reviews (9) · Last reviewed commit: "test(computer): where and how a pair bui..." · Reviewed by Greptile