feat(agents): Luke's own sandbox template for native agents

Merged
CH
5d ago
+1397-357

P08b of the rollout in "Bringing Codex or Claude Code Max plans into Luke" (https://tryluke.dev/stage/plan/89f19bfc-88b5-471a-886a-84b00180aa09). It replaces P08's hand-set LUKE_AGENT_SANDBOX_SNAPSHOT with Luke's own template of the coding agent's image, so a native launch always opens on the current image. Nothing routes to native agents yet (that's P11).

What it does

The image module, split out (agent-runtime/sandbox-image.ts)

  • PREPARE_SCRIPT, its pinned installers, and prepareImage move out of sandbox.ts, unchanged byte for byte. sandbox.ts imports them.
  • sandbox.ts reaches eve's sandbox runtime (eve/sandbox/vercel), which a web function can't bundle. The new module reaches only the code extension's tooling installer.
  • eve's own LUKE_SANDBOX_IMAGE is unchanged: same script, same function text, same digest.

The template (native-sandbox-template.ts)

  • Key: prepareImage is run against a recording session, which keeps the files it writes and the commands it runs. That is the approach tools/codex-probe already uses. The key is the sha256 of all of it plus eve's base image. It changes whenever PREPARE_SCRIPT, a pin, prepareImage, eve's tooling, or eve's base image changes.
    • This differs from the literal LUKE_SANDBOX_IMAGE. That value hashes prepareImage.toString(), which can differ between esbuild bundles if identifiers are renamed. The sweep and the launch live in different function bundles, so they could disagree on the key and the sweep would delete the live template.
  • Lock: the template is a persistent sandbox named luke-native-template-<key>, created from eve's base image (vcr.vercel.com/vercel/eve/base:0.74.0; a test holds it to the installed eve). Vercel refuses a second create of the same name with 409, so concurrent launches build it once and the others wait.
  • No function waits on the build: the recorded preparation is uploaded and run as a detached command, whose id goes in a tag.
  • Ready: a reader that finds the build exited 0 tags it lukeTemplateBuilt, then snapshots it with no expiration.
    • A template is ready when it is marked built, stopped, and holds a snapshot.
    • The mark is what tells a whole image apart, because a persistent sandbox also snapshots itself on stop, including mid-build.
    • A snapshot stops the session, so when two readers (or two sweeps) race, Vercel takes the first snapshot and refuses the second. The loser reads on and finds the template ready.
  • Failure and recovery:
    • A failed build is stopped and marked. It answers failed for 30 minutes, then is rebuilt.
    • A template stopped unready, or created but never started for 5 minutes, is deleted and rebuilt.
    • Tags in use: 4–5 of Vercel's 5.

The seam (agent-sandbox-image.ts)

  • AgentSandboxImage keeps snapshotId: Effect<Option<string>, …>. The layer is now agentSandboxImageFromTemplate.
  • A launch waits on a build for up to 3 minutes, polling every 10 s. If the image is still being prepared after that, open answers FAILED with the exported IMAGE_PREPARING_STATUS (425). P11 shows it as "Preparing the agent's image…" and retries; this was agreed with the orchestrator.
  • The env-var filler is removed: LUKE_AGENT_SANDBOX_SNAPSHOT, agentSandboxSnapshot, and its test fixtures.

The minute sweep

  • It moves the build on and snapshots it whether or not a launch is waiting.
  • Where LUKE_NATIVE_AGENT_ACCOUNTS is non-empty, it builds a new image's template before the first launch asks.
  • It deletes other keys' templates once they're a day old, so a Preview of another image keeps its own template while it's being tested.

Vercel API (vercel-sandbox-api.ts)

  • New calls, copied from @vercel/sandbox 3.8.0: a sandbox read by name without resume, a list by name prefix, a create from an image, and a session snapshot.

P08 follow-up from Devin's review of P09 (the launch race)

  • A relaunch now tags its first event number before its command starts.
  • runnerEnded reads that tag on its own, so an END that the replaced launch posts late can't stop the new launch.
  • Ported onto P21's shared runner-sessions.ts after merging main. Its tag is lukeRunnerLaunch (<firstSequence>-<stamp>), written before the command starts, and runnerEnded reads it on its own. The sandbox still carries 5 tags. ImagePreparing → 425 is in the shared failedStatus, so Claude Code and Codex both get it.
  • The other finding (the sweep retrying a failed stop) is split out as P08c.

Bundle evidence (eve stays out of the web functions' runtime)

From apps/web's real scripts/bundle-functions.ts metafile on this branch:

functionreaches the templateeve inputseve runtime (execution/, runtime/, sandbox/providers, microsandbox, just-bash, compiled @vercel/sandbox)
maintenance-sweep.jsyes7: extensions/code/extension/lib/{sandbox,tooling,shell,signed-commit,command-failure}.js, computer-use/extension/lib/computer-use.js, compiled/@vercel/connect0
default.js, pull-request-chapters.js, terminal/sessions.js, voice/sessions.jsno00

A probe bundle of claude-code-sessions.ts plus the seam (what P11's function will reach) has 15 eve inputs, all under extensions/code/extension/lib and computer-use/extension/lib, and no runtime. undeclaredImports passed.

Checks

  • native-sandbox-template.test.ts runs against a fake of the Sandbox REST API with TestClock. It covers:
    • reuse of a ready template;
    • a build when one is missing, from eve's image with today's PREPARE_SCRIPT;
    • a single build and a single snapshot under three concurrent launches;
    • racing snapshotters, which take one snapshot;
    • a new build after a key change, with the old template swept once it's a day old;
    • the sweep pre-building;
    • a failed build's retry window;
    • a template stopped half-built being rebuilt, not opened;
    • the seam's bounded wait failing as preparing, then answering the snapshot.
  • claude-code-sessions.test.ts adds two tests: a late END from the replaced launch while the new command starts is stale (this test fails without the fix), and a launch while the image is preparing answers 425 and opens nothing.
  • Typecheck passes for service, web, dev, luke and both probes. Biome, oxlint, discarded-effect and knip are clean.
  • pnpm check:affected ran lint green, then its parallel typechecks were OOM-killed (137) in my sandbox. P08 hit the same thing. I ran them one by one instead, and CI gates the rest.
  • Not run live: this sandbox has no Vercel credentials, so the template hasn't been built against real Vercel. The first native launch on Preview will be its first live build.
  • The developer waived per-PR Preview runs for this rollout.

Follow-ups

  • P08c: the sweep stops idle native sandboxes whose turn-end stop failed.
  • A snapshot that Vercel deletes despite having no expiration would make the launch fail on create. The template isn't marked stale in that case.

Devin Review