Cloudflare Dynamic Workers: Sandbox AI Code 100x Faster

Cloudflare Dynamic Workers: Sandbox AI Code 100x Faster

K
Kodetra Technologies·April 30, 2026·5 min read Intermediate

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 emits fetch('https://attacker.com'), it works. Always opt out unless you have a reason not to.
  • Re-loading per requestload() is fast but not free. For a long-lived API, use get(id, ...) so the isolate stays hot.
  • Passing live env directly — never spread your Worker's env into the sandbox. Build a minimal RPC surface and pass only that.
  • Wrangler version — older Wranglers don't know about worker_loaders and silently drop it. Pin wrangler@^4.40.
  • CPU limits — sandboxed isolates share your Worker's CPU budget. Long-running agent loops should use ctx.waitUntil or queues.

Quick reference

APIWhen to useCaches?
env.LOADER.load(opts)One-shot agent codeNo
env.LOADER.get(id, factory)Long-lived authored servicesYes (by id)
worker.getEntrypoint()Call exported RPC methods
worker.getEntrypoint().fetch()Call as HTTP handler
globalOutbound: nullBlock 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/codemode if 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

Subscribe to join the conversation...

Be the first to comment