Amends 0194: decisions 2 and 5 (the single box and the
limit of three worktree agents). Worktrees for agent work move into one fresh sandbox per task on
a pool of agent boxes, and the box plan stays. Amends
0174 decision 1
(public repositories publish images to GHCR; private ones stay on ECR). Amends
0227 (a Deferred row for a microVM tier
for tenant code). Leaves 0154 and
0267 unchanged.
Context
Agent work ran on one shared machine, with at most three worktree agents at once (0194). On 2026-10-22 the agent box queue at Academorix showed what that costs. CI jobs of 64 to 70 minutes held three of four boxes, and each box ran one job at a time at low load. A 16 GB box swapped, new spot boxes waited on AMI snapshots, and one seed test took 23 minutes. This ADR is about compute for internal development only. “Sandbox” in 0210, 0242 and 0280 means a tenant sandbox, which is data and is out of scope here. Tenant code stays in the pool of 0154.Decision
Purpose, placement and provider
- Use (SB1). Sandboxes run agent development, tests and workflow test runs, all internal. Tenant code keeps the QuickJS-WASM and Pyodide pool of 0154, unchanged. Its tenant-code item in STA-307 becomes “confirm 0154 holds”.
-
The package now (SB2, owner’s choice). The SandboxProvider port lives in
@stackra/sandbox, in the runtime category, now. It has a local Docker driver and an OpenSandbox driver. The alternative weighed was a Figentra-internal script intools/scriptsuntil a product service needed the port. The owner chose the package now, against that alternative. The package does not depend on Vellum: Vellum records the runner’s evidence (0286 decision 11). - Provider order (SB3). The box with worktrees comes first, as 0194 plans. The spike measures OpenSandbox on the box as the isolation layer, in place of a Linux user per agent. Daytona is the managed fallback at about 30 agents.
- One fresh sandbox per task (SB4, owner’s choice). Each task gets one fresh container sandbox, created from the prebuilt image of decision 10 and destroyed when the task ends. No workspace is shared between tasks. Sandboxes run on a pool of agent boxes: the pool starts with one box, and instances from the same image are added past about six concurrent sandboxes. The alternative weighed was to share the agent box of STA-236 until more than six sandboxes were needed at once, with worktrees kept on it. The owner’s rule is a fresh sandbox per task and never a shared workspace, and this decision follows it.
- Isolation (SB5). Docker containers run with user namespaces, with no host mounts beyond the task’s worktree. Docker is reachable for Testcontainers through a per-sandbox rootless daemon. gVisor and microVMs are not used on day one.
- MicroVMs are deferred (SB6). A microVM tier waits until untrusted third-party code must run outside 0154’s limits, for example marketplace app builds. When it is revived, the first choice is a hosted Firecracker (E2B), which needs no KVM host of ours. This ADR adds the deferred row to 0227.
Lifecycle, limits and network
- Limits on one task’s sandbox (SB7). The default sandbox is 2 vCPU, 8 GB of memory and 40 GB of disk. A sandbox stops after 60 idle minutes and lasts at most 8 hours. Both limits apply to a single task’s sandbox, and a sandbox is never reused by another task. A box runs at most six sandboxes concurrently. The pnpm store and the image cache are mounted read-only from the host.
- Egress (SB8). Egress is denied by default, with an allowlist: the npm registry, GitHub, Anthropic and Bedrock, Infisical (EU), Encore, ECR and GHCR, Docker Hub and Linear. Every connection is logged. Web research stays on the coordinator, not in the sandbox.
Secrets
- Infisical everywhere (SB9, owner’s choice). Each sandbox gets a short-lived Infisical machine identity, scoped to
its task’s path, and runs its commands with
infisical run --. Values exist only in the process environment: never on disk and never in the model context. The alternative weighed was a GitHub App installation token per task, with no Infisical identity inside a sandbox. The owner chose the Infisical identity. The reason the alternative gave still holds: an agent’s shell can read whatever is injected, so the credential must be short-lived and narrow, and it is. Infisical Agent Vault stays Deferred under 0267. It is evaluated in the STA-307 spike, and a new ADR revives it only if it proves out.
Environment and images
- One environment definition (SB10).
.devcontainer/devcontainer.json(STA-313) is the single environment source. CI builds one image from it andmise.toml. The devcontainer, Codespaces, the box and the sandboxes all use that image. Public repositories publish it to GHCR, and private repositories to ECR, the registry of 0174. The image is prebuilt in CI and kept warm (lesson 4).
Access for agents
- One implementation, two entry points (SB11, owner’s choice). The sandbox sits behind the runner API of STA-255,
which takes a sandbox option. From day one it is also an MCP tool in
@stackra/mcp, with a usage skill in@stackra/skills, installed bystackra agents setupfor Claude Code and OpenCode. The alternative weighed was the runner flag first and MCP later. The owner chose both at once. The MCP tool wraps the same call, so there is still one implementation. Vellum calls the same API and records the job id (0286 decision 11).
One system
- The runner and the sandboxes are one system (SB12). Runner jobs run inside sandboxes on the box or on its autoscaled copies. Decided reading: CI and agents use one lock implementation, with separate slot pools. CI runners keep their dedicated boxes under a hard cap (lesson 1), and agent sandboxes take only agent slots, so CI never waits behind agent sandboxes.
Binding lessons from the Academorix box queue
These seven requirements are binding on STA-255, STA-307 and STA-236. They were observed on 2026-10-22.- Separate capacity. CI runners are pinned to dedicated boxes with a hard cap. Agent sandboxes never compete with CI for the same slots.
- Two job classes. Heavy jobs (
encore test, end-to-end tests and the fullverify) take an exclusive slot on a box. Light jobs (install, lint, typecheck and single test files) run in parallel slots. Slots are sized from measured CPU and memory, not from one lock per box. - Memory sizing. No 16 GB boxes for heavy jobs. A box runs one heavy job, on an m7i.4xlarge (64 GB), with light slots beside it, within the sandbox limits of decision 7.
- Warm images. The sandbox image (decision 10) is prebuilt in CI and kept warm. Scale-out uses a ready image or a warm pool, never a fresh AMI snapshot on the hot path.
- Queue visibility. The runner exposes the queue length, the wait time and the holder of each slot. An alert fires when a wait exceeds 10 minutes, and the autoscaler adds a box from the warm image.
- Fast tests by design. Seeds are idempotent, and each test is isolated through fixtures, transactions or Testcontainers snapshots. Long suites are sharded. A single test over 5 minutes fails review.
- Main stays green. Once the queue is live, a red main blocks new merges through it and pages the owning agent (planned under STA-333; 0288). Agents do not stack pull requests on a red main.
Exit criteria, caps and order
-
The spike bar (SB13). STA-307 passes only if all of these hold:
- a warm sandbox starts in 10 seconds or less;
- at least six run concurrently on an m7i.4xlarge;
- Testcontainers works;
- the egress allowlist is enforced;
- only short-lived Infisical credentials are inside, as decision 9 says;
- it costs no more than $0.10 per agent-hour above the bare box.
- Caps and residency (SB14). Sandboxes fall under the box’s caps of $150 a day and $4,500 a month, both set by this decision. Any hosted fallback needs an owner raise. Sandboxes run in eu-central-1, hold no tenant data, and use only synthetic seeds.
- Account (OP3). The shared tooling AWS account is created now, under AWS Organizations with Identity Center, as 0172 says. The box of STA-236 is built there, not in the Academorix account.
- Order (OP4). STA-236 (the box) comes first, then STA-307 on the box, then STA-298 uses the box for evidence (0286 decision 11). The local Docker driver is built first for STA-307. STA-298’s steps that need no evidence may start on a laptop meanwhile.
Consequences
- Agent capacity becomes sandbox capacity. Worktree agents move into one fresh sandbox per task on the agent boxes, and the three-worktree limit of 0194 is replaced by the limit of six sandboxes per box, with at most one heavy job at a time.
- CI needs its own dedicated boxes (lesson 1), which is extra infrastructure beside the agent box.
- The agent’s shell can read its own environment, so the credential is short-lived and narrow. The guard that blocks
infisical run -- envandprintenvstays in force. - Two new surfaces must be built and kept in step: the
@stackra/sandboxpackage and the MCP tool with its skill. - The sandbox image becomes a release artefact, built in CI and kept warm.
Enforcement
planned — the bar of decision 13 is the STA-307 spike’s own test; the egress allowlist and the logging of decision 8 need a test in@stackra/sandbox; the queue metrics and the alert of lesson 5 belong to the runner; the five-minute test limit of
lesson 6 belongs to review until a test runner check exists; the warm-image build is a CI job; the block on a red main and the page to the owning agent (lesson 7) are planned under STA-333.