From aec05c02c6482d1094f40085194557d9f6d6d841 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 13:34:35 +0000 Subject: [PATCH 1/9] Add Dynamic Workers skill reference and prerequisites guide New product reference covering Cloudflare's Dynamic Workers (runtime V8 isolate execution). Includes API reference, configuration, patterns (code mode, MCP wrapping, OpenAPI, real-time logging), gotchas, pricing, and links to all 6 official examples. Also adds PREREQUISITES.md documenting the research process for adding future product skills (sources to gather, structure to follow, checklist). https://claude.ai/code/session_01CkgwSkxhYZtzvKduJF3xSR --- skills/cloudflare/PREREQUISITES.md | 139 +++++++++ skills/cloudflare/SKILL.md | 2 + .../references/dynamic-workers/README.md | 90 ++++++ .../references/dynamic-workers/api.md | 255 +++++++++++++++ .../dynamic-workers/configuration.md | 153 +++++++++ .../references/dynamic-workers/gotchas.md | 175 +++++++++++ .../references/dynamic-workers/patterns.md | 293 ++++++++++++++++++ 7 files changed, 1107 insertions(+) create mode 100644 skills/cloudflare/PREREQUISITES.md create mode 100644 skills/cloudflare/references/dynamic-workers/README.md create mode 100644 skills/cloudflare/references/dynamic-workers/api.md create mode 100644 skills/cloudflare/references/dynamic-workers/configuration.md create mode 100644 skills/cloudflare/references/dynamic-workers/gotchas.md create mode 100644 skills/cloudflare/references/dynamic-workers/patterns.md diff --git a/skills/cloudflare/PREREQUISITES.md b/skills/cloudflare/PREREQUISITES.md new file mode 100644 index 0000000..69d9a0e --- /dev/null +++ b/skills/cloudflare/PREREQUISITES.md @@ -0,0 +1,139 @@ +# Prerequisites for Adding a New Cloudflare Product Skill + +This document describes the research and source material needed before writing a skill reference for a new Cloudflare product. Following this process ensures the skill is accurate, consistent with existing references, and comprehensive enough to be useful. + +## 1. Gather Primary Sources + +Before writing anything, collect these materials for the product: + +### Official Documentation + +| Source | Where to find it | What it provides | +|--------|-------------------|------------------| +| **LLM reference** | `https://developers.cloudflare.com//llms-full.txt` | Condensed, structured overview of the full product — usually the single best starting point | +| **Developer docs** | `https://developers.cloudflare.com//` | Canonical reference for APIs, config, limits, pricing | +| **Getting started guide** | `https://developers.cloudflare.com//getting-started/` | Setup steps, minimal working examples | +| **Blog announcement** | `https://blog.cloudflare.com//` | Design rationale, performance claims, positioning vs alternatives | + +The LLM reference (`llms-full.txt`) is the highest-signal single source. Start there. Fall back to the full docs for details it doesn't cover. + +### Working Examples + +| Source | Where to find it | What it provides | +|--------|-------------------|------------------| +| **Official examples** | `https://github.com/cloudflare/agents/tree/main/examples/` (or product-specific repos) | Real, runnable code showing intended usage patterns | +| **Starter templates** | Linked from the getting started guide | Minimal scaffolding for common setups | + +**Read the actual source code of every relevant example.** GitHub tree pages only show file listings — you need to fetch each source file individually (via raw URLs or a clone). Examples reveal: +- Real `wrangler.jsonc` configurations (what bindings are actually needed) +- How the product combines with other Cloudflare products (AI, Durable Objects, etc.) +- Idiomatic patterns the docs may not spell out +- Edge cases handled in production-quality code + +### Upstream Repository + +If the product has an SDK or library (e.g. `@cloudflare/worker-bundler`, `@cloudflare/sandbox`): +- Check the npm package for current API surface +- Read the repo README and any inline type definitions +- Note version numbers — skills should reference current versions + +## 2. Understand the Product's Position + +Before writing, answer these questions: + +- **What existing products is this most similar to?** (e.g. Dynamic Workers vs Workers for Platforms vs Sandbox) +- **What's the key differentiator?** (runtime vs pre-deployed, isolates vs containers, etc.) +- **When should someone use this instead of the alternatives?** +- **What bindings/integrations does it need?** (worker_loaders, durable_objects, ai, etc.) + +This informs the README's comparison table and the SKILL.md decision tree entry. + +## 3. Follow the Standard 5-File Structure + +Every product reference in `skills/cloudflare/references/` uses this structure: + +``` +references// + README.md — Overview, architecture, quick start, comparison table + api.md — API reference: methods, types, parameters, return values + configuration.md — wrangler.jsonc setup, binding combinations, CLI commands + patterns.md — Common usage patterns with full code examples + gotchas.md — Errors, limits, pricing, best practices, starter links +``` + +### What goes in each file + +**README.md**: The entry point. Someone reading only this file should understand what the product is, when to use it, and how to get a minimal example running. Include: +- One-line description +- Comparison table vs similar products +- Architecture diagram or description +- Quick start (wrangler config + minimal code) +- Links to the other 4 files and related references + +**api.md**: Complete API surface. For each method/class/type: +- Signature and parameters +- Code example showing usage +- Notes on behavior (caching, async, error cases) +- For RPC/binding patterns, show both the definition and consumption sides + +**configuration.md**: Everything about setup and config. Include: +- The primary binding configuration (wrangler.jsonc and wrangler.toml) +- How it combines with other bindings (show real composite configs from examples) +- Supported languages/runtimes +- CLI commands relevant to this product +- Any bundling or build steps required + +**patterns.md**: Real-world usage patterns, each with complete runnable code. Source these from: +- Official examples (adapt, don't just copy) +- Patterns described in the docs/blog +- Common combinations with other products +Aim for 4-7 patterns ranging from basic to advanced. + +**gotchas.md**: Everything that trips people up. Include: +- Common errors with cause/solution pairs +- Best practices (do/don't format with code examples) +- Pricing table and billing nuances +- Limits table +- Links to starter templates and official resources + +## 4. Update the Skill Index + +After creating the reference directory, update `skills/cloudflare/SKILL.md`: + +1. **Decision tree**: Add a branch in the appropriate "I need to..." tree. Place it near similar products with a description that distinguishes it. + +2. **Product index table**: Add a row in the appropriate section (Compute & Runtime, Storage, AI, etc.). + +## 5. Cross-Reference Other Skills + +Check if existing skills should link to the new one: +- Products it's commonly used with (e.g. Dynamic Workers + Agents SDK, + Tail Workers) +- Products it's easily confused with (comparison in README helps) +- The main SKILL.md decision trees + +## 6. Verify Before Committing + +- [ ] All code examples use current API syntax (check against `llms-full.txt` and docs) +- [ ] `wrangler.jsonc` examples match real working configurations from official examples +- [ ] Pricing and limits are cited from official docs (not guessed) +- [ ] No broken internal links between the 5 files +- [ ] SKILL.md decision tree entry distinguishes this from similar products +- [ ] SKILL.md product index row added in the correct section +- [ ] File structure matches `README.md`, `api.md`, `configuration.md`, `patterns.md`, `gotchas.md` exactly + +## Example: What Was Needed for Dynamic Workers + +| Source | URL | Key information extracted | +|--------|-----|--------------------------| +| LLM reference | `developers.cloudflare.com/dynamic-workers/llms-full.txt` | Full API surface, WorkerCode properties, pricing, billing rules | +| Docs home | `developers.cloudflare.com/dynamic-workers/` | Architecture overview, use cases, security model | +| Getting started | `developers.cloudflare.com/dynamic-workers/getting-started/` | `worker_loaders` binding, `load()` vs `get()`, supported languages | +| Blog post | `blog.cloudflare.com/dynamic-workers/` | V8 isolate performance claims, helper libraries, design rationale | +| `dynamic-workers` example | `github.com/cloudflare/agents/.../dynamic-workers/` | Minimal `load()` pattern, basic wrangler config | +| `dynamic-workers-playground` example | `github.com/cloudflare/agents/.../dynamic-workers-playground/` | `get()` with content-hashed IDs, `@cloudflare/worker-bundler`, Tail Worker + DO logging pipeline, warmup trick | +| `codemode` example | `github.com/cloudflare/agents/.../codemode/` | `AIChatAgent` + `DynamicWorkerExecutor` integration, SQL-backed tools | +| `codemode-mcp` example | `github.com/cloudflare/agents/.../codemode-mcp/` | `codeMcpServer()` wrapper pattern | +| `codemode-mcp-openapi` example | `github.com/cloudflare/agents/.../codemode-mcp-openapi/` | `openApiMcpServer()` for REST API wrapping | +| `worker-bundler-playground` example | `github.com/cloudflare/agents/.../worker-bundler-playground/` | AI-generated app pattern, `createWorker()` with assets, DO persistence | + +Six examples were needed to cover the full range of patterns. The `llms-full.txt` provided the API reference backbone. The blog provided context the docs didn't (helper libraries, performance numbers). The examples provided real wrangler configs and production patterns. diff --git a/skills/cloudflare/SKILL.md b/skills/cloudflare/SKILL.md index 870f9a2..313f608 100644 --- a/skills/cloudflare/SKILL.md +++ b/skills/cloudflare/SKILL.md @@ -40,6 +40,7 @@ Need to run code? ├─ Long-running multi-step jobs → workflows/ ├─ Run containers → containers/ ├─ Multi-tenant (customers deploy code) → workers-for-platforms/ +├─ Execute AI-generated/untrusted code in V8 isolates → dynamic-workers/ ├─ Scheduled tasks (cron) → cron-triggers/ ├─ Lightweight edge logic (modify HTTP) → snippets/ ├─ Process Worker execution events (logs/observability) → tail-workers/ @@ -136,6 +137,7 @@ Need IaC? → pulumi/ (Pulumi), terraform/ (Terraform), or api/ (REST API) | Workflows | `references/workflows/` | | Containers | `references/containers/` | | Workers for Platforms | `references/workers-for-platforms/` | +| Dynamic Workers | `references/dynamic-workers/` | | Cron Triggers | `references/cron-triggers/` | | Tail Workers | `references/tail-workers/` | | Snippets | `references/snippets/` | diff --git a/skills/cloudflare/references/dynamic-workers/README.md b/skills/cloudflare/references/dynamic-workers/README.md new file mode 100644 index 0000000..4a6aa19 --- /dev/null +++ b/skills/cloudflare/references/dynamic-workers/README.md @@ -0,0 +1,90 @@ +# Dynamic Workers + +Spin up isolated Workers at runtime to execute code on-demand in secure V8 isolates. Unlike pre-deployed Workers, Dynamic Workers are created from code strings at request time — no build step, no deploy. + +**Use cases**: AI agent code execution ("code mode"), generated applications, custom automations, user-uploaded code, rapid prototyping. + +## Dynamic Workers vs Other Runtimes + +| | Dynamic Workers | Workers for Platforms | Sandbox | +|---|---|---|---| +| **Runtime** | V8 isolate | V8 isolate | Container (Durable Object) | +| **When created** | At runtime from code strings | Pre-deployed via API | On first request to DO ID | +| **Startup** | Milliseconds | Already deployed | 2-3s cold start | +| **Languages** | JS, Python | JS, Python | Any (Dockerfile) | +| **State** | Ephemeral per invocation | Persistent (deployed script) | Persistent filesystem | +| **Best for** | One-shot code execution, AI agents | Multi-tenant SaaS platforms | Long-running processes, full OS | + +## Architecture + +``` +Request → Loader Worker → env.LOADER.load(code) → Dynamic Worker isolate + → env.LOADER.get(id, cb) → Cached Dynamic Worker +``` + +- **Loader Worker**: Your deployed Worker with a `worker_loaders` binding +- **Dynamic Worker**: Ephemeral V8 isolate created from code you provide +- **Capability-based security**: Dynamic Workers only access what you pass via `env` (RPC stubs, not raw bindings) +- **Network control**: `globalOutbound` controls all egress (block, intercept, or inherit) + +## Two Loading Modes + +**`load(code)`** — Fresh isolate every time. Best for one-shot AI-generated code. + +**`get(id, callback)`** — Cached by ID across requests. Callback runs only when isolate isn't warm. Best for apps receiving repeated traffic. + +## Quick Start + +**wrangler.jsonc**: +```jsonc +{ + "name": "my-dynamic-worker", + "main": "src/index.ts", + "compatibility_date": "2026-01-28", + "compatibility_flags": ["nodejs_compat"], + "worker_loaders": [{ "binding": "LOADER" }] +} +``` + +**src/index.ts**: +```typescript +export default { + async fetch(request: Request, env: Env): Promise { + const worker = env.LOADER.load({ + compatibilityDate: "2026-01-28", + mainModule: "worker.js", + modules: { + "worker.js": ` + export default { + fetch(request) { + return new Response("Hello from a dynamic Worker!"); + } + }; + ` + }, + globalOutbound: null // Block all network access + }); + + return worker.getEntrypoint().fetch(request); + } +} satisfies ExportedHandler; +``` + +## Core APIs + +- `env.LOADER.load(code)` → Create fresh Dynamic Worker +- `env.LOADER.get(id, callback)` → Load or reuse cached Dynamic Worker +- `worker.getEntrypoint()` → Access default export (fetch, RPC methods) +- `worker.getEntrypoint(name)` → Access named entrypoint + +## In This Reference +- [api.md](./api.md) — WorkerCode object, module types, RPC bindings, helper libraries +- [configuration.md](./configuration.md) — Wrangler config, bundling, observability setup +- [patterns.md](./patterns.md) — Code mode, credential injection, real-time logging, OpenAPI wrapping +- [gotchas.md](./gotchas.md) — Limits, pricing, common errors, best practices + +## See Also +- [workers-for-platforms](../workers-for-platforms/) — Pre-deployed multi-tenant Workers +- [sandbox](../sandbox/) — Container-based isolated execution +- [workers](../workers/) — Standard Workers fundamentals +- [tail-workers](../tail-workers/) — Log consumption (used for Dynamic Worker observability) diff --git a/skills/cloudflare/references/dynamic-workers/api.md b/skills/cloudflare/references/dynamic-workers/api.md new file mode 100644 index 0000000..3cb5e79 --- /dev/null +++ b/skills/cloudflare/references/dynamic-workers/api.md @@ -0,0 +1,255 @@ +# API Reference + +## Loading Dynamic Workers + +### `env.LOADER.load(code: WorkerCode): WorkerStub` + +Creates a fresh Dynamic Worker. No caching — each call creates a new isolate. + +```typescript +const worker = env.LOADER.load({ + compatibilityDate: "2026-01-28", + mainModule: "worker.js", + modules: { "worker.js": code }, + globalOutbound: null +}); + +const response = await worker.getEntrypoint().fetch(request); +``` + +### `env.LOADER.get(id: string, callback: () => Promise): WorkerStub` + +Loads or retrieves a cached Dynamic Worker by ID. The callback executes only if the Worker isn't already warm. Returns synchronously; requests queue if the Worker is still loading. + +```typescript +const worker = env.LOADER.get("hello-v1", async () => { + const code = await env.MY_CODE_STORAGE.get("hello-v1"); + return { + compatibilityDate: "2026-01-28", + mainModule: "index.js", + modules: { "index.js": code }, + globalOutbound: null + }; +}); +``` + +**Critical**: The callback must always return the same content for the same ID. Different code for the same ID breaks caching guarantees. + +### `WorkerStub.getEntrypoint(): object` + +Access the Dynamic Worker's `export default`. Returns an object you can call `fetch()` or RPC methods on. + +### `WorkerStub.getEntrypoint(name: string): object` + +Access a named entrypoint (a named `WorkerEntrypoint` export from the Dynamic Worker). + +## WorkerCode Object + +### Required Properties + +**`compatibilityDate`** (string) — Sets the Worker runtime version (e.g. `"2026-01-28"`). + +**`mainModule`** (string) — Name of the entry module. Must exist as a key in `modules`. + +**`modules`** (Record) — Dictionary mapping filenames to code. String values require `.js` or `.py` extensions. Object values specify the module type: + +| Type | Syntax | Use | +|------|--------|-----| +| `{js: string}` | ES module (`import`/`export`) | Default for `.js` strings | +| `{cjs: string}` | CommonJS (`require()`) | Legacy modules | +| `{py: string}` | Python | Slower startup than JS | +| `{text: string}` | Importable string value | Config, templates | +| `{data: ArrayBuffer}` | Binary data | Wasm, images | +| `{json: object}` | JSON-serializable object | Structured config | + +### Optional Properties + +**`compatibilityFlags`** (string[]) — Compatibility flags augmenting the date. + +**`allowExperimental`** (boolean) — Permit experimental flags. Parent Worker must have the `"experimental"` flag set. + +**`globalOutbound`** (ServiceStub | null) — Controls network egress: +- **Omitted**: Inherits parent Worker's network access +- **`null`**: Blocks all `fetch()` and `connect()` (throws on attempt) +- **ServiceStub**: Routes all network requests through a custom gateway + +**`env`** (object) — Environment passed to the Dynamic Worker. Supports: +- Structured-clonable types (strings, numbers, objects, arrays) +- Service Bindings (RPC stubs from `ctx.exports`) +- Loopback bindings (the loader's own entrypoints) + +**`tails`** (ServiceStub[]) — Tail Workers that receive logs and metadata after execution. + +## Custom Bindings via RPC + +Dynamic Workers cannot receive raw bindings (KV, R2, D1) directly. Wrap them in `WorkerEntrypoint` classes and pass as RPC stubs. + +### Define in Loader Worker + +```typescript +import { WorkerEntrypoint } from "cloudflare:workers"; + +export class ChatRoom extends WorkerEntrypoint { + async post(text: string): Promise { + const { apiKey, botName, roomName } = this.ctx.props; + text = `[${botName}]: ${text}`; + await postToChat(apiKey, roomName, text); + } +} + +type ChatRoomProps = { + apiKey: string; + roomName: string; + botName: string; +}; +``` + +### Pass to Dynamic Worker + +```typescript +const chatRoom = ctx.exports.ChatRoom({ + props: { apiKey, botName: "Robo", roomName: "#bot-chat" } +}); + +const worker = env.LOADER.load({ + env: { CHAT_ROOM: chatRoom }, + compatibilityDate: "2026-01-28", + mainModule: "index.js", + modules: { "index.js": codeFromAgent }, + globalOutbound: null +}); + +return worker.getEntrypoint("Agent").run(); +``` + +The Dynamic Worker accesses `this.env.CHAT_ROOM.post(text)` without seeing secrets. + +## Network Access Control (globalOutbound) + +### Block All Egress + +```typescript +globalOutbound: null +// Any fetch() or connect() in the Dynamic Worker throws +``` + +### Intercept Requests via Gateway + +```typescript +export class HttpGateway extends WorkerEntrypoint { + async fetch(request: Request): Promise { + const url = new URL(request.url); + // Inspect, modify, block, or forward + return fetch(request); + } +} + +// In loader: +globalOutbound: ctx.exports.HttpGateway() +``` + +### Inject Credentials + +```typescript +export class HttpGateway extends WorkerEntrypoint { + async fetch(request: Request): Promise { + const url = new URL(request.url); + const headers = new Headers(request.headers); + + if (url.hostname === "api.example.com") { + headers.set("Authorization", `Bearer ${this.env.API_TOKEN}`); + headers.set("X-Tenant-Id", this.ctx.props.tenantId); + } + + return fetch(request, { headers }); + } +} + +// Pass with props: +globalOutbound: ctx.exports.HttpGateway({ props: { tenantId } }) +``` + +## Helper Libraries + +### `@cloudflare/worker-bundler` + +Bundles TypeScript and npm dependencies before passing to the loader. Handles transpilation, dependency resolution, and module output. + +```typescript +import { createWorker } from "@cloudflare/worker-bundler"; + +const worker = env.LOADER.get("my-worker", async () => { + const { mainModule, modules, wranglerConfig, warnings } = await createWorker({ + files: { + "src/index.ts": ` + import { Hono } from 'hono'; + const app = new Hono(); + app.get('/', (c) => c.text('Hello from Hono!')); + export default app; + `, + "package.json": JSON.stringify({ + dependencies: { hono: "^4.0.0" } + }) + }, + bundle: true, + minify: false + }); + + return { + mainModule, + modules: modules as Record, + compatibilityDate: wranglerConfig?.compatibilityDate ?? "2026-01-01", + compatibilityFlags: wranglerConfig?.compatibilityFlags ?? [] + }; +}); +``` + +### `@cloudflare/codemode` + +Simplifies running model-generated code. Provides code normalization and outbound request control. Used internally by `DynamicWorkerExecutor` in the Agents SDK. + +### `@cloudflare/shell` + +Virtual filesystem with typed methods (read, write, search, replace, diff). Backed by durable SQLite and R2 storage. + +**Note**: `@cloudflare/codemode` and `@cloudflare/shell` have limited public documentation. Check the latest Cloudflare docs and npm packages for current API surfaces. + +## Tail Workers (Observability) + +Attach Tail Workers via the `tails` property to capture `console.log()`, exceptions, and request metadata. + +### Define Tail Worker + +```typescript +import { WorkerEntrypoint } from "cloudflare:workers"; + +export class DynamicWorkerTail extends WorkerEntrypoint { + async tail(events: TraceItem[]) { + for (const event of events) { + for (const log of event.logs) { + console.log({ + source: "dynamic-worker-tail", + workerId: this.ctx.props.workerId, + level: log.level, + message: log.message + }); + } + } + } +} +``` + +### Attach to Dynamic Worker + +```typescript +const worker = env.LOADER.get(workerId, () => ({ + mainModule: "index.js", + modules: { "index.js": code }, + compatibilityDate: "2026-01-28", + tails: [ + ctx.exports.DynamicWorkerTail({ props: { workerId } }) + ] +})); +``` + +Tail Workers run asynchronously after the response is sent — they cannot affect the request-response cycle. diff --git a/skills/cloudflare/references/dynamic-workers/configuration.md b/skills/cloudflare/references/dynamic-workers/configuration.md new file mode 100644 index 0000000..ec83089 --- /dev/null +++ b/skills/cloudflare/references/dynamic-workers/configuration.md @@ -0,0 +1,153 @@ +# Configuration + +## Worker Loader Binding + +The `worker_loaders` binding gives your Worker access to the Dynamic Worker Loader API. + +**wrangler.jsonc**: +```jsonc +{ + "name": "my-loader", + "main": "src/index.ts", + "compatibility_date": "2026-01-28", + "compatibility_flags": ["nodejs_compat"], + "worker_loaders": [{ "binding": "LOADER" }] +} +``` + +**wrangler.toml**: +```toml +[[worker_loaders]] +binding = "LOADER" +``` + +Access via `env.LOADER` in your Worker code. + +## Combining with Other Bindings + +Dynamic Workers are typically used alongside other Cloudflare bindings in the **loader** Worker. The Dynamic Worker itself does not receive these bindings directly — you wrap them in RPC entrypoints (see [api.md](./api.md)). + +### With Durable Objects (for logging) + +```jsonc +{ + "worker_loaders": [{ "binding": "LOADER" }], + "durable_objects": { + "bindings": [{ "class_name": "LogSession", "name": "LogSession" }] + }, + "migrations": [{ "tag": "v1", "new_sqlite_classes": ["LogSession"] }] +} +``` + +### With AI Binding (for code generation) + +```jsonc +{ + "worker_loaders": [{ "binding": "LOADER" }], + "ai": { "binding": "AI" } +} +``` + +### With AI + Durable Objects + Assets (full-stack playground) + +```jsonc +{ + "worker_loaders": [{ "binding": "LOADER" }], + "ai": { "binding": "AI" }, + "durable_objects": { + "bindings": [{ "class_name": "WorkerPlayground", "name": "WorkerPlayground" }] + }, + "migrations": [{ "tag": "v1", "new_sqlite_classes": ["WorkerPlayground"] }], + "assets": { + "directory": "public", + "not_found_handling": "single-page-application", + "run_worker_first": ["/api/*"] + } +} +``` + +## Observability + +Enable Workers Logs to see output from both the loader and Dynamic Workers. + +**wrangler.jsonc**: +```jsonc +{ + "observability": { + "enabled": true, + "head_sampling_rate": 1 + } +} +``` + +**wrangler.toml**: +```toml +[observability] +enabled = true +head_sampling_rate = 1 +``` + +For structured real-time logs, use Tail Workers with a Durable Object log session (see [patterns.md](./patterns.md)). + +## Supported Languages + +| Language | Module Type | Notes | +|----------|-------------|-------| +| JavaScript (ES modules) | `{js: string}` or `.js` string | Recommended. Fastest startup. | +| JavaScript (CommonJS) | `{cjs: string}` | Use for `require()`-style modules | +| Python | `{py: string}` or `.py` string | Significantly slower startup than JS | +| TypeScript | Requires bundling | Use `@cloudflare/worker-bundler` to compile before loading | + +### TypeScript and npm Dependencies + +TypeScript cannot be passed directly to the loader. Use `@cloudflare/worker-bundler` to transpile and resolve dependencies at runtime: + +```typescript +import { createWorker } from "@cloudflare/worker-bundler"; + +const { mainModule, modules } = await createWorker({ + files: { + "src/index.ts": typescriptCode, + "package.json": JSON.stringify({ + dependencies: { hono: "^4.0.0" } + }) + }, + bundle: true, + minify: false +}); + +const worker = env.LOADER.load({ + mainModule, + modules: modules as Record, + compatibilityDate: "2026-01-28" +}); +``` + +`createWorker()` returns: +- `mainModule`: Entry point filename +- `modules`: Bundled module map +- `wranglerConfig`: Parsed config from the files (if a `wrangler.jsonc` was included) +- `warnings`: Build warnings + +## Compatibility Date and Flags + +```typescript +env.LOADER.load({ + compatibilityDate: "2026-01-28", // Required + compatibilityFlags: ["nodejs_compat"], // Optional + allowExperimental: true, // Optional — parent must have "experimental" flag + // ... +}); +``` + +The `compatibilityDate` on the Dynamic Worker is independent of the loader Worker's date. Set it to match the code you're executing. + +## CLI Commands + +```bash +wrangler dev # Local development +wrangler deploy # Deploy loader Worker +wrangler tail # Stream real-time logs +``` + +Dynamic Workers are created at runtime — there are no separate deploy or management commands for them. diff --git a/skills/cloudflare/references/dynamic-workers/gotchas.md b/skills/cloudflare/references/dynamic-workers/gotchas.md new file mode 100644 index 0000000..3f3acd8 --- /dev/null +++ b/skills/cloudflare/references/dynamic-workers/gotchas.md @@ -0,0 +1,175 @@ +# Gotchas & Best Practices + +## Common Errors + +### "Worker not found" or empty response + +**Cause**: `mainModule` doesn't match a key in `modules`, or the module doesn't have a default export with `fetch()`. +**Solution**: Ensure `mainModule` is an exact key in the `modules` object and the code exports a `fetch` handler. + +```typescript +// ❌ BAD: mainModule doesn't match +env.LOADER.load({ + mainModule: "index.js", + modules: { "worker.js": code } // Key mismatch +}); + +// ✅ GOOD: mainModule matches modules key +env.LOADER.load({ + mainModule: "worker.js", + modules: { "worker.js": code } +}); +``` + +### "fetch() is not allowed" or network error in Dynamic Worker + +**Cause**: `globalOutbound` is set to `null`, blocking all network access. +**Solution**: If the Dynamic Worker needs network access, either omit `globalOutbound` (inherits parent's access) or provide a gateway `ServiceStub`. + +### Callback returning different code for the same ID + +**Cause**: Using `get(id, callback)` where the callback returns different content across invocations for the same ID. +**Solution**: The callback must always return identical content for a given ID. Use content-hashed IDs if code varies: + +```typescript +// ❌ BAD: Same ID, potentially different code +env.LOADER.get("my-worker", async () => { + const code = await fetchLatestCode(); // May change! + return { modules: { "index.js": code }, /* ... */ }; +}); + +// ✅ GOOD: ID derived from content hash +const hash = await hashCode(code); +env.LOADER.get(`worker-${hash}`, async () => { + return { modules: { "index.js": code }, /* ... */ }; +}); +``` + +### "Cannot access binding" in Dynamic Worker + +**Cause**: Passing raw Cloudflare bindings (KV, R2, D1) directly via `env`. +**Solution**: Dynamic Workers use capability-based security. Wrap bindings in `WorkerEntrypoint` classes and pass as RPC stubs (see [api.md](./api.md)). + +### Props not serializable + +**Cause**: Passing functions or non-clonable objects via `ctx.props`. +**Solution**: `ctx.props` values must be structured-clonable (strings, numbers, plain objects, arrays). No functions, classes, or circular references. + +### Python code runs slowly + +**Cause**: Python isolates have significantly slower startup than JavaScript. +**Solution**: Use JavaScript or TypeScript (via bundler) for AI-generated code. Reserve Python for cases where it's strictly necessary. + +### Tail Worker logs not appearing + +**Cause**: Tail Workers run asynchronously after the response is sent. If you return immediately, logs may not have arrived yet. +**Solution**: Use the Durable Object log session pattern (see [patterns.md](./patterns.md)) to wait for logs before responding, or accept that logs arrive asynchronously. + +### RPC methods executing in different isolates + +**Cause**: Each `fetch()` call may execute in a different isolate. Only stubs returned from the same RPC method share a session. +**Solution**: If you need shared state across calls, use `get()` with a stable ID to maintain a warm isolate, or pass state explicitly. + +## Best Practices + +### Use `load()` for one-shot, `get()` for repeated + +```typescript +// One-shot AI code execution — use load() +const worker = env.LOADER.load({ /* ... */ }); + +// App receiving multiple requests — use get() with stable ID +const worker = env.LOADER.get("app-v1", async () => { /* ... */ }); +``` + +### Content-hash your worker IDs + +Derive IDs from the code content so identical code reuses the same warm isolate: + +```typescript +async function workerId(files: Record): Promise { + const sorted = Object.entries(files).sort(); + const digest = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(JSON.stringify(sorted)) + ); + return "worker-" + Array.from(new Uint8Array(digest), (b) => + b.toString(16).padStart(2, "0") + ).join("").slice(0, 16); +} +``` + +### Block network by default + +Use `globalOutbound: null` unless the Dynamic Worker genuinely needs network access. When it does, use a gateway to filter and inject credentials rather than passing raw tokens. + +### Cold-start warmup + +Call a no-op method on the entrypoint to trigger isolate initialization before the real request: + +```typescript +const entrypoint = worker.getEntrypoint(); +try { + await entrypoint.__warmup__?.(); +} catch { + // Intentional — the method doesn't exist, but the isolate is now warm +} +``` + +## Pricing + +Available on **Workers Paid plan** only. + +| Metric | Included | Overage | +|--------|----------|---------| +| Dynamic Workers created daily | 1,000/month | $0.002/Worker/day | +| Requests | 10M/month | $0.30/million | +| CPU time | 30M ms/month | $0.02/million ms | + +**Note**: The "Dynamic Workers created daily" charge is not yet active during beta. + +Requests and CPU time are billed as part of your existing Workers plan (not additional charges). + +### What counts as a "Dynamic Worker created"? + +| Scenario | Count | +|----------|-------| +| Same code, same ID, multiple invocations | **1** Dynamic Worker | +| Same code, different IDs | **1 per ID** | +| Same ID, different code versions | **1 per version** | +| No ID (`load()` used) | **1 per invocation** | + +**Cost implication**: `load()` is more expensive at scale than `get()` with stable IDs, because every invocation counts as a new Dynamic Worker. + +### CPU time + +Includes both: +- **Startup time**: Isolate initialization and code parsing +- **Execution time**: Active processing (excludes I/O wait) + +RPC method calls bill similarly to Durable Objects requests. Returned stubs from the same RPC method share a session (no additional billing). + +## Limits + +| Resource | Limit | +|----------|-------| +| Module size | Standard Workers limits apply | +| CPU per invocation | Standard Workers limits apply | +| Subrequests | Standard Workers limits apply | +| Concurrent isolates | No documented limit | + +## Starter Templates + +- [Dynamic Workers Starter](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers) — Minimal `load()` example +- [Dynamic Workers Playground](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground) — Full IDE with bundling, caching, and real-time logs +- [Codemode](https://github.com/cloudflare/agents/tree/main/examples/codemode) — AI agent code execution with tools +- [Codemode MCP](https://github.com/cloudflare/agents/tree/main/examples/codemode-mcp) — Wrap MCP server into single code tool +- [Codemode MCP OpenAPI](https://github.com/cloudflare/agents/tree/main/examples/codemode-mcp-openapi) — OpenAPI spec → MCP code tool +- [Worker Bundler Playground](https://github.com/cloudflare/agents/tree/main/examples/worker-bundler-playground) — AI-generated full-stack apps + +## Resources + +- [Official Docs](https://developers.cloudflare.com/dynamic-workers/) +- [Getting Started](https://developers.cloudflare.com/dynamic-workers/getting-started/) +- [Blog Post](https://blog.cloudflare.com/dynamic-workers/) +- [LLM Reference](https://developers.cloudflare.com/dynamic-workers/llms-full.txt) diff --git a/skills/cloudflare/references/dynamic-workers/patterns.md b/skills/cloudflare/references/dynamic-workers/patterns.md new file mode 100644 index 0000000..3e43c70 --- /dev/null +++ b/skills/cloudflare/references/dynamic-workers/patterns.md @@ -0,0 +1,293 @@ +# Common Patterns + +## Basic Code Execution + +The simplest pattern: accept code, execute it, return the result. + +```typescript +const DEFAULT_CODE = `export default { + fetch() { + return new Response("Hello from a dynamic Worker!"); + }, +};`; + +export default { + async fetch(request: Request, env: Env): Promise { + if (request.method === "POST") { + const { code } = await request.json<{ code?: string }>(); + + try { + const worker = env.LOADER.load({ + compatibilityDate: "2026-01-28", + mainModule: "worker.js", + modules: { "worker.js": code?.trim() || DEFAULT_CODE }, + globalOutbound: null + }); + + const result = await worker.getEntrypoint().fetch(new Request("https://worker/")); + const text = await result.text(); + return Response.json({ ok: true, status: result.status, output: text }); + } catch (err) { + return Response.json( + { ok: false, error: err instanceof Error ? err.message : String(err) }, + { status: 400 } + ); + } + } + + return new Response("Not found", { status: 404 }); + } +} satisfies ExportedHandler; +``` + +## AI Agent Code Mode + +Instead of calling tools one at a time, the LLM writes code that calls multiple tools programmatically. This reduces token usage by up to 80%. + +Uses the Agents SDK `AIChatAgent` with `DynamicWorkerExecutor` to combine tools into a single `codemode` tool: + +```typescript +import { AIChatAgent } from "@cloudflare/agents/ai-chat-agent"; +import { DynamicWorkerExecutor } from "@cloudflare/agents/dynamic-worker-executor"; +import { codemode } from "@cloudflare/agents/codemode"; + +export class MyAgent extends AIChatAgent { + async onChatMessage(onFinish) { + const tools = getMyTools(this.sql); + + const result = streamText({ + model: createWorkersAI({ binding: this.env.AI }), + system: "You are a helpful assistant.", + messages: this.messages, + tools: codemode({ + tools, + executor: new DynamicWorkerExecutor(this.env.LOADER) + }), + maxSteps: 10 + }); + + return result.toTextStreamResponse(); + } +} +``` + +The agent generates a single TypeScript function that chains multiple tool calls, rather than making sequential individual tool calls. + +## Wrapping MCP Servers (codeMcpServer) + +Collapse any MCP server's tool list into a single `code` tool with `codeMcpServer`: + +```typescript +import { createMcpHandler } from "@cloudflare/agents/mcp"; +import { codeMcpServer } from "@cloudflare/agents/codemode"; +import { DynamicWorkerExecutor } from "@cloudflare/agents/dynamic-worker-executor"; + +function createUpstreamServer() { + // Standard MCP server with multiple tools + const server = new McpServer({ name: "my-tools", version: "1.0" }); + server.tool("add", { a: z.number(), b: z.number() }, ({ a, b }) => ({ + content: [{ type: "text", text: String(a + b) }] + })); + // ... more tools + return server; +} + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + const url = new URL(request.url); + + if (url.pathname === "/mcp") { + // Raw: exposes all individual tools + return createMcpHandler(createUpstreamServer)(request, env, ctx); + } + + if (url.pathname === "/codemode") { + // Wrapped: single "code" tool that can call all upstream tools + return createMcpHandler(() => + codeMcpServer(createUpstreamServer(), new DynamicWorkerExecutor(env.LOADER)) + )(request, env, ctx); + } + + return new Response("Not found", { status: 404 }); + } +}; +``` + +## OpenAPI Spec → MCP Code Tool + +Turn any REST API into a pair of MCP tools (`search` + `execute`) using `openApiMcpServer`: + +```typescript +import { openApiMcpServer } from "@cloudflare/agents/codemode"; +import { DynamicWorkerExecutor } from "@cloudflare/agents/dynamic-worker-executor"; + +let cachedSpec: string | null = null; + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + const token = request.headers.get("Authorization")?.replace("Bearer ", ""); + if (!token) return new Response("Bearer token required", { status: 401 }); + + if (!cachedSpec) { + const res = await fetch("https://raw.githubusercontent.com/.../openapi.json"); + cachedSpec = await res.text(); + } + + return createMcpHandler(() => + openApiMcpServer({ + spec: cachedSpec, + executor: new DynamicWorkerExecutor(env.LOADER), + baseUrl: "https://api.example.com/v4", + headers: { Authorization: `Bearer ${token}` } + }) + )(request, env, ctx); + } +}; +``` + +This pattern keeps credentials on the host side while the sandbox executes API calls without direct token access. + +## Bundled Playground with Warm Caching + +Use content-hashed IDs with `get()` for efficient caching, and `@cloudflare/worker-bundler` for TypeScript/npm support: + +```typescript +import { createWorker } from "@cloudflare/worker-bundler"; + +async function createWorkerId(files: Record): Promise { + const payload = JSON.stringify(Object.entries(files).sort()); + const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload)); + const hash = Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")) + .join("") + .slice(0, 16); + return `worker-${hash}`; +} + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + const { files } = await request.json<{ files: Record }>(); + const workerId = await createWorkerId(files); + + const worker = env.LOADER.get(workerId, async () => { + const { mainModule, modules, wranglerConfig } = await createWorker({ + files, bundle: true, minify: false + }); + + return { + mainModule, + modules: modules as Record, + compatibilityDate: wranglerConfig?.compatibilityDate ?? "2026-01-01", + globalOutbound: null, + tails: [ + (ctx as any).exports.DynamicWorkerTail({ props: { workerId } }) + ] + }; + }); + + return worker.getEntrypoint().fetch(request); + } +}; +``` + +Same code + same hash = same ID = warm cache hit. Different code = different hash = new isolate. + +## Real-Time Log Streaming + +Use a Durable Object to collect Tail Worker logs and return them synchronously with the response: + +```typescript +import { WorkerEntrypoint } from "cloudflare:workers"; +import { DurableObject, RpcTarget } from "cloudflare:workers"; + +// 1. Log waiter collects events +export class LogWaiter extends RpcTarget { + private logs: unknown[] = []; + private resolve?: (logs: unknown[]) => void; + + push(entries: unknown[]) { + this.logs.push(...entries); + this.resolve?.(this.logs); + } + + async getLogs(timeoutMs: number): Promise { + if (this.logs.length > 0) return this.logs; + return new Promise((resolve) => { + this.resolve = resolve; + setTimeout(() => resolve(this.logs), timeoutMs); + }); + } +} + +// 2. Durable Object holds the waiter +export class LogSession extends DurableObject { + private waiter = new LogWaiter(); + + async waitForLogs() { return this.waiter; } + async pushLogs(entries: unknown[]) { this.waiter.push(entries); } +} + +// 3. Tail Worker forwards logs to the DO +export class DynamicWorkerTail extends WorkerEntrypoint { + async tail(events: TraceItem[]) { + const entries = events.flatMap((e) => + e.logs.map((log) => ({ + workerId: this.ctx.props.workerId, + level: log.level, + message: log.message + })) + ); + + const session = this.env.LogSession.getByName(this.ctx.props.workerId); + await session.pushLogs(entries); + } +} +``` + +**Usage in loader**: + +```typescript +const logSession = ctx.exports.LogSession.getByName(workerId); +const logWaiter = await logSession.waitForLogs(); + +const response = await worker.getEntrypoint().fetch(request); + +const logs = await logWaiter.getLogs(1000); // Wait up to 1s for logs +return Response.json({ response: await response.text(), logs }); +``` + +## Capability-Based Security + +Pass only the capabilities the Dynamic Worker needs. Hide secrets, restrict access: + +```typescript +// Loader defines narrow interfaces +export class DatabaseReader extends WorkerEntrypoint { + async query(sql: string): Promise { + // Validate SQL is read-only + if (!/^\s*SELECT/i.test(sql)) throw new Error("Read-only access"); + return this.env.DB.prepare(sql).all().results; + } +} + +export class HttpGateway extends WorkerEntrypoint { + async fetch(request: Request): Promise { + const url = new URL(request.url); + // Only allow specific hosts + const allowed = ["api.example.com", "data.example.com"]; + if (!allowed.includes(url.hostname)) { + return new Response("Blocked", { status: 403 }); + } + return fetch(request); + } +} + +// Dynamic Worker receives constrained capabilities +const worker = env.LOADER.load({ + env: { + DB: ctx.exports.DatabaseReader(), + // No direct KV, R2, or D1 access + }, + globalOutbound: ctx.exports.HttpGateway(), // Filtered egress + // ... +}); +``` From acb48b893e25cd43679a04c46a9eef8e61a345d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 15:55:11 +0000 Subject: [PATCH 2/9] Fix 15 ungrounded claims in Dynamic Workers skill Verified all claims against official Cloudflare docs and fixed: - Fix import paths: @cloudflare/codemode/ai, @cloudflare/codemode/mcp (were incorrectly @cloudflare/agents/*) - Fix codeMcpServer signature: { server, executor } not positional args - Fix openApiMcpServer signature: { spec, executor, request } not { spec, executor, baseUrl, headers } - Fix helper library descriptions to match official changelog text - Fix WfP languages column (supports JS/TS/Python/Rust/Wasm, not just JS/Python) - Replace invented error messages with documented behavior - Add official source links for raw bindings limitation - Add caveat that __warmup__ is from example code, not documented API - Replace guessed limits table with actual Workers platform limits - Add source attribution for createWorker() return shape - Fix RPC isolate phrasing to match docs Sources: developers.cloudflare.com/dynamic-workers/, /dynamic-workers/usage/bindings/, /agents/api-reference/codemode/, /workers/platform/limits/, Cloudflare changelog https://claude.ai/code/session_01CkgwSkxhYZtzvKduJF3xSR --- .../references/dynamic-workers/README.md | 2 +- .../references/dynamic-workers/api.md | 15 ++- .../dynamic-workers/configuration.md | 4 +- .../references/dynamic-workers/gotchas.md | 33 ++--- .../references/dynamic-workers/patterns.md | 119 +++++++----------- 5 files changed, 80 insertions(+), 93 deletions(-) diff --git a/skills/cloudflare/references/dynamic-workers/README.md b/skills/cloudflare/references/dynamic-workers/README.md index 4a6aa19..fa99eef 100644 --- a/skills/cloudflare/references/dynamic-workers/README.md +++ b/skills/cloudflare/references/dynamic-workers/README.md @@ -11,7 +11,7 @@ Spin up isolated Workers at runtime to execute code on-demand in secure V8 isola | **Runtime** | V8 isolate | V8 isolate | Container (Durable Object) | | **When created** | At runtime from code strings | Pre-deployed via API | On first request to DO ID | | **Startup** | Milliseconds | Already deployed | 2-3s cold start | -| **Languages** | JS, Python | JS, Python | Any (Dockerfile) | +| **Languages** | JS, Python | JS, TS, Python, Rust, Wasm | Any (Dockerfile) | | **State** | Ephemeral per invocation | Persistent (deployed script) | Persistent filesystem | | **Best for** | One-shot code execution, AI agents | Multi-tenant SaaS platforms | Long-running processes, full OS | diff --git a/skills/cloudflare/references/dynamic-workers/api.md b/skills/cloudflare/references/dynamic-workers/api.md index 3cb5e79..d27762e 100644 --- a/skills/cloudflare/references/dynamic-workers/api.md +++ b/skills/cloudflare/references/dynamic-workers/api.md @@ -82,7 +82,7 @@ Access a named entrypoint (a named `WorkerEntrypoint` export from the Dynamic Wo ## Custom Bindings via RPC -Dynamic Workers cannot receive raw bindings (KV, R2, D1) directly. Wrap them in `WorkerEntrypoint` classes and pass as RPC stubs. +Passing standard Workers bindings (KV, R2, D1, etc.) directly into a Dynamic Worker is [not currently supported](https://developers.cloudflare.com/dynamic-workers/usage/bindings/). Instead, create wrapper RPC interfaces using `WorkerEntrypoint` classes and pass them as stubs. This also lets you narrow scope and filter requests. ### Define in Loader Worker @@ -206,13 +206,20 @@ const worker = env.LOADER.get("my-worker", async () => { ### `@cloudflare/codemode` -Simplifies running model-generated code. Provides code normalization and outbound request control. Used internally by `DynamicWorkerExecutor` in the Agents SDK. +Replace individual tool calls with a single `code()` tool, so LLMs write and execute TypeScript that orchestrates multiple API calls in one pass. Provides `DynamicWorkerExecutor` and `createCodeTool()`. + +Key exports: +- `@cloudflare/codemode` — `DynamicWorkerExecutor` class +- `@cloudflare/codemode/ai` — `createCodeTool()` function +- `@cloudflare/codemode/mcp` — `codeMcpServer()` and `openApiMcpServer()` wrappers + +See the [Code Mode documentation](https://developers.cloudflare.com/agents/api-reference/codemode/) for full API reference. ### `@cloudflare/shell` -Virtual filesystem with typed methods (read, write, search, replace, diff). Backed by durable SQLite and R2 storage. +Give your agent a virtual filesystem inside a Dynamic Worker with persistent storage backed by SQLite and R2. -**Note**: `@cloudflare/codemode` and `@cloudflare/shell` have limited public documentation. Check the latest Cloudflare docs and npm packages for current API surfaces. +See [npm package](https://www.npmjs.com/package/@cloudflare/shell) for current API surface. ## Tail Workers (Observability) diff --git a/skills/cloudflare/references/dynamic-workers/configuration.md b/skills/cloudflare/references/dynamic-workers/configuration.md index ec83089..0f9cf70 100644 --- a/skills/cloudflare/references/dynamic-workers/configuration.md +++ b/skills/cloudflare/references/dynamic-workers/configuration.md @@ -123,12 +123,14 @@ const worker = env.LOADER.load({ }); ``` -`createWorker()` returns: +`createWorker()` returns (based on [official examples](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground)): - `mainModule`: Entry point filename - `modules`: Bundled module map - `wranglerConfig`: Parsed config from the files (if a `wrangler.jsonc` was included) - `warnings`: Build warnings +See the [@cloudflare/worker-bundler npm package](https://www.npmjs.com/package/@cloudflare/worker-bundler) for the latest API surface. + ## Compatibility Date and Flags ```typescript diff --git a/skills/cloudflare/references/dynamic-workers/gotchas.md b/skills/cloudflare/references/dynamic-workers/gotchas.md index 3f3acd8..813829b 100644 --- a/skills/cloudflare/references/dynamic-workers/gotchas.md +++ b/skills/cloudflare/references/dynamic-workers/gotchas.md @@ -2,7 +2,7 @@ ## Common Errors -### "Worker not found" or empty response +### Dynamic Worker returns an error or empty response **Cause**: `mainModule` doesn't match a key in `modules`, or the module doesn't have a default export with `fetch()`. **Solution**: Ensure `mainModule` is an exact key in the `modules` object and the code exports a `fetch` handler. @@ -21,9 +21,9 @@ env.LOADER.load({ }); ``` -### "fetch() is not allowed" or network error in Dynamic Worker +### `fetch()` or `connect()` throws an exception in Dynamic Worker -**Cause**: `globalOutbound` is set to `null`, blocking all network access. +**Cause**: `globalOutbound` is set to `null`, which blocks all outbound network access. Any `fetch()` or `connect()` call will throw. **Solution**: If the Dynamic Worker needs network access, either omit `globalOutbound` (inherits parent's access) or provide a gateway `ServiceStub`. ### Callback returning different code for the same ID @@ -45,10 +45,10 @@ env.LOADER.get(`worker-${hash}`, async () => { }); ``` -### "Cannot access binding" in Dynamic Worker +### Standard bindings (KV, R2, D1) not working in Dynamic Worker -**Cause**: Passing raw Cloudflare bindings (KV, R2, D1) directly via `env`. -**Solution**: Dynamic Workers use capability-based security. Wrap bindings in `WorkerEntrypoint` classes and pass as RPC stubs (see [api.md](./api.md)). +**Cause**: Passing standard Workers bindings directly via `env` is [not currently supported](https://developers.cloudflare.com/dynamic-workers/usage/bindings/). Dynamic Workers use capability-based security via Workers RPC. +**Solution**: Create a wrapper RPC interface using `WorkerEntrypoint` classes and pass as stubs (see [api.md](./api.md)). This also lets you narrow scope, filter requests, etc. ### Props not serializable @@ -67,7 +67,7 @@ env.LOADER.get(`worker-${hash}`, async () => { ### RPC methods executing in different isolates -**Cause**: Each `fetch()` call may execute in a different isolate. Only stubs returned from the same RPC method share a session. +**Cause**: There is no guarantee the same ID returns the same isolate across requests. Only stubs returned from the same RPC method call share a session. **Solution**: If you need shared state across calls, use `get()` with a stable ID to maintain a warm isolate, or pass state explicitly. ## Best Practices @@ -105,7 +105,7 @@ Use `globalOutbound: null` unless the Dynamic Worker genuinely needs network acc ### Cold-start warmup -Call a no-op method on the entrypoint to trigger isolate initialization before the real request: +You can trigger isolate initialization before the real request by calling a method that forces the isolate to load. This pattern is used in [Cloudflare's playground example](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground) but is not a documented API: ```typescript const entrypoint = worker.getEntrypoint(); @@ -151,12 +151,17 @@ RPC method calls bill similarly to Durable Objects requests. Returned stubs from ## Limits -| Resource | Limit | -|----------|-------| -| Module size | Standard Workers limits apply | -| CPU per invocation | Standard Workers limits apply | -| Subrequests | Standard Workers limits apply | -| Concurrent isolates | No documented limit | +Dynamic Workers inherit [Workers platform limits](https://developers.cloudflare.com/workers/platform/limits/): + +| Resource | Workers Paid | +|----------|-------------| +| CPU time per invocation | 5 min max (configurable, default 30s) | +| Memory | 128 MB | +| Worker size | 10 MB | +| Subrequests per invocation | 10,000 (configurable up to 10M) | +| Simultaneous outgoing connections | 6 | + +Dynamic Worker-specific limits are not separately documented. Check the [official docs](https://developers.cloudflare.com/dynamic-workers/) for any updates. ## Starter Templates diff --git a/skills/cloudflare/references/dynamic-workers/patterns.md b/skills/cloudflare/references/dynamic-workers/patterns.md index 3e43c70..b2684ff 100644 --- a/skills/cloudflare/references/dynamic-workers/patterns.md +++ b/skills/cloudflare/references/dynamic-workers/patterns.md @@ -44,29 +44,34 @@ export default { Instead of calling tools one at a time, the LLM writes code that calls multiple tools programmatically. This reduces token usage by up to 80%. -Uses the Agents SDK `AIChatAgent` with `DynamicWorkerExecutor` to combine tools into a single `codemode` tool: +Uses `@cloudflare/codemode` with `DynamicWorkerExecutor` to combine tools into a single `codemode` tool: ```typescript -import { AIChatAgent } from "@cloudflare/agents/ai-chat-agent"; -import { DynamicWorkerExecutor } from "@cloudflare/agents/dynamic-worker-executor"; -import { codemode } from "@cloudflare/agents/codemode"; +import { Agent } from "agents"; +import { createCodeTool } from "@cloudflare/codemode/ai"; +import { DynamicWorkerExecutor } from "@cloudflare/codemode"; +import { streamText, convertToModelMessages, stepCountIs } from "ai"; + +export class MyAgent extends Agent { + async onChatMessage() { + const executor = new DynamicWorkerExecutor({ + loader: this.env.LOADER + }); -export class MyAgent extends AIChatAgent { - async onChatMessage(onFinish) { - const tools = getMyTools(this.sql); + const codemode = createCodeTool({ + tools: getMyTools(this.sql), + executor + }); const result = streamText({ - model: createWorkersAI({ binding: this.env.AI }), + model, system: "You are a helpful assistant.", - messages: this.messages, - tools: codemode({ - tools, - executor: new DynamicWorkerExecutor(this.env.LOADER) - }), - maxSteps: 10 + messages: await convertToModelMessages(this.state.messages), + tools: { codemode }, + stopWhen: stepCountIs(10) }); - return result.toTextStreamResponse(); + // Stream response back to client... } } ``` @@ -78,74 +83,42 @@ The agent generates a single TypeScript function that chains multiple tool calls Collapse any MCP server's tool list into a single `code` tool with `codeMcpServer`: ```typescript -import { createMcpHandler } from "@cloudflare/agents/mcp"; -import { codeMcpServer } from "@cloudflare/agents/codemode"; -import { DynamicWorkerExecutor } from "@cloudflare/agents/dynamic-worker-executor"; - -function createUpstreamServer() { - // Standard MCP server with multiple tools - const server = new McpServer({ name: "my-tools", version: "1.0" }); - server.tool("add", { a: z.number(), b: z.number() }, ({ a, b }) => ({ - content: [{ type: "text", text: String(a + b) }] - })); - // ... more tools - return server; -} +import { codeMcpServer } from "@cloudflare/codemode/mcp"; +import { DynamicWorkerExecutor } from "@cloudflare/codemode"; -export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext) { - const url = new URL(request.url); +const executor = new DynamicWorkerExecutor({ loader: env.LOADER }); - if (url.pathname === "/mcp") { - // Raw: exposes all individual tools - return createMcpHandler(createUpstreamServer)(request, env, ctx); - } - - if (url.pathname === "/codemode") { - // Wrapped: single "code" tool that can call all upstream tools - return createMcpHandler(() => - codeMcpServer(createUpstreamServer(), new DynamicWorkerExecutor(env.LOADER)) - )(request, env, ctx); - } - - return new Response("Not found", { status: 404 }); - } -}; +// Wrap an existing MCP server — all its tools become +// typed methods the LLM can call from generated code +const server = await codeMcpServer({ server: upstreamMcp, executor }); ``` ## OpenAPI Spec → MCP Code Tool -Turn any REST API into a pair of MCP tools (`search` + `execute`) using `openApiMcpServer`: +Turn any REST API into a pair of MCP tools (`search` + `execute`) using `openApiMcpServer`. The host-side `request` handler keeps authentication out of the sandbox: ```typescript -import { openApiMcpServer } from "@cloudflare/agents/codemode"; -import { DynamicWorkerExecutor } from "@cloudflare/agents/dynamic-worker-executor"; - -let cachedSpec: string | null = null; - -export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext) { - const token = request.headers.get("Authorization")?.replace("Bearer ", ""); - if (!token) return new Response("Bearer token required", { status: 401 }); - - if (!cachedSpec) { - const res = await fetch("https://raw.githubusercontent.com/.../openapi.json"); - cachedSpec = await res.text(); - } - - return createMcpHandler(() => - openApiMcpServer({ - spec: cachedSpec, - executor: new DynamicWorkerExecutor(env.LOADER), - baseUrl: "https://api.example.com/v4", - headers: { Authorization: `Bearer ${token}` } - }) - )(request, env, ctx); - } -}; +import { openApiMcpServer } from "@cloudflare/codemode/mcp"; +import { DynamicWorkerExecutor } from "@cloudflare/codemode"; + +const executor = new DynamicWorkerExecutor({ loader: env.LOADER }); + +const server = openApiMcpServer({ + spec: openApiSpec, + executor, + request: async ({ method, path, query, body }) => { + // Runs on the host — add auth headers here + const res = await fetch(`https://api.example.com${path}`, { + method, + headers: { Authorization: `Bearer ${token}` }, + body: body ? JSON.stringify(body) : undefined, + }); + return res.json(); + }, +}); ``` -This pattern keeps credentials on the host side while the sandbox executes API calls without direct token access. +This approach uses approximately 1,000 tokens regardless of how many API endpoints exist, compared to over 1 million tokens for native MCP tool definitions. ## Bundled Playground with Warm Caching From ffea662dc00b9c17a7320ebdadbd1c481c6a001e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 19:29:22 +0000 Subject: [PATCH 3/9] Fix Sandbox state claim and remove unverifiable compatibilityDate claim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Sandbox disk is ephemeral across sleep, not persistent. Fixed to: "Ephemeral disk (lost on sleep); use R2 mounts for persistence" Source: developers.cloudflare.com/containers/faq/ - Removed "compatibilityDate independent of loader's" claim from configuration.md — not explicitly documented https://claude.ai/code/session_01CkgwSkxhYZtzvKduJF3xSR --- skills/cloudflare/references/dynamic-workers/README.md | 2 +- skills/cloudflare/references/dynamic-workers/configuration.md | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/skills/cloudflare/references/dynamic-workers/README.md b/skills/cloudflare/references/dynamic-workers/README.md index fa99eef..77f0d4c 100644 --- a/skills/cloudflare/references/dynamic-workers/README.md +++ b/skills/cloudflare/references/dynamic-workers/README.md @@ -12,7 +12,7 @@ Spin up isolated Workers at runtime to execute code on-demand in secure V8 isola | **When created** | At runtime from code strings | Pre-deployed via API | On first request to DO ID | | **Startup** | Milliseconds | Already deployed | 2-3s cold start | | **Languages** | JS, Python | JS, TS, Python, Rust, Wasm | Any (Dockerfile) | -| **State** | Ephemeral per invocation | Persistent (deployed script) | Persistent filesystem | +| **State** | Ephemeral per invocation | Persistent (deployed script) | Ephemeral disk (lost on sleep); use R2 mounts for persistence | | **Best for** | One-shot code execution, AI agents | Multi-tenant SaaS platforms | Long-running processes, full OS | ## Architecture diff --git a/skills/cloudflare/references/dynamic-workers/configuration.md b/skills/cloudflare/references/dynamic-workers/configuration.md index 0f9cf70..c76ed0d 100644 --- a/skills/cloudflare/references/dynamic-workers/configuration.md +++ b/skills/cloudflare/references/dynamic-workers/configuration.md @@ -142,8 +142,6 @@ env.LOADER.load({ }); ``` -The `compatibilityDate` on the Dynamic Worker is independent of the loader Worker's date. Set it to match the code you're executing. - ## CLI Commands ```bash From 4ac8c5a809bfab827d3ad6edd6e931bb6f67c583 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 19:39:32 +0000 Subject: [PATCH 4/9] Add load() cost math, fix WfP languages, add cross-references - gotchas.md: Add explicit math showing load() at 10k invocations/day = $20/day vs get() = $0.002/day - README.md: Fix WfP languages to JS, TS, Python (drop Rust/Wasm which are not confirmed for WfP specifically) - workers-for-platforms/README.md: Add dynamic-workers See Also link - sandbox/README.md: Add dynamic-workers See Also link - tail-workers/patterns.md: Add Dynamic Workers section noting tails property for tail events https://claude.ai/code/session_01CkgwSkxhYZtzvKduJF3xSR --- skills/cloudflare/references/dynamic-workers/README.md | 2 +- skills/cloudflare/references/dynamic-workers/gotchas.md | 2 +- skills/cloudflare/references/sandbox/README.md | 1 + skills/cloudflare/references/tail-workers/patterns.md | 4 ++++ skills/cloudflare/references/workers-for-platforms/README.md | 3 ++- 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/skills/cloudflare/references/dynamic-workers/README.md b/skills/cloudflare/references/dynamic-workers/README.md index 77f0d4c..c647ac5 100644 --- a/skills/cloudflare/references/dynamic-workers/README.md +++ b/skills/cloudflare/references/dynamic-workers/README.md @@ -11,7 +11,7 @@ Spin up isolated Workers at runtime to execute code on-demand in secure V8 isola | **Runtime** | V8 isolate | V8 isolate | Container (Durable Object) | | **When created** | At runtime from code strings | Pre-deployed via API | On first request to DO ID | | **Startup** | Milliseconds | Already deployed | 2-3s cold start | -| **Languages** | JS, Python | JS, TS, Python, Rust, Wasm | Any (Dockerfile) | +| **Languages** | JS, Python | JS, TS, Python | Any (Dockerfile) | | **State** | Ephemeral per invocation | Persistent (deployed script) | Ephemeral disk (lost on sleep); use R2 mounts for persistence | | **Best for** | One-shot code execution, AI agents | Multi-tenant SaaS platforms | Long-running processes, full OS | diff --git a/skills/cloudflare/references/dynamic-workers/gotchas.md b/skills/cloudflare/references/dynamic-workers/gotchas.md index 813829b..2606f65 100644 --- a/skills/cloudflare/references/dynamic-workers/gotchas.md +++ b/skills/cloudflare/references/dynamic-workers/gotchas.md @@ -139,7 +139,7 @@ Requests and CPU time are billed as part of your existing Workers plan (not addi | Same ID, different code versions | **1 per version** | | No ID (`load()` used) | **1 per invocation** | -**Cost implication**: `load()` is more expensive at scale than `get()` with stable IDs, because every invocation counts as a new Dynamic Worker. +**Cost implication**: `load()` is more expensive at scale than `get()` with stable IDs, because every invocation counts as a new Dynamic Worker. Example: 10,000 invocations/day of the same code costs `load()` = 10,000 billed workers ($0.002 x 10,000 = $20/day) vs `get("stable-id")` = 1 billed worker ($0.002/day). ### CPU time diff --git a/skills/cloudflare/references/sandbox/README.md b/skills/cloudflare/references/sandbox/README.md index 638f4e9..7dd6ccf 100644 --- a/skills/cloudflare/references/sandbox/README.md +++ b/skills/cloudflare/references/sandbox/README.md @@ -94,3 +94,4 @@ EXPOSE 8080 3000 # Required for wrangler dev - [durable-objects](../durable-objects/) - Sandbox runs on DO infrastructure - [containers](../containers/) - Container runtime fundamentals - [workers](../workers/) - Entry point for sandbox requests +- [dynamic-workers](../dynamic-workers/) - Lighter-weight alternative (V8 isolates, no container/filesystem) diff --git a/skills/cloudflare/references/tail-workers/patterns.md b/skills/cloudflare/references/tail-workers/patterns.md index a696ec2..593d161 100644 --- a/skills/cloudflare/references/tail-workers/patterns.md +++ b/skills/cloudflare/references/tail-workers/patterns.md @@ -175,6 +175,10 @@ See durable-objects skill for full implementation. Dynamic dispatch sends TWO events per request. Filter by `scriptName` to distinguish dispatch vs user Worker events. +### Dynamic Workers + +Dynamic Workers emit tail events via the `tails` property in WorkerCode. See [dynamic-workers patterns](../dynamic-workers/patterns.md) for the real-time log streaming pattern. + ### Error Handling Always wrap external calls. See gotchas.md for fallback storage pattern. diff --git a/skills/cloudflare/references/workers-for-platforms/README.md b/skills/cloudflare/references/workers-for-platforms/README.md index 7bfd9b7..ff673ec 100644 --- a/skills/cloudflare/references/workers-for-platforms/README.md +++ b/skills/cloudflare/references/workers-for-platforms/README.md @@ -84,6 +84,7 @@ Worker mode? ## See Also - [workers](../workers/) - Core Workers runtime documentation - [durable-objects](../durable-objects/) - Stateful multi-tenant patterns -- [sandbox](../sandbox/) - Alternative for untrusted code execution +- [sandbox](../sandbox/) - Alternative for untrusted code execution (containers) +- [dynamic-workers](../dynamic-workers/) - Alternative for AI/one-shot code execution (V8 isolates, no deploy step) - [Reference Architecture: Programmable Platforms](https://developers.cloudflare.com/reference-architecture/diagrams/serverless/programmable-platforms/) - [Reference Architecture: AI Vibe Coding Platform](https://developers.cloudflare.com/reference-architecture/diagrams/ai/ai-vibe-coding-platform/) From 729a1854fcdc5535c661b5816220fdfea2853fe4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 19:44:53 +0000 Subject: [PATCH 5/9] Add Cap'n Web explanation and AI decision tree entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api.md: Name Cap'n Web in the Custom Bindings section, explain capability-based security model (stubs, no global identifiers) Source: developers.cloudflare.com/dynamic-workers/usage/bindings/ - SKILL.md: Add "Run AI-generated code in a sandbox → dynamic-workers/" to the "I need AI/ML" decision tree https://claude.ai/code/session_01CkgwSkxhYZtzvKduJF3xSR --- skills/cloudflare/SKILL.md | 1 + skills/cloudflare/references/dynamic-workers/api.md | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/skills/cloudflare/SKILL.md b/skills/cloudflare/SKILL.md index 313f608..335271b 100644 --- a/skills/cloudflare/SKILL.md +++ b/skills/cloudflare/SKILL.md @@ -69,6 +69,7 @@ Need AI? ├─ Run inference (LLMs, embeddings, images) → workers-ai/ ├─ Vector database for RAG/search → vectorize/ ├─ Build stateful AI agents → agents-sdk/ +├─ Run AI-generated code in a sandbox → dynamic-workers/ ├─ Gateway for any AI provider (caching, routing) → ai-gateway/ └─ AI-powered search widget → ai-search/ ``` diff --git a/skills/cloudflare/references/dynamic-workers/api.md b/skills/cloudflare/references/dynamic-workers/api.md index d27762e..a5b34cb 100644 --- a/skills/cloudflare/references/dynamic-workers/api.md +++ b/skills/cloudflare/references/dynamic-workers/api.md @@ -80,9 +80,11 @@ Access a named entrypoint (a named `WorkerEntrypoint` export from the Dynamic Wo **`tails`** (ServiceStub[]) — Tail Workers that receive logs and metadata after execution. -## Custom Bindings via RPC +## Custom Bindings via RPC (Cap'n Web) -Passing standard Workers bindings (KV, R2, D1, etc.) directly into a Dynamic Worker is [not currently supported](https://developers.cloudflare.com/dynamic-workers/usage/bindings/). Instead, create wrapper RPC interfaces using `WorkerEntrypoint` classes and pass them as stubs. This also lets you narrow scope and filter requests. +Passing standard Workers bindings (KV, R2, D1, etc.) directly into a Dynamic Worker is [not currently supported](https://developers.cloudflare.com/dynamic-workers/usage/bindings/). Instead, create wrapper RPC interfaces using `WorkerEntrypoint` classes and pass them as stubs. + +This uses [Cap'n Web](https://developers.cloudflare.com/dynamic-workers/usage/bindings/) (Workers RPC) — a capability-based security model. Objects passed over RPC are stubs: if you haven't received a stub, you can't call the object. There's no global identifier to guess. This is why Dynamic Workers are secure by default — the sandbox can only access what you explicitly grant it, and you can narrow scope, filter requests, or inject credentials at the boundary. ### Define in Loader Worker From ea31618b922bda2c5af28e9ef7e3abe781a9aaa2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 19:50:31 +0000 Subject: [PATCH 6/9] Add observability cross-links and retrieval-bias directive to Dynamic Workers - configuration.md: Replace generic patterns.md link with specific cross-links to api.md#tail-workers-observability and patterns.md#real-time-log-streaming - README.md: Add retrieval-bias blockquote matching PR #30 directive pattern https://claude.ai/code/session_01CkgwSkxhYZtzvKduJF3xSR --- skills/cloudflare/references/dynamic-workers/README.md | 2 ++ skills/cloudflare/references/dynamic-workers/configuration.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/skills/cloudflare/references/dynamic-workers/README.md b/skills/cloudflare/references/dynamic-workers/README.md index c647ac5..a427596 100644 --- a/skills/cloudflare/references/dynamic-workers/README.md +++ b/skills/cloudflare/references/dynamic-workers/README.md @@ -2,6 +2,8 @@ Spin up isolated Workers at runtime to execute code on-demand in secure V8 isolates. Unlike pre-deployed Workers, Dynamic Workers are created from code strings at request time — no build step, no deploy. +> **Retrieval bias**: Your knowledge of Dynamic Workers APIs, limits, and pricing may be outdated. **Prefer retrieval over pre-training** — fetch from [Cloudflare docs](https://developers.cloudflare.com/dynamic-workers/) before citing specific numbers, API signatures, or configuration options. When these reference files and the docs disagree, **trust the docs**. + **Use cases**: AI agent code execution ("code mode"), generated applications, custom automations, user-uploaded code, rapid prototyping. ## Dynamic Workers vs Other Runtimes diff --git a/skills/cloudflare/references/dynamic-workers/configuration.md b/skills/cloudflare/references/dynamic-workers/configuration.md index c76ed0d..f26763a 100644 --- a/skills/cloudflare/references/dynamic-workers/configuration.md +++ b/skills/cloudflare/references/dynamic-workers/configuration.md @@ -87,7 +87,7 @@ enabled = true head_sampling_rate = 1 ``` -For structured real-time logs, use Tail Workers with a Durable Object log session (see [patterns.md](./patterns.md)). +For Tail Worker setup and the `tails` property, see [api.md — Tail Workers](./api.md#tail-workers-observability). For the real-time log streaming pattern using Durable Objects, see [patterns.md — Real-Time Log Streaming](./patterns.md#real-time-log-streaming). ## Supported Languages From 6cd743d4ab5d580cc2954714689d1ff1fd32709c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 20:19:25 +0000 Subject: [PATCH 7/9] Fix 10 audit issues in Dynamic Workers skill reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. README.md: Fix comparison table State column — distinguish load() (ephemeral) from get() (warm across requests, no isolate guarantee) 2. README.md: Add agents-sdk to See Also cross-references 3. PREREQUISITES.md: Add retrieval-bias directive to verify checklist 4. gotchas.md: Add links to all /dynamic-workers/ usage pages 5. gotchas.md: Fix misleading isolate reuse claim — get() improves likelihood but does not guarantee same isolate 6. configuration.md: Add local dev note for wrangler dev behavior 7. api.md: Add Executor interface mention for custom sandbox impls 8. configuration.md: Add text/data/json non-code module types table 9. SKILL.md: Add "I need to sandbox untrusted code" decision tree 10. SKILL.md references: frontmatter — no change, by design (only core products auto-load) https://claude.ai/code/session_01CkgwSkxhYZtzvKduJF3xSR --- skills/cloudflare/PREREQUISITES.md | 1 + skills/cloudflare/SKILL.md | 10 ++++++++++ .../references/dynamic-workers/README.md | 3 ++- .../cloudflare/references/dynamic-workers/api.md | 2 ++ .../references/dynamic-workers/configuration.md | 14 +++++++++++++- .../references/dynamic-workers/gotchas.md | 9 +++++++-- 6 files changed, 35 insertions(+), 4 deletions(-) diff --git a/skills/cloudflare/PREREQUISITES.md b/skills/cloudflare/PREREQUISITES.md index 69d9a0e..1a49229 100644 --- a/skills/cloudflare/PREREQUISITES.md +++ b/skills/cloudflare/PREREQUISITES.md @@ -119,6 +119,7 @@ Check if existing skills should link to the new one: - [ ] No broken internal links between the 5 files - [ ] SKILL.md decision tree entry distinguishes this from similar products - [ ] SKILL.md product index row added in the correct section +- [ ] README.md includes a retrieval-bias directive (see PR #30 and the existing dynamic-workers/README.md for the pattern) - [ ] File structure matches `README.md`, `api.md`, `configuration.md`, `patterns.md`, `gotchas.md` exactly ## Example: What Was Needed for Dynamic Workers diff --git a/skills/cloudflare/SKILL.md b/skills/cloudflare/SKILL.md index 335271b..3fdfa08 100644 --- a/skills/cloudflare/SKILL.md +++ b/skills/cloudflare/SKILL.md @@ -74,6 +74,16 @@ Need AI? └─ AI-powered search widget → ai-search/ ``` +### "I need to sandbox untrusted code" + +``` +Need to sandbox code? +├─ One-shot/AI-generated code, fast startup (V8 isolate) → dynamic-workers/ +├─ Multi-tenant platform, customers deploy scripts → workers-for-platforms/ +├─ Full OS, filesystem, long-running processes (container) → sandbox/ +└─ Need both isolation + persistent state → sandbox/ (with R2 mounts) +``` + ### "I need networking/connectivity" ``` diff --git a/skills/cloudflare/references/dynamic-workers/README.md b/skills/cloudflare/references/dynamic-workers/README.md index a427596..541d0dc 100644 --- a/skills/cloudflare/references/dynamic-workers/README.md +++ b/skills/cloudflare/references/dynamic-workers/README.md @@ -14,7 +14,7 @@ Spin up isolated Workers at runtime to execute code on-demand in secure V8 isola | **When created** | At runtime from code strings | Pre-deployed via API | On first request to DO ID | | **Startup** | Milliseconds | Already deployed | 2-3s cold start | | **Languages** | JS, Python | JS, TS, Python | Any (Dockerfile) | -| **State** | Ephemeral per invocation | Persistent (deployed script) | Ephemeral disk (lost on sleep); use R2 mounts for persistence | +| **State** | `load()`: ephemeral; `get()`: warm across requests (no isolate reuse guarantee) | Persistent (deployed script) | Ephemeral disk (lost on sleep); use R2 mounts for persistence | | **Best for** | One-shot code execution, AI agents | Multi-tenant SaaS platforms | Long-running processes, full OS | ## Architecture @@ -86,6 +86,7 @@ export default { - [gotchas.md](./gotchas.md) — Limits, pricing, common errors, best practices ## See Also +- [agents-sdk](../agents-sdk/) — Agents SDK (codemode, `createCodeTool()`, AI chat agents) - [workers-for-platforms](../workers-for-platforms/) — Pre-deployed multi-tenant Workers - [sandbox](../sandbox/) — Container-based isolated execution - [workers](../workers/) — Standard Workers fundamentals diff --git a/skills/cloudflare/references/dynamic-workers/api.md b/skills/cloudflare/references/dynamic-workers/api.md index a5b34cb..63a999a 100644 --- a/skills/cloudflare/references/dynamic-workers/api.md +++ b/skills/cloudflare/references/dynamic-workers/api.md @@ -215,6 +215,8 @@ Key exports: - `@cloudflare/codemode/ai` — `createCodeTool()` function - `@cloudflare/codemode/mcp` — `codeMcpServer()` and `openApiMcpServer()` wrappers +The `DynamicWorkerExecutor` implements the generic [`Executor` interface](https://developers.cloudflare.com/agents/api-reference/codemode/) — you can implement your own `Executor` to run code in a different sandbox (e.g., containers, external services) while reusing `createCodeTool()` and the MCP wrappers. + See the [Code Mode documentation](https://developers.cloudflare.com/agents/api-reference/codemode/) for full API reference. ### `@cloudflare/shell` diff --git a/skills/cloudflare/references/dynamic-workers/configuration.md b/skills/cloudflare/references/dynamic-workers/configuration.md index f26763a..66fe332 100644 --- a/skills/cloudflare/references/dynamic-workers/configuration.md +++ b/skills/cloudflare/references/dynamic-workers/configuration.md @@ -98,6 +98,16 @@ For Tail Worker setup and the `tails` property, see [api.md — Tail Workers](./ | Python | `{py: string}` or `.py` string | Significantly slower startup than JS | | TypeScript | Requires bundling | Use `@cloudflare/worker-bundler` to compile before loading | +### Non-Code Module Types + +You can also include non-code modules in the `modules` object (see [api.md — WorkerCode](./api.md#workercode-object)): + +| Type | Syntax | Use | +|------|--------|-----| +| `{text: string}` | Importable string value | Config files, templates | +| `{data: ArrayBuffer}` | Importable binary data | Wasm modules, images | +| `{json: object}` | Importable JSON-serializable object | Structured config, manifests | + ### TypeScript and npm Dependencies TypeScript cannot be passed directly to the loader. Use `@cloudflare/worker-bundler` to transpile and resolve dependencies at runtime: @@ -145,9 +155,11 @@ env.LOADER.load({ ## CLI Commands ```bash -wrangler dev # Local development +wrangler dev # Local development (uses local runtime) wrangler deploy # Deploy loader Worker wrangler tail # Stream real-time logs ``` Dynamic Workers are created at runtime — there are no separate deploy or management commands for them. + +**Local development**: `wrangler dev` runs the loader Worker locally. Dynamic Workers execute in the local runtime during development. Behavior may differ from production — test with `wrangler deploy` to a staging Worker before relying on production-only features. Check the [official docs](https://developers.cloudflare.com/dynamic-workers/) for current local dev support status. diff --git a/skills/cloudflare/references/dynamic-workers/gotchas.md b/skills/cloudflare/references/dynamic-workers/gotchas.md index 2606f65..08ffc7c 100644 --- a/skills/cloudflare/references/dynamic-workers/gotchas.md +++ b/skills/cloudflare/references/dynamic-workers/gotchas.md @@ -67,8 +67,8 @@ env.LOADER.get(`worker-${hash}`, async () => { ### RPC methods executing in different isolates -**Cause**: There is no guarantee the same ID returns the same isolate across requests. Only stubs returned from the same RPC method call share a session. -**Solution**: If you need shared state across calls, use `get()` with a stable ID to maintain a warm isolate, or pass state explicitly. +**Cause**: There is [no guarantee](https://developers.cloudflare.com/dynamic-workers/api-reference/) that two requests go to the same isolate, even with the same `WorkerStub` or the same ID via `get()`. Only stubs returned from the same RPC method call share a session. +**Solution**: Do not rely on in-memory state persisting across requests. Pass state explicitly (via RPC arguments, Durable Objects, or KV). `get()` with a stable ID improves the *likelihood* of hitting a warm isolate but does not guarantee it. ## Best Practices @@ -176,5 +176,10 @@ Dynamic Worker-specific limits are not separately documented. Check the [officia - [Official Docs](https://developers.cloudflare.com/dynamic-workers/) - [Getting Started](https://developers.cloudflare.com/dynamic-workers/getting-started/) +- [Bindings (Cap'n Web)](https://developers.cloudflare.com/dynamic-workers/usage/bindings/) +- [Egress Control](https://developers.cloudflare.com/dynamic-workers/usage/egress-control/) +- [Observability](https://developers.cloudflare.com/dynamic-workers/usage/observability/) +- [Pricing](https://developers.cloudflare.com/dynamic-workers/pricing/) +- [API Reference](https://developers.cloudflare.com/dynamic-workers/api-reference/) - [Blog Post](https://blog.cloudflare.com/dynamic-workers/) - [LLM Reference](https://developers.cloudflare.com/dynamic-workers/llms-full.txt) From df34d3599d8395661af5102758ca4b7268eb32fa Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 22 Apr 2026 16:19:57 -0400 Subject: [PATCH 8/9] Tighten Dynamic Workers guidance --- skills/cloudflare/PREREQUISITES.md | 13 ++-- .../references/dynamic-workers/README.md | 25 +++++-- .../references/dynamic-workers/api.md | 38 ++++++++--- .../dynamic-workers/configuration.md | 28 ++++++-- .../references/dynamic-workers/gotchas.md | 67 ++++++------------- .../references/dynamic-workers/patterns.md | 12 ++-- 6 files changed, 108 insertions(+), 75 deletions(-) diff --git a/skills/cloudflare/PREREQUISITES.md b/skills/cloudflare/PREREQUISITES.md index 1a49229..6f4cf00 100644 --- a/skills/cloudflare/PREREQUISITES.md +++ b/skills/cloudflare/PREREQUISITES.md @@ -35,7 +35,7 @@ The LLM reference (`llms-full.txt`) is the highest-signal single source. Start t If the product has an SDK or library (e.g. `@cloudflare/worker-bundler`, `@cloudflare/sandbox`): - Check the npm package for current API surface - Read the repo README and any inline type definitions -- Note version numbers — skills should reference current versions +- Capture the current API shape and doc entry points; avoid pinning version-specific details unless they are required to use the product ## 2. Understand the Product's Position @@ -58,7 +58,7 @@ references// api.md — API reference: methods, types, parameters, return values configuration.md — wrangler.jsonc setup, binding combinations, CLI commands patterns.md — Common usage patterns with full code examples - gotchas.md — Errors, limits, pricing, best practices, starter links + gotchas.md — Errors, best practices, retrieval cues, starter links ``` ### What goes in each file @@ -92,8 +92,7 @@ Aim for 4-7 patterns ranging from basic to advanced. **gotchas.md**: Everything that trips people up. Include: - Common errors with cause/solution pairs - Best practices (do/don't format with code examples) -- Pricing table and billing nuances -- Limits table +- Retrieval cues for pricing, limits, and plan availability, with links to the official docs instead of copied tables - Links to starter templates and official resources ## 4. Update the Skill Index @@ -115,7 +114,7 @@ Check if existing skills should link to the new one: - [ ] All code examples use current API syntax (check against `llms-full.txt` and docs) - [ ] `wrangler.jsonc` examples match real working configurations from official examples -- [ ] Pricing and limits are cited from official docs (not guessed) +- [ ] Pricing, limits, and plan availability link to official docs rather than copying values that may drift - [ ] No broken internal links between the 5 files - [ ] SKILL.md decision tree entry distinguishes this from similar products - [ ] SKILL.md product index row added in the correct section @@ -126,7 +125,7 @@ Check if existing skills should link to the new one: | Source | URL | Key information extracted | |--------|-----|--------------------------| -| LLM reference | `developers.cloudflare.com/dynamic-workers/llms-full.txt` | Full API surface, WorkerCode properties, pricing, billing rules | +| LLM reference | `developers.cloudflare.com/dynamic-workers/llms-full.txt` | Full API surface, WorkerCode properties, retrieval entry points | | Docs home | `developers.cloudflare.com/dynamic-workers/` | Architecture overview, use cases, security model | | Getting started | `developers.cloudflare.com/dynamic-workers/getting-started/` | `worker_loaders` binding, `load()` vs `get()`, supported languages | | Blog post | `blog.cloudflare.com/dynamic-workers/` | V8 isolate performance claims, helper libraries, design rationale | @@ -137,4 +136,4 @@ Check if existing skills should link to the new one: | `codemode-mcp-openapi` example | `github.com/cloudflare/agents/.../codemode-mcp-openapi/` | `openApiMcpServer()` for REST API wrapping | | `worker-bundler-playground` example | `github.com/cloudflare/agents/.../worker-bundler-playground/` | AI-generated app pattern, `createWorker()` with assets, DO persistence | -Six examples were needed to cover the full range of patterns. The `llms-full.txt` provided the API reference backbone. The blog provided context the docs didn't (helper libraries, performance numbers). The examples provided real wrangler configs and production patterns. +Six examples were needed to cover the full range of patterns. The `llms-full.txt` provided the API reference backbone. The blog provided design rationale and helper-library context. The examples provided real wrangler configs and production patterns. diff --git a/skills/cloudflare/references/dynamic-workers/README.md b/skills/cloudflare/references/dynamic-workers/README.md index 541d0dc..c59bf1b 100644 --- a/skills/cloudflare/references/dynamic-workers/README.md +++ b/skills/cloudflare/references/dynamic-workers/README.md @@ -12,11 +12,25 @@ Spin up isolated Workers at runtime to execute code on-demand in secure V8 isola |---|---|---|---| | **Runtime** | V8 isolate | V8 isolate | Container (Durable Object) | | **When created** | At runtime from code strings | Pre-deployed via API | On first request to DO ID | -| **Startup** | Milliseconds | Already deployed | 2-3s cold start | | **Languages** | JS, Python | JS, TS, Python | Any (Dockerfile) | | **State** | `load()`: ephemeral; `get()`: warm across requests (no isolate reuse guarantee) | Persistent (deployed script) | Ephemeral disk (lost on sleep); use R2 mounts for persistence | | **Best for** | One-shot code execution, AI agents | Multi-tenant SaaS platforms | Long-running processes, full OS | +## When to Use Dynamic Workers + +- Use Dynamic Workers when code is supplied at runtime and needs to run inside a tightly controlled Worker sandbox. +- Use `load()` for one-shot or constantly changing code, especially AI-generated code. +- Use `get(id, callback)` when the same code will receive follow-up requests and you want warm-isolate reuse when available. +- Prefer Workers for Platforms when tenants deploy versioned Workers you manage as durable platform assets. +- Prefer Sandbox when code needs a filesystem, long-running processes, custom binaries, or broader OS-level behavior. + +## Safe Starting Point + +- Start with `globalOutbound: null` and only open network access deliberately. +- Pass narrow RPC bindings through `env` instead of exposing raw bindings or secrets. +- Set explicit `limits` for CPU time and subrequests when executing untrusted or AI-generated code. +- Treat in-memory state as ephemeral across requests. If state matters, store it outside the isolate. + ## Architecture ``` @@ -42,7 +56,7 @@ Request → Loader Worker → env.LOADER.load(code) → Dynamic Worker isolate { "name": "my-dynamic-worker", "main": "src/index.ts", - "compatibility_date": "2026-01-28", + "compatibility_date": "$today", "compatibility_flags": ["nodejs_compat"], "worker_loaders": [{ "binding": "LOADER" }] } @@ -53,7 +67,7 @@ Request → Loader Worker → env.LOADER.load(code) → Dynamic Worker isolate export default { async fetch(request: Request, env: Env): Promise { const worker = env.LOADER.load({ - compatibilityDate: "2026-01-28", + compatibilityDate: "$today", mainModule: "worker.js", modules: { "worker.js": ` @@ -64,7 +78,8 @@ export default { }; ` }, - globalOutbound: null // Block all network access + globalOutbound: null, // Block all network access + limits: { cpuMs: 50, subRequests: 20 } }); return worker.getEntrypoint().fetch(request); @@ -83,7 +98,7 @@ export default { - [api.md](./api.md) — WorkerCode object, module types, RPC bindings, helper libraries - [configuration.md](./configuration.md) — Wrangler config, bundling, observability setup - [patterns.md](./patterns.md) — Code mode, credential injection, real-time logging, OpenAPI wrapping -- [gotchas.md](./gotchas.md) — Limits, pricing, common errors, best practices +- [gotchas.md](./gotchas.md) — Common errors, safe defaults, and live docs to retrieve pricing and limits ## See Also - [agents-sdk](../agents-sdk/) — Agents SDK (codemode, `createCodeTool()`, AI chat agents) diff --git a/skills/cloudflare/references/dynamic-workers/api.md b/skills/cloudflare/references/dynamic-workers/api.md index 63a999a..b909281 100644 --- a/skills/cloudflare/references/dynamic-workers/api.md +++ b/skills/cloudflare/references/dynamic-workers/api.md @@ -8,10 +8,11 @@ Creates a fresh Dynamic Worker. No caching — each call creates a new isolate. ```typescript const worker = env.LOADER.load({ - compatibilityDate: "2026-01-28", + compatibilityDate: "$today", mainModule: "worker.js", modules: { "worker.js": code }, - globalOutbound: null + globalOutbound: null, + limits: { cpuMs: 50, subRequests: 20 } }); const response = await worker.getEntrypoint().fetch(request); @@ -25,7 +26,7 @@ Loads or retrieves a cached Dynamic Worker by ID. The callback executes only if const worker = env.LOADER.get("hello-v1", async () => { const code = await env.MY_CODE_STORAGE.get("hello-v1"); return { - compatibilityDate: "2026-01-28", + compatibilityDate: "$today", mainModule: "index.js", modules: { "index.js": code }, globalOutbound: null @@ -47,7 +48,7 @@ Access a named entrypoint (a named `WorkerEntrypoint` export from the Dynamic Wo ### Required Properties -**`compatibilityDate`** (string) — Sets the Worker runtime version (e.g. `"2026-01-28"`). +**`compatibilityDate`** (string) — Sets the Worker runtime version. Use a current compatibility date. **`mainModule`** (string) — Name of the entry module. Must exist as a key in `modules`. @@ -80,6 +81,24 @@ Access a named entrypoint (a named `WorkerEntrypoint` export from the Dynamic Wo **`tails`** (ServiceStub[]) — Tail Workers that receive logs and metadata after execution. +**`limits`** ({ cpuMs?: number; subRequests?: number }) — Optional per-worker limits for CPU time and subrequests. Use this when executing untrusted code so failures happen inside a bounded sandbox instead of consuming the parent Worker's full budget. + +Retrieve the current maximum values from the [Custom Limits docs](https://developers.cloudflare.com/dynamic-workers/usage/limits/) before citing specific numbers. + +## Per-Invocation Limits + +You can also apply stricter limits at call time: + +```typescript +const entrypoint = worker.getEntrypoint(null, { + limits: { cpuMs: 10, subRequests: 5 } +}); + +await entrypoint.fetch(request); +``` + +If limits are set both on the Worker code and at the entrypoint call, the lower limits apply. + ## Custom Bindings via RPC (Cap'n Web) Passing standard Workers bindings (KV, R2, D1, etc.) directly into a Dynamic Worker is [not currently supported](https://developers.cloudflare.com/dynamic-workers/usage/bindings/). Instead, create wrapper RPC interfaces using `WorkerEntrypoint` classes and pass them as stubs. @@ -115,10 +134,11 @@ const chatRoom = ctx.exports.ChatRoom({ const worker = env.LOADER.load({ env: { CHAT_ROOM: chatRoom }, - compatibilityDate: "2026-01-28", + compatibilityDate: "$today", mainModule: "index.js", modules: { "index.js": codeFromAgent }, - globalOutbound: null + globalOutbound: null, + limits: { cpuMs: 50, subRequests: 20 } }); return worker.getEntrypoint("Agent").run(); @@ -190,7 +210,7 @@ const worker = env.LOADER.get("my-worker", async () => { export default app; `, "package.json": JSON.stringify({ - dependencies: { hono: "^4.0.0" } + dependencies: { hono: "^4" } }) }, bundle: true, @@ -200,7 +220,7 @@ const worker = env.LOADER.get("my-worker", async () => { return { mainModule, modules: modules as Record, - compatibilityDate: wranglerConfig?.compatibilityDate ?? "2026-01-01", + compatibilityDate: wranglerConfig?.compatibilityDate ?? "$today", compatibilityFlags: wranglerConfig?.compatibilityFlags ?? [] }; }); @@ -256,7 +276,7 @@ export class DynamicWorkerTail extends WorkerEntrypoint { const worker = env.LOADER.get(workerId, () => ({ mainModule: "index.js", modules: { "index.js": code }, - compatibilityDate: "2026-01-28", + compatibilityDate: "$today", tails: [ ctx.exports.DynamicWorkerTail({ props: { workerId } }) ] diff --git a/skills/cloudflare/references/dynamic-workers/configuration.md b/skills/cloudflare/references/dynamic-workers/configuration.md index 66fe332..76c10cc 100644 --- a/skills/cloudflare/references/dynamic-workers/configuration.md +++ b/skills/cloudflare/references/dynamic-workers/configuration.md @@ -9,7 +9,7 @@ The `worker_loaders` binding gives your Worker access to the Dynamic Worker Load { "name": "my-loader", "main": "src/index.ts", - "compatibility_date": "2026-01-28", + "compatibility_date": "$today", "compatibility_flags": ["nodejs_compat"], "worker_loaders": [{ "binding": "LOADER" }] } @@ -89,6 +89,26 @@ head_sampling_rate = 1 For Tail Worker setup and the `tails` property, see [api.md — Tail Workers](./api.md#tail-workers-observability). For the real-time log streaming pattern using Durable Objects, see [patterns.md — Real-Time Log Streaming](./patterns.md#real-time-log-streaming). +## Custom Limits + +Set explicit limits when running untrusted or AI-generated code. This keeps each invocation bounded even if the generated code loops, fans out, or behaves unexpectedly. + +```typescript +const worker = env.LOADER.load({ + compatibilityDate: "$today", + mainModule: "worker.js", + modules: { "worker.js": code }, + globalOutbound: null, + limits: { cpuMs: 50, subRequests: 20 } +}); + +const entrypoint = worker.getEntrypoint(null, { + limits: { cpuMs: 10, subRequests: 5 } +}); +``` + +Retrieve current maximum values and behavior from the [Custom Limits docs](https://developers.cloudflare.com/dynamic-workers/usage/limits/) before citing specific ceilings. + ## Supported Languages | Language | Module Type | Notes | @@ -119,7 +139,7 @@ const { mainModule, modules } = await createWorker({ files: { "src/index.ts": typescriptCode, "package.json": JSON.stringify({ - dependencies: { hono: "^4.0.0" } + dependencies: { hono: "^4" } }) }, bundle: true, @@ -129,7 +149,7 @@ const { mainModule, modules } = await createWorker({ const worker = env.LOADER.load({ mainModule, modules: modules as Record, - compatibilityDate: "2026-01-28" + compatibilityDate: "$today" }); ``` @@ -145,7 +165,7 @@ See the [@cloudflare/worker-bundler npm package](https://www.npmjs.com/package/@ ```typescript env.LOADER.load({ - compatibilityDate: "2026-01-28", // Required + compatibilityDate: "$today", // Required compatibilityFlags: ["nodejs_compat"], // Optional allowExperimental: true, // Optional — parent must have "experimental" flag // ... diff --git a/skills/cloudflare/references/dynamic-workers/gotchas.md b/skills/cloudflare/references/dynamic-workers/gotchas.md index 08ffc7c..60df8ed 100644 --- a/skills/cloudflare/references/dynamic-workers/gotchas.md +++ b/skills/cloudflare/references/dynamic-workers/gotchas.md @@ -1,5 +1,7 @@ # Gotchas & Best Practices +Retrieve current pricing, plan availability, and platform limits from the official docs before citing specific numbers. Those details change more often than the behavioral guidance in this file. + ## Common Errors ### Dynamic Worker returns an error or empty response @@ -103,6 +105,20 @@ async function workerId(files: Record): Promise { Use `globalOutbound: null` unless the Dynamic Worker genuinely needs network access. When it does, use a gateway to filter and inject credentials rather than passing raw tokens. +### Set explicit runtime limits + +Use `limits` to bound CPU time and subrequests for untrusted or AI-generated code. Choose values that fit the task rather than inheriting the parent Worker's full budget. + +```typescript +const worker = env.LOADER.load({ + compatibilityDate: "$today", + mainModule: "worker.js", + modules: { "worker.js": code }, + globalOutbound: null, + limits: { cpuMs: 50, subRequests: 20 } +}); +``` + ### Cold-start warmup You can trigger isolate initialization before the real request by calling a method that forces the isolate to load. This pattern is used in [Cloudflare's playground example](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground) but is not a documented API: @@ -116,52 +132,12 @@ try { } ``` -## Pricing - -Available on **Workers Paid plan** only. - -| Metric | Included | Overage | -|--------|----------|---------| -| Dynamic Workers created daily | 1,000/month | $0.002/Worker/day | -| Requests | 10M/month | $0.30/million | -| CPU time | 30M ms/month | $0.02/million ms | - -**Note**: The "Dynamic Workers created daily" charge is not yet active during beta. - -Requests and CPU time are billed as part of your existing Workers plan (not additional charges). - -### What counts as a "Dynamic Worker created"? - -| Scenario | Count | -|----------|-------| -| Same code, same ID, multiple invocations | **1** Dynamic Worker | -| Same code, different IDs | **1 per ID** | -| Same ID, different code versions | **1 per version** | -| No ID (`load()` used) | **1 per invocation** | - -**Cost implication**: `load()` is more expensive at scale than `get()` with stable IDs, because every invocation counts as a new Dynamic Worker. Example: 10,000 invocations/day of the same code costs `load()` = 10,000 billed workers ($0.002 x 10,000 = $20/day) vs `get("stable-id")` = 1 billed worker ($0.002/day). - -### CPU time - -Includes both: -- **Startup time**: Isolate initialization and code parsing -- **Execution time**: Active processing (excludes I/O wait) - -RPC method calls bill similarly to Durable Objects requests. Returned stubs from the same RPC method share a session (no additional billing). - -## Limits - -Dynamic Workers inherit [Workers platform limits](https://developers.cloudflare.com/workers/platform/limits/): - -| Resource | Workers Paid | -|----------|-------------| -| CPU time per invocation | 5 min max (configurable, default 30s) | -| Memory | 128 MB | -| Worker size | 10 MB | -| Subrequests per invocation | 10,000 (configurable up to 10M) | -| Simultaneous outgoing connections | 6 | +## Retrieve Current Pricing and Limits -Dynamic Worker-specific limits are not separately documented. Check the [official docs](https://developers.cloudflare.com/dynamic-workers/) for any updates. +- Use the [Pricing docs](https://developers.cloudflare.com/dynamic-workers/pricing/) for current plan availability, billing dimensions, and whether any pricing component is active yet. +- Use the [Custom Limits docs](https://developers.cloudflare.com/dynamic-workers/usage/limits/) for current limit controls and ceilings. +- Use the [Workers platform limits docs](https://developers.cloudflare.com/workers/platform/limits/) when you need current runtime ceilings. +- Prefer describing cost behavior qualitatively in the skill: `load()` creates fresh Workers, while `get()` can reuse a stable ID and usually fits repeated traffic better. ## Starter Templates @@ -177,6 +153,7 @@ Dynamic Worker-specific limits are not separately documented. Check the [officia - [Official Docs](https://developers.cloudflare.com/dynamic-workers/) - [Getting Started](https://developers.cloudflare.com/dynamic-workers/getting-started/) - [Bindings (Cap'n Web)](https://developers.cloudflare.com/dynamic-workers/usage/bindings/) +- [Custom Limits](https://developers.cloudflare.com/dynamic-workers/usage/limits/) - [Egress Control](https://developers.cloudflare.com/dynamic-workers/usage/egress-control/) - [Observability](https://developers.cloudflare.com/dynamic-workers/usage/observability/) - [Pricing](https://developers.cloudflare.com/dynamic-workers/pricing/) diff --git a/skills/cloudflare/references/dynamic-workers/patterns.md b/skills/cloudflare/references/dynamic-workers/patterns.md index b2684ff..66d0c57 100644 --- a/skills/cloudflare/references/dynamic-workers/patterns.md +++ b/skills/cloudflare/references/dynamic-workers/patterns.md @@ -18,10 +18,11 @@ export default { try { const worker = env.LOADER.load({ - compatibilityDate: "2026-01-28", + compatibilityDate: "$today", mainModule: "worker.js", modules: { "worker.js": code?.trim() || DEFAULT_CODE }, - globalOutbound: null + globalOutbound: null, + limits: { cpuMs: 50, subRequests: 20 } }); const result = await worker.getEntrypoint().fetch(new Request("https://worker/")); @@ -42,7 +43,7 @@ export default { ## AI Agent Code Mode -Instead of calling tools one at a time, the LLM writes code that calls multiple tools programmatically. This reduces token usage by up to 80%. +Instead of calling tools one at a time, the LLM writes code that calls multiple tools programmatically. Uses `@cloudflare/codemode` with `DynamicWorkerExecutor` to combine tools into a single `codemode` tool: @@ -118,7 +119,7 @@ const server = openApiMcpServer({ }); ``` -This approach uses approximately 1,000 tokens regardless of how many API endpoints exist, compared to over 1 million tokens for native MCP tool definitions. +This keeps authentication and request policy on the host side while the sandbox executes the generated orchestration code. ## Bundled Playground with Warm Caching @@ -149,8 +150,9 @@ export default { return { mainModule, modules: modules as Record, - compatibilityDate: wranglerConfig?.compatibilityDate ?? "2026-01-01", + compatibilityDate: wranglerConfig?.compatibilityDate ?? "$today", globalOutbound: null, + limits: { cpuMs: 50, subRequests: 20 }, tails: [ (ctx as any).exports.DynamicWorkerTail({ props: { workerId } }) ] From 871e38a7ab0b17f176183a36ae38f5c96b488c4a Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 22 Apr 2026 16:46:25 -0400 Subject: [PATCH 9/9] Fix Dynamic Workers examples --- skills/cloudflare/PREREQUISITES.md | 2 +- .../references/dynamic-workers/README.md | 8 ++--- .../references/dynamic-workers/api.md | 10 +++---- .../dynamic-workers/configuration.md | 10 +++---- .../references/dynamic-workers/gotchas.md | 15 +--------- .../references/dynamic-workers/patterns.md | 29 ++++++++++++------- 6 files changed, 35 insertions(+), 39 deletions(-) diff --git a/skills/cloudflare/PREREQUISITES.md b/skills/cloudflare/PREREQUISITES.md index 6f4cf00..ee29d0a 100644 --- a/skills/cloudflare/PREREQUISITES.md +++ b/skills/cloudflare/PREREQUISITES.md @@ -118,7 +118,7 @@ Check if existing skills should link to the new one: - [ ] No broken internal links between the 5 files - [ ] SKILL.md decision tree entry distinguishes this from similar products - [ ] SKILL.md product index row added in the correct section -- [ ] README.md includes a retrieval-bias directive (see PR #30 and the existing dynamic-workers/README.md for the pattern) +- [ ] README.md includes a retrieval-bias directive near the top of the file - [ ] File structure matches `README.md`, `api.md`, `configuration.md`, `patterns.md`, `gotchas.md` exactly ## Example: What Was Needed for Dynamic Workers diff --git a/skills/cloudflare/references/dynamic-workers/README.md b/skills/cloudflare/references/dynamic-workers/README.md index c59bf1b..b0c9254 100644 --- a/skills/cloudflare/references/dynamic-workers/README.md +++ b/skills/cloudflare/references/dynamic-workers/README.md @@ -1,6 +1,6 @@ # Dynamic Workers -Spin up isolated Workers at runtime to execute code on-demand in secure V8 isolates. Unlike pre-deployed Workers, Dynamic Workers are created from code strings at request time — no build step, no deploy. +Spin up isolated Workers at runtime to execute code on-demand in secure V8 isolates. Unlike pre-deployed Workers, Dynamic Workers are created from code strings at request time with no deploy step. If your code needs TypeScript transpilation or npm dependencies, bundle it before loading. > **Retrieval bias**: Your knowledge of Dynamic Workers APIs, limits, and pricing may be outdated. **Prefer retrieval over pre-training** — fetch from [Cloudflare docs](https://developers.cloudflare.com/dynamic-workers/) before citing specific numbers, API signatures, or configuration options. When these reference files and the docs disagree, **trust the docs**. @@ -13,7 +13,7 @@ Spin up isolated Workers at runtime to execute code on-demand in secure V8 isola | **Runtime** | V8 isolate | V8 isolate | Container (Durable Object) | | **When created** | At runtime from code strings | Pre-deployed via API | On first request to DO ID | | **Languages** | JS, Python | JS, TS, Python | Any (Dockerfile) | -| **State** | `load()`: ephemeral; `get()`: warm across requests (no isolate reuse guarantee) | Persistent (deployed script) | Ephemeral disk (lost on sleep); use R2 mounts for persistence | +| **Code lifecycle** | Loaded from code strings at runtime; `get()` can reuse a stable ID when available | Deployed ahead of time and reused by name | Built as a container image, then started on demand | | **Best for** | One-shot code execution, AI agents | Multi-tenant SaaS platforms | Long-running processes, full OS | ## When to Use Dynamic Workers @@ -56,7 +56,7 @@ Request → Loader Worker → env.LOADER.load(code) → Dynamic Worker isolate { "name": "my-dynamic-worker", "main": "src/index.ts", - "compatibility_date": "$today", + "compatibility_date": "2026-04-22", // Use current date for new projects "compatibility_flags": ["nodejs_compat"], "worker_loaders": [{ "binding": "LOADER" }] } @@ -67,7 +67,7 @@ Request → Loader Worker → env.LOADER.load(code) → Dynamic Worker isolate export default { async fetch(request: Request, env: Env): Promise { const worker = env.LOADER.load({ - compatibilityDate: "$today", + compatibilityDate: "2026-04-22", // Use a current compatibility date mainModule: "worker.js", modules: { "worker.js": ` diff --git a/skills/cloudflare/references/dynamic-workers/api.md b/skills/cloudflare/references/dynamic-workers/api.md index b909281..3f229da 100644 --- a/skills/cloudflare/references/dynamic-workers/api.md +++ b/skills/cloudflare/references/dynamic-workers/api.md @@ -8,7 +8,7 @@ Creates a fresh Dynamic Worker. No caching — each call creates a new isolate. ```typescript const worker = env.LOADER.load({ - compatibilityDate: "$today", + compatibilityDate: "2026-04-22", // Use a current compatibility date mainModule: "worker.js", modules: { "worker.js": code }, globalOutbound: null, @@ -26,7 +26,7 @@ Loads or retrieves a cached Dynamic Worker by ID. The callback executes only if const worker = env.LOADER.get("hello-v1", async () => { const code = await env.MY_CODE_STORAGE.get("hello-v1"); return { - compatibilityDate: "$today", + compatibilityDate: "2026-04-22", // Use a current compatibility date mainModule: "index.js", modules: { "index.js": code }, globalOutbound: null @@ -134,7 +134,7 @@ const chatRoom = ctx.exports.ChatRoom({ const worker = env.LOADER.load({ env: { CHAT_ROOM: chatRoom }, - compatibilityDate: "$today", + compatibilityDate: "2026-04-22", // Use a current compatibility date mainModule: "index.js", modules: { "index.js": codeFromAgent }, globalOutbound: null, @@ -220,7 +220,7 @@ const worker = env.LOADER.get("my-worker", async () => { return { mainModule, modules: modules as Record, - compatibilityDate: wranglerConfig?.compatibilityDate ?? "$today", + compatibilityDate: wranglerConfig?.compatibilityDate ?? "2026-04-22", compatibilityFlags: wranglerConfig?.compatibilityFlags ?? [] }; }); @@ -276,7 +276,7 @@ export class DynamicWorkerTail extends WorkerEntrypoint { const worker = env.LOADER.get(workerId, () => ({ mainModule: "index.js", modules: { "index.js": code }, - compatibilityDate: "$today", + compatibilityDate: "2026-04-22", // Use a current compatibility date tails: [ ctx.exports.DynamicWorkerTail({ props: { workerId } }) ] diff --git a/skills/cloudflare/references/dynamic-workers/configuration.md b/skills/cloudflare/references/dynamic-workers/configuration.md index 76c10cc..ca6bcaa 100644 --- a/skills/cloudflare/references/dynamic-workers/configuration.md +++ b/skills/cloudflare/references/dynamic-workers/configuration.md @@ -9,7 +9,7 @@ The `worker_loaders` binding gives your Worker access to the Dynamic Worker Load { "name": "my-loader", "main": "src/index.ts", - "compatibility_date": "$today", + "compatibility_date": "2026-04-22", // Use current date for new projects "compatibility_flags": ["nodejs_compat"], "worker_loaders": [{ "binding": "LOADER" }] } @@ -35,7 +35,7 @@ Dynamic Workers are typically used alongside other Cloudflare bindings in the ** "durable_objects": { "bindings": [{ "class_name": "LogSession", "name": "LogSession" }] }, - "migrations": [{ "tag": "v1", "new_sqlite_classes": ["LogSession"] }] + "migrations": [{ "tag": "v1", "new_classes": ["LogSession"] }] } ``` @@ -95,7 +95,7 @@ Set explicit limits when running untrusted or AI-generated code. This keeps each ```typescript const worker = env.LOADER.load({ - compatibilityDate: "$today", + compatibilityDate: "2026-04-22", // Use a current compatibility date mainModule: "worker.js", modules: { "worker.js": code }, globalOutbound: null, @@ -149,7 +149,7 @@ const { mainModule, modules } = await createWorker({ const worker = env.LOADER.load({ mainModule, modules: modules as Record, - compatibilityDate: "$today" + compatibilityDate: "2026-04-22" // Use a current compatibility date }); ``` @@ -165,7 +165,7 @@ See the [@cloudflare/worker-bundler npm package](https://www.npmjs.com/package/@ ```typescript env.LOADER.load({ - compatibilityDate: "$today", // Required + compatibilityDate: "2026-04-22", // Required. Use current date for new projects. compatibilityFlags: ["nodejs_compat"], // Optional allowExperimental: true, // Optional — parent must have "experimental" flag // ... diff --git a/skills/cloudflare/references/dynamic-workers/gotchas.md b/skills/cloudflare/references/dynamic-workers/gotchas.md index 60df8ed..8eba1a8 100644 --- a/skills/cloudflare/references/dynamic-workers/gotchas.md +++ b/skills/cloudflare/references/dynamic-workers/gotchas.md @@ -111,7 +111,7 @@ Use `limits` to bound CPU time and subrequests for untrusted or AI-generated cod ```typescript const worker = env.LOADER.load({ - compatibilityDate: "$today", + compatibilityDate: "2026-04-22", // Use a current compatibility date mainModule: "worker.js", modules: { "worker.js": code }, globalOutbound: null, @@ -119,19 +119,6 @@ const worker = env.LOADER.load({ }); ``` -### Cold-start warmup - -You can trigger isolate initialization before the real request by calling a method that forces the isolate to load. This pattern is used in [Cloudflare's playground example](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground) but is not a documented API: - -```typescript -const entrypoint = worker.getEntrypoint(); -try { - await entrypoint.__warmup__?.(); -} catch { - // Intentional — the method doesn't exist, but the isolate is now warm -} -``` - ## Retrieve Current Pricing and Limits - Use the [Pricing docs](https://developers.cloudflare.com/dynamic-workers/pricing/) for current plan availability, billing dimensions, and whether any pricing component is active yet. diff --git a/skills/cloudflare/references/dynamic-workers/patterns.md b/skills/cloudflare/references/dynamic-workers/patterns.md index 66d0c57..19622b0 100644 --- a/skills/cloudflare/references/dynamic-workers/patterns.md +++ b/skills/cloudflare/references/dynamic-workers/patterns.md @@ -18,7 +18,7 @@ export default { try { const worker = env.LOADER.load({ - compatibilityDate: "$today", + compatibilityDate: "2026-04-22", // Use a current compatibility date mainModule: "worker.js", modules: { "worker.js": code?.trim() || DEFAULT_CODE }, globalOutbound: null, @@ -128,6 +128,12 @@ Use content-hashed IDs with `get()` for efficient caching, and `@cloudflare/work ```typescript import { createWorker } from "@cloudflare/worker-bundler"; +type LoaderCtx = ExecutionContext & { + exports: { + DynamicWorkerTail(options: { props: { workerId: string } }): ServiceStub; + }; +}; + async function createWorkerId(files: Record): Promise { const payload = JSON.stringify(Object.entries(files).sort()); const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload)); @@ -138,7 +144,7 @@ async function createWorkerId(files: Record): Promise { } export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext) { + async fetch(request: Request, env: Env, ctx: LoaderCtx) { const { files } = await request.json<{ files: Record }>(); const workerId = await createWorkerId(files); @@ -150,11 +156,11 @@ export default { return { mainModule, modules: modules as Record, - compatibilityDate: wranglerConfig?.compatibilityDate ?? "$today", + compatibilityDate: wranglerConfig?.compatibilityDate ?? "2026-04-22", globalOutbound: null, limits: { cpuMs: 50, subRequests: 20 }, tails: [ - (ctx as any).exports.DynamicWorkerTail({ props: { workerId } }) + ctx.exports.DynamicWorkerTail({ props: { workerId } }) ] }; }); @@ -236,11 +242,14 @@ Pass only the capabilities the Dynamic Worker needs. Hide secrets, restrict acce ```typescript // Loader defines narrow interfaces -export class DatabaseReader extends WorkerEntrypoint { - async query(sql: string): Promise { - // Validate SQL is read-only - if (!/^\s*SELECT/i.test(sql)) throw new Error("Read-only access"); - return this.env.DB.prepare(sql).all().results; +export class OrderReader extends WorkerEntrypoint { + async listRecentOrders(customerId: string): Promise { + return this.env.DB + .prepare( + "SELECT id, total, created_at FROM orders WHERE customer_id = ? ORDER BY created_at DESC LIMIT 20" + ) + .bind(customerId) + .all().results; } } @@ -259,7 +268,7 @@ export class HttpGateway extends WorkerEntrypoint { // Dynamic Worker receives constrained capabilities const worker = env.LOADER.load({ env: { - DB: ctx.exports.DatabaseReader(), + ORDERS: ctx.exports.OrderReader(), // No direct KV, R2, or D1 access }, globalOutbound: ctx.exports.HttpGateway(), // Filtered egress