ADR 0012: Host the Sandbox class in its own Worker, deployed in one upload
- Status: Accepted (supersedes the attach step of ADR 0011)
- Date: 2026-10-09
- Decider: Project owner
Context
Section titled “Context”ADR 0011 added the Sandbox class to the agents Worker by migration, then re-uploaded the
agents script with containers metadata and created the container application. Every API read
looked right (version metadata, namespace use_containers: true, healthy application), yet at
runtime ctx.container stayed undefined in the class. A Durable Object class gets a container
only when the upload that creates the class also carries the container metadata. Wrangler does
this on a first deploy. Version uploads (cloudflare_worker_version) cannot carry
migrations (ADR 0005), so the agents Worker cannot do both.
Decision
Section titled “Decision”rhumbatron-sandbox-<env>is a separate Worker (workers/sandbox) that only exports the SDK’sSandboxclass. A temporary adapter (scripts/sandbox-deploy.ts,terraform_data.sandbox_worker) deploys it with one full upload: code, theSANDBOXbinding, the class migration, andcontainers: [{ name, class_name }]. It then creates the container application, bound by Durable Object namespace ID. On destroy, it deletes both.- The agents Worker binds
SANDBOXcross-script (script_name). Agents deploys never touch the container, so the re-attach step and its detach window are gone. - Verified on dev:
bun run smoke:sandboxforks the Acme Shop repo, clones it, and runsbun install,bun test(13 passing, including the Cart invariant), andbun run typecheck.
Bootstrap note
Section titled “Bootstrap note”A fresh environment has a coordinator <-> event router binding cycle. local.bootstrap = true
omits the coordinator’s EVENT_ROUTER nudge binding for the first apply. Then set it to false
and apply again. Migrations start from { new_tag = "v1", new_sqlite_classes = [...] } and then move
to the steady v1 -> v1.
Update (2026-10-10)
Section titled “Update (2026-10-10)”- Dev and prod both run this layout. The integration Worker also binds
SANDBOXcross-script, for Candidate composition and checks in the same Project Sandbox. - A teardown resource (
terraform_data.sandbox_worker_teardown) runs the destroy mode. It is keyed by the Worker and application names only, so a bundle or size change never deletes the live Worker. - The adapter reads the live migration tag. A first upload creates
v1with theSandboxclass; later uploads keep the live tag. If the application points at an older namespace, the adapter recreates it against the current one. local.paused = truesetsmax_instances = 0on the application.
Diagram
Section titled “Diagram”One terraform apply of an env root (scripts/sandbox-deploy.ts, deploy mode).
flowchart TD
T["terraform apply"] --> D["terraform_data.sandbox_worker<br/>runs sandbox-deploy.ts"]
D --> U["One full script upload: code,<br/>SANDBOX binding, migration,<br/>containers metadata"]
U --> N["Read the Durable Object<br/>namespace ID of Sandbox"]
N --> Q{"Container application<br/>bound to that namespace?"}
Q -- no --> C["Create rhumbatron-sandbox-env<br/>basic, max_instances 1"]
Q -- yes --> P["Patch only on drift<br/>image, size, max_instances"]
C --> W["rhumbatron-sandbox-env Worker<br/>exports Sandbox"]
P --> W
A["rhumbatron-agents-env"] -- "SANDBOX, script_name" --> W
I["rhumbatron-integration-env"] -- "SANDBOX, script_name" --> W