The feedback loop for AI agents.
An agent writes code. Lab runs it in a Cloudflare sandbox and saves canonical result JSON plus a shareable viewer URL. Successful runs include full step data — code, inputs, outputs, timing. The agent reads the JSON, fixes what broke, and runs again. Same loop a developer uses, except the agent does it.
agent writes code → Lab runs it → saved result (what happened) → agent reads, fixes, reruns
Try it now: lab.coey.dev/compose — run a chain, click the saved-result link.
0.0.3 — API and saved-result shapes may still move. Pin to exact versions or self-host.
npm install @acoyfellow/labimport { createLabClient } from "@acoyfellow/lab";
const lab = createLabClient({
baseUrl: process.env.LAB_URL, // your Lab instance URL
});
// Self-healing pipeline: load broken JSON → try parse → heal → validate
const out = await lab.runChain([
{ name: "Load", body: `return { raw: '{"users": [{"id": 1,}]}', attempt: 1 }`, capabilities: [] },
{ name: "Parse", body: `try { return { ok: true, data: JSON.parse(input.raw) } } catch(e) { return { ok: false, error: e.message, raw: input.raw } }`, capabilities: [] },
{ name: "Heal", body: `if (input.ok) return input; const fixed = input.raw.replace(/,(\\s*[}\\]])/g, '$1'); return { ok: true, data: JSON.parse(fixed), healed: true }`, capabilities: [] },
{ name: "Verify", body: `return { valid: input.ok, healed: !!input.healed, users: input.data?.users?.length }`, capabilities: [] },
]);
console.log(out.result); // { valid: true, healed: true, users: 1 }
console.log(out.resultId); // → machine JSON: $LAB_URL/results/<id>.json; viewer: $LAB_URL/results/<id>Each step runs in its own sandbox. Step 2's output flows to Step 3's input. The result is saved at a URL — share it to show what happened.
These are the workflows agents build with Lab. Every pattern saves a result. The result is the point.
| Pattern | What happens | The result shows |
|---|---|---|
| Prove It | Agent writes code + edge cases, runs them all | 10/10 pass — the receipt |
| Self-Healing | Step fails → agent reads result → patches → retries | The saved result, including any successful chain steps |
| Agent Handoff | Agent A → B → C, one chain | Who did what |
| Canary Deploy | Old vs new logic, same inputs | What changed |
| Stress Test | Run N times, find where it breaks | Which runs failed and why |
See all patterns: lab.coey.dev/docs/patterns
Workflows — chain JavaScript steps together. Each step's return value becomes the next step's input. Each step runs in its own V8 sandbox via Cloudflare Worker Loaders. Nothing leaks between steps.
Capabilities — each step can only access what you explicitly grant:
| Capability | What the guest gets |
|---|---|
kvRead |
kv.get(key) / kv.list(prefix) — read-only KV snapshot |
workersAi |
ai.run(prompt) — Workers AI (keys stay on host) |
r2Read |
r2.list() / r2.getText(key) — R2 object storage |
d1Read |
d1.query(sql) — read-only D1 queries |
spawn |
spawn(code, caps) — nested child isolates with depth budget |
durableObjectFetch |
labDo.fetch(name, { method, path, body }) — Durable Object RPC |
containerHttp |
labContainer.get(path) — bound container service |
No capabilities = pure compute, no I/O. Denied capabilities produce clear errors recorded in the saved result.
Results — every run saves a JSON document. Agents and scripts should read /results/:id.json. Humans can open /results/:id as the viewer over that same saved result. Successful runs include code, capabilities, return values, and timing. Failed or aborted runs include the top-level error and reason; chain step detail may be partial or empty. Share the URL. Fork it into a new run. Hand it to another agent.
| Method | Path | Body |
|---|---|---|
GET |
/health |
health check |
POST |
/run |
{ body, capabilities? } |
POST |
/run/kv |
same — always includes kvRead |
POST |
/run/chain |
{ steps: [{ body, capabilities, name? }] } |
POST |
/run/spawn |
{ body, capabilities, depth? } |
POST |
/run/generate |
{ prompt, capabilities } |
POST |
/seed |
{} — writes demo KV data |
GET |
/lab/catalog |
capability + route metadata for agents |
GET |
/results/:id |
human saved-result viewer |
GET |
/results/:id.json |
canonical saved-result JSON |
npm install @acoyfellow/lab| Method | What it does |
|---|---|
runSandbox(payload) |
Single sandbox run |
runKv(payload) |
Run with KV snapshot |
runChain(steps) |
Multi-step workflow |
runSpawn(payload) |
Nested isolates |
runGenerate(payload) |
AI-generated code + run |
seed() |
Seed demo KV data |
getResult(resultId) |
Fetch canonical saved-result JSON |
Effect client: import { createLabEffectClient } from "@acoyfellow/lab/effect" — same API, returns Effect instead of Promise.
Lab exposes two MCP tools — find (discover capabilities, fetch saved results) and execute (run any mode). Give an agent access to Lab and it can execute code, read saved results, and build on previous runs.
npm install -g @acoyfellow/lab-mcp{
"mcpServers": {
"lab": {
"command": "npx",
"args": ["-y", "@acoyfellow/lab-mcp"],
"env": { "LAB_URL": "https://your-lab.example" }
}
}
}Works with Claude Desktop, Cursor, or any MCP client. See packages/lab-mcp.
Deploy to your own Cloudflare account. Your agents, your data, your capabilities.
git clone https://github.com/acoyfellow/lab.git && cd lab
bun install && bun run deployRequires Cloudflare Workers Paid ($5/mo). Provisions the public app, the private Worker, auth D1, engine D1, KV, Worker Loader, Durable Objects, and optional R2/AI bindings via Alchemy.
worker/ Sandbox engine (Effect v4, Worker Loaders)
index.ts Routes, chain/spawn orchestration, saved-result storage
Loader.ts V8 sandbox lifecycle
guest/templates.ts Guest module composition + capability shims
capabilities/ Capability registry
packages/lab/ TypeScript client (@acoyfellow/lab)
packages/lab-mcp/ MCP server (@acoyfellow/lab-mcp)
src/ SvelteKit app (compose, saved-result viewer, docs)
alchemy.run.ts Infrastructure-as-code
bun dev # Worker (port 1337) + SvelteKit app
bun test # Guest body syntax validation
bun run lint # oxlint
bun run check # svelte-check + typecheckIntegration tests in worker/index.test.ts run against a live Worker and skip gracefully when unavailable.
MIT