
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