
Cloudflare Dynamic Workers: Sandbox AI Code 100x Faster
Summary
Run AI-generated TypeScript in JS isolates - faster startup than containers.
On April 9, 2026 Cloudflare opened the public beta of Dynamic Workers, a Worker Loader API that spins up a sandboxed V8 isolate per request — roughly 100x faster than booting a container. For agent builders this is huge: when an LLM emits TypeScript at runtime, you no longer have to hand it to Docker and wait 500ms for a cold start. You hand it to env.LOADER and it runs in single-digit milliseconds, with no network access unless you grant it.
This guide walks you through wiring a Worker Loader binding, executing untrusted LLM-generated code, exposing scoped RPC tools to the sandbox, and bundling npm dependencies on the fly. By the end you'll have a working tool-execution sandbox you can drop into any agent loop.
Prerequisites
- Cloudflare account on the Workers Paid plan (Dynamic Workers is in open beta)
- Node 20+ and Wrangler 4.40+:
npm i -g wrangler@latest - Basic TypeScript and an existing Worker project
- An OpenAI / Anthropic / Workers AI key if you want to wire it to a real model
Step 1 — Add the Worker Loader binding
Open wrangler.jsonc and declare a loader. The id is what you reference in code as env.LOADER.
{
"name": "agent-sandbox",
"main": "src/index.ts",
"compatibility_date": "2026-04-15",
"compatibility_flags": ["nodejs_compat"],
"worker_loaders": [
{ "binding": "LOADER" }
]
}
Generate types so your IDE knows about Env.LOADER:
npx wrangler types
Step 2 — Run LLM-generated code in 5 lines
Here's the smallest end-to-end example. The model returns a string of TypeScript, and env.LOADER.load() instantiates a fresh isolate with globalOutbound: null so the code cannot make any outbound HTTP calls.
// src/index.ts
export default {
async fetch(req, env: Env): Promise<Response> {
const agentCode = `
export default {
async myAgent(input, env, ctx) {
return { doubled: input.value * 2, ts: Date.now() };
}
}
`;
const worker = env.LOADER.load({
compatibilityDate: "2026-03-01",
mainModule: "agent.js",
modules: { "agent.js": agentCode },
globalOutbound: null, // fully air-gapped
});
const result = await worker.getEntrypoint().myAgent({ value: 21 });
return Response.json(result);
}
} satisfies ExportedHandler<Env>;
Example output:
$ curl https://agent-sandbox.you.workers.dev
{ "doubled": 42, "ts": 1745983204221 }
Step 3 — Hand scoped tools to the sandbox
Air-gapping is the default, but most agents need some tools. The env field on load() lets you pass RPC stubs — anything that implements Workers RPC. The sandbox can call them, but it cannot reach anything else on your account.
// Define a tool the agent is allowed to call.
class ChatRoomAPI extends WorkerEntrypoint {
async post(message: string) {
// ... write to Durable Object, D1, etc.
return { id: crypto.randomUUID(), message };
}
}
const chatRoomStub = /* RPC stub to your ChatRoomAPI */;
const worker = env.LOADER.load({
compatibilityDate: "2026-03-01",
mainModule: "agent.js",
modules: {
"agent.js": `
export default {
async myAgent(text, env) {
// Only env.CHAT_ROOM is reachable. Nothing else.
return env.CHAT_ROOM.post(text);
}
}
`,
},
env: { CHAT_ROOM: chatRoomStub },
globalOutbound: null,
});
const out = await worker.getEntrypoint().myAgent("hello");
Example output: { id: "5c0f...", message: "hello" }. The sandbox cannot read other secrets, hit the public internet, or talk to KV — it sees only what you injected.
Step 4 — Cache warm sandboxes with get()
Use load() for one-shot execution. Use get(id, factory) when you want the isolate to stay warm across requests — perfect for an HTTP server the LLM authored. The factory only runs on a cache miss.
import { createWorker } from "@cloudflare/worker-bundler";
const worker = env.LOADER.get("hono-app-v1", async () => {
const { mainModule, modules } = await createWorker({
files: {
"src/index.ts": `
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello from a sandboxed Hono app'));
app.get('/json', (c) => c.json({ ok: true }));
export default app;
`,
"package.json": JSON.stringify({ dependencies: { hono: "^4.0.0" } }),
},
});
return { mainModule, modules, compatibilityDate: "2026-01-01" };
});
return worker.getEntrypoint().fetch(request);
@cloudflare/worker-bundler resolves npm deps and builds a module graph in-process — no Docker, no Webpack, no S3 round trip.
Step 5 — Plug it into a tool loop with codemode
If you use the Vercel AI SDK or similar, @cloudflare/codemode wraps the loader as a single "codemode" tool the model can call to execute multi-step plans in one shot.
import { DynamicWorkerExecutor } from "@cloudflare/codemode";
import { createCodeTool } from "@cloudflare/codemode/ai-sdk";
import { generateText } from "ai";
const executor = new DynamicWorkerExecutor({
loader: env.LOADER,
globalOutbound: null,
});
const codemode = createCodeTool({ tools: myTools, executor });
const { text } = await generateText({
model,
messages,
tools: { codemode },
});
Common pitfalls
- Forgetting
globalOutbound: null— the default is to inherit your Worker's network. If the model emitsfetch('https://attacker.com'), it works. Always opt out unless you have a reason not to. - Re-loading per request —
load()is fast but not free. For a long-lived API, useget(id, ...)so the isolate stays hot. - Passing live env directly — never spread your Worker's
envinto the sandbox. Build a minimal RPC surface and pass only that. - Wrangler version — older Wranglers don't know about
worker_loadersand silently drop it. Pinwrangler@^4.40. - CPU limits — sandboxed isolates share your Worker's CPU budget. Long-running agent loops should use
ctx.waitUntilor queues.
Quick reference
| API | When to use | Caches? |
|---|---|---|
| env.LOADER.load(opts) | One-shot agent code | No |
| env.LOADER.get(id, factory) | Long-lived authored services | Yes (by id) |
| worker.getEntrypoint() | Call exported RPC methods | — |
| worker.getEntrypoint().fetch() | Call as HTTP handler | — |
| globalOutbound: null | Block all outbound | — |
| env: { ... } | Inject scoped RPC tools | — |
Next steps
- Read the Dynamic Workers docs for full binding options
- Pair this with Agent Memory for stateful long-running agents
- Try
@cloudflare/codemodeif you already use the Vercel AI SDK - Add request-level CPU and wall-clock limits before going to production
Dynamic Workers turn the "untrusted code execution" problem from a multi-second container boot into a 5ms isolate. If you've been stalling on shipping an agent that writes its own code, this is the unblock.
Comments
Be the first to comment
Found this useful?
Get new AI guides for builders by email. Free.