Skip to content
Cloudflare Dynamic Workers: Sandbox AI Code 100x Faster — ContentBuffer guide

Cloudflare Dynamic Workers: Sandbox AI Code 100x Faster

K
Kodetra Technologies··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

Found this useful?

Get new AI guides for builders by email. Free.