Command palette
⌘K to open • ↑/↓ to move • Enter to go

Docs / Architecture

How Lab works

You send code. Lab runs it. You get a receipt.

That’s the whole idea. Your agent (or script, or curl command) sends JavaScript to Lab. Lab runs it inside a Cloudflare sandbox — a fresh, isolated environment that starts in milliseconds. When you run code on Lab, the receipt JSON is available at GET /results/:id.json. Humans can open GET /results/:id in the public app viewer. Successful runs include full step data. Failed or aborted runs include the error and reason; per-step detail may be partial.


Permissions start at zero

Your code can’t do anything dangerous by default. It runs in a locked-down sandbox with no network access, no storage, no AI calls — nothing.

You unlock what it needs by declaring permissions:

PermissionWhat it unlocks
kvReadRead from Cloudflare KV storage
workersAiCall Cloudflare AI models
r2ReadRead files from Cloudflare R2
d1ReadQuery the guest-readable engine D1 database
spawnLaunch more sandboxed code (with equal or fewer permissions)
durableObjectFetchCall a Durable Object through the host Worker
containerHttpCall a configured container binding
petriMutate and read a Petri dish (experimental)

If you don’t declare it, the code can’t use it. There’s no “grant all” shortcut.


Multi-step pipelines

You can run multiple pieces of code in sequence. The output of each one becomes the input of the next:

Step 1: Fetch data     →  { users: [...] }
Step 2: Transform it   →  { cleaned: [...] }
Step 3: Validate it    →  { valid: true, count: 42 }

Each step runs in its own sandbox. Each step can have different permissions. Step 1 might need kvRead. Step 2 might need nothing. Step 3 might need d1Read.


Code can launch more code

If you grant the spawn permission, your code can launch additional sandboxes — useful for parallel work or breaking a problem into parts.

There’s a depth limit. Each level down gets the same or fewer permissions than its parent, never more. At depth 0, spawning is blocked entirely.


What gets saved

Every run saves a JSON result document. GET /results/:id.json is the canonical machine path, and GET /results/:id is the human-facing viewer on the public app.

For successful runs, it contains:

  • The code that ran in each step
  • Each step’s input and output
  • Timing for each step

For failed or aborted runs, it contains the top-level error and reason. Per-step detail depends on where the run stopped and may be partial or empty — see Failures & step data.

See the full schema for details.


The four packages

PackageWhat it doesInstall
@acoyfellow/labTypeScript client — run code, get resultsnpm i @acoyfellow/lab
@acoyfellow/lab-cliCLI — agents shell out, get results backnpm i -g @acoyfellow/lab-cli
@acoyfellow/lab-mcpMCP server — plug Lab into Cursor, Claude Codenpm i -g @acoyfellow/lab-mcp
@acoyfellow/lab-petriPersistent shared state for experimentsnpm i @acoyfellow/lab-petri

Under the hood (for contributors)

Cloudflare Worker Loaders

Lab runs code using Worker Loaders — a Cloudflare feature (closed beta) that creates V8 isolates at runtime from a string of code. Single-digit millisecond startup, near-zero cost.

Worker Loaders don’t have a built-in permission model — an isolate either has full network access or none. Lab adds the permission layer on top.

How permissions work internally

Permissions that can’t be passed directly into a sandbox (AI, R2, D1, Durable Objects) use a proxy pattern: the sandbox calls an internal URL, and the parent Worker handles it with the real Cloudflare bindings. This keeps privileged access on the host side.

KV works differently — the parent reads the data into memory before creating the sandbox, and injects it as a snapshot. This means KV data is a point-in-time copy, not a live connection.

How spawning works internally

Sandboxes can’t create other sandboxes directly — they don’t have the Worker Loader binding. Instead, they make a fetch request back to the parent Worker via a SELF service binding, which creates a new sandbox on their behalf. Depth is decremented at each level.

Effect (TypeScript library)

The Worker internals use Effect for typed error handling, service injection, and composition. New Worker-side behavior should stay in Effect pipelines. The client libraries and website are plain TypeScript/Svelte over fetch.

Code generation (`/run/generate`)

The /run/generate endpoint takes a natural language prompt, builds a system prompt from capability hints, and runs a Cloudflare AI model. The generated code gets cleaned up (strip markdown fences, unwrap wrappers, auto-add return) before running in a sandbox.