Cloudflare Agents Week 2026: Sandboxes GA, Durable Object Facets, and the Unified CLI
Contents
Cloudflare kicked off “Agents Week 2026” on April 13 with a batch of announcements aimed at giving AI agents a full development stack on Cloudflare’s infrastructure. Three things stood out: Sandboxes hitting GA as a persistent isolated execution environment for agents, Durable Object Facets for giving dynamically loaded code its own SQLite database, and a unified CLI cf that covers all 3,000+ Cloudflare API operations.
Sandboxes GA: Persistent Dev Environments for Agents
The code execution problem
When you have an LLM-based agent write code, where that code actually runs is a surprisingly thorny problem. Spin up a clean container every time and you’re safe, but git clone plus npm install takes 30 seconds. Reuse an existing container and state bleeds across sessions. Hand credentials to the agent and you’ve got a leak risk.
Cloudflare decided to solve this as a platform primitive. About 9 months after the June 2025 beta, Sandboxes has reached general availability.
The basic usage is straightforward — request a sandbox by name:
import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request: Request, env: Env) {
const sandbox = getSandbox(env.Sandbox, "agent-session-47");
await sandbox.gitCheckout("https://github.com/org/repo", {
targetDir: "/workspace",
depth: 1,
});
return sandbox.exec("npm", ["test"], { stream: true });
},
};
If the sandbox is running, you get it back. If it’s stopped, it boots up. If it’s idle, it auto-sleeps and wakes on the next request. Same ID, same sandbox, from anywhere in the world.
Key features added for GA
Secure credential injection
A mechanism for letting agents access private services without ever seeing the credentials. Implemented as network-layer credential injection, it acts as a programmable egress proxy. The agent never touches the token itself.
class OpenCodeInABox extends Sandbox {
static outboundByHost = {
"my-internal-vcs.dev": (request, env, ctx) => {
const headersWithAuth = new Headers(request.headers);
headersWithAuth.set("x-auth-token", env.SECRET);
return fetch(request, { headers: headersWithAuth });
}
}
}
env.SECRET is passed securely through Workers bindings. Identity-aware injection and dynamic rule changes are also supported.
PTY (pseudo-terminal) support
Early agent shell access was a request-response loop: send a command, wait for output. Real terminal usage is different — you watch streaming output, interrupt, reconnect later, and pick up where you left off. PTY support, added in February, bridges that gap.
Pseudo-terminal sessions are proxied over WebSocket and are compatible with xterm.js. Each session has its own shell and working directory, and output is buffered server-side so you can replay missed output on reconnect.
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
if (url.pathname === "/terminal") {
const sandbox = getSandbox(env.Sandbox, "my-session");
return sandbox.terminal(request, { cols: 80, rows: 24 });
}
return new Response("Not found", { status: 404 });
},
};
Persistent code interpreter
A stateful code execution context for data analysis and scripting. Most code interpreter implementations spin up a fresh environment per snippet, so a variable set in one call isn’t available in the next. Sandboxes’ persistent context works like a Jupyter notebook — state carries over.
const ctx = await sandbox.createCodeContext({ language: "python" });
// First call: load data
await sandbox.runCode(`
import pandas as pd
df = pd.read_csv('/workspace/sales.csv')
df['margin'] = (df['revenue'] - df['cost']) / df['revenue']
`, { context: ctx });
// Second call: df is still alive
const result = await sandbox.runCode(`
df.groupby('region')['margin'].mean().sort_values(ascending=False)
`, { context: ctx, onStdout: (line) => console.log(line.text) });
Supports Python, JavaScript, and TypeScript. Results come back as matplotlib charts, structured JSON, or HTML-formatted Pandas tables.
Background processes and filesystem watching
Dev servers can run in the background with a public preview URL. waitForPort() and waitForLog() let you wait on actual signals rather than relying on sleep(2000).
const server = await sandbox.startProcess("npm run dev", {
cwd: "/workspace",
});
await server.waitForLog(/Local:.*localhost:(\d+)/);
const { url } = await sandbox.exposePort(3000);
sandbox.watch(), added in March, returns an SSE stream backed by Linux kernel inotify. File changes can trigger builds, config changes can restart servers — the same event-driven development loop that human developers take for granted.
const stream = await sandbox.watch('/workspace/src', {
recursive: true,
include: ['*.ts', '*.tsx']
});
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === 'modify' && event.path.endsWith('.ts')) {
await sandbox.exec('npx tsc --noEmit', { cwd: '/workspace' });
}
}
Snapshots (coming soon)
After an agent clones a repo, installs dependencies, and writes code, you might want to pause the session for code review. Keep the container running and you keep paying. Start from the container image and npm install runs again.
Snapshots save the full disk state — OS config, installed dependencies, modified files, data files — and restore it fast.
class AgentDevEnvironment extends Sandbox {
sleepAfter = "5m";
persistAcrossSessions = { type: "disk" };
}
Manual snapshots and forking (spinning up multiple instances from the same state) are also possible. Snapshots are coming soon; in the meantime, backup/restore methods are available and can cut a 30-second npm install down to 2 seconds.
Future releases will include memory state as well, letting terminals and editors resume exactly where they left off.
Pricing changes
A new “Active CPU Pricing” model charges only for active CPU cycles. Idle time while the agent waits for LLM responses is free.
| Instance type | Concurrency limit |
|---|---|
| lite | 15,000 |
| basic | 6,000 |
| large instances | 1,000+ |
Figma Make, an early beta partner, runs user-written agent code on Cloudflare Containers. “We needed a reliable, high-scale sandbox capable of executing untrusted agent and user-created code” (Figma, AI and Developer Platforms).
The SDK is at version 0.8.9, installable via npm i @cloudflare/sandbox@latest.
Durable Object Facets: Dedicated SQLite for AI-Generated Code
The Dynamic Workers constraint
Standard Cloudflare Workers are deployed via wrangler with bindings declared in a config file. Dynamic Workers take a different approach: code is injected at runtime via API and executed instantly as a V8 isolate.
Isolates are fundamentally different from containers. Linux containers take hundreds of milliseconds and hundreds of MB of memory to start. Isolates spin up in single-digit milliseconds with single-digit MB of memory — 100x faster, 10x lighter. This makes it practical for AI-generated code to go straight from generation to execution. Cloudflare reports an 81% reduction in token usage compared to sequential tool calls.
The catch with early Dynamic Workers: no persistent state. Code runs and the result is returned, but nothing is remembered.
Durable Objects and SQLite
Durable Objects are a Workers extension — globally unique objects where only one instance exists for any given ID. Each Durable Object comes with a local SQLite database.
| Property | Cloudflare KV | Durable Objects SQLite |
|---|---|---|
| Consistency | Eventually consistent | Strongly consistent (transactions) |
| Access pattern | Key-based | Arbitrary SQL queries |
| Latency | Low (edge cache) | Ultra-low (same machine) |
| Concurrent writes | Multiple Workers | Single instance serialization |
| State scope | Globally shared | Object-specific |
SQLite is stored locally on the machine where the Durable Object runs, so there’s no network round-trip for DB calls.
Why Dynamic Workers and Durable Objects didn’t mix
To give AI-generated code access to Durable Objects, you previously needed to:
- Write a class extending
DurableObject - Export it from the Worker’s main module
- Provision storage via the Cloudflare API
- Configure namespace bindings in
wrangler.toml
This doesn’t work well with “AI generates code, code runs immediately.” On top of that, handing a Durable Object namespace directly to dynamic code means AI-generated code could create unlimited objects or manipulate storage without audit trails.
The parent-child structure of Facets
Facets solve this with a parent-child model.
flowchart TD
A[User request] --> B[AI generates code]
B --> C{Execution mode}
C -- One-shot --> D[Dynamic Worker<br/>isolate spins up]
D --> E[Code executes,<br/>returns result]
E --> F[Isolate discarded]
C -- Persistent app --> G[AppRunner<br/>parent Durable Object]
G --> H[Dynamic Worker Loader<br/>loads code]
H --> I[ctx.facets.get]
I --> J{First access?}
J -- Yes --> K[Facet initialized<br/>new SQLite created]
J -- No --> L[Existing Facet reused<br/>state restored]
K --> M[Facet handles request<br/>persists to own SQLite]
L --> M
The parent (AppRunner) is a standard Durable Object deployed normally. Platform-level controls like Facet creation permissions and usage tracking live here.
The child (Facet) is created when dynamically loaded code exports a class extending DurableObject. The first argument to ctx.facets.get() is the Facet’s name — same name reuses the existing instance, new name triggers initialization.
A single Durable Object instance can manage multiple Facets by name. App A and App B can belong to the same AppRunner instance while each having their own independent SQLite.
// wrangler.jsonc
{
"durable_objects": {
"bindings": [
{
"name": "APP_RUNNER",
"class_name": "AppRunner",
"sqlite_database": "SqliteDb"
}
]
},
"worker_loaders": [{ "binding": "LOADER" }]
}
Dynamic code can only touch its own SQLite — the parent’s DB is off-limits. Storage quotas are managed at the Durable Object level. Logging, auditing, and billing are consolidated in the parent while children focus on their own work. This is the multi-tenant isolation that enterprise AI platforms need.
The open beta is available on Workers paid plans only.
Unified CLI cf and Local Explorer
Why Wrangler couldn’t cover all of Cloudflare
Cloudflare now has over 100 products and roughly 3,000 HTTP API operations. API SDKs, Terraform providers, and MCP servers are auto-generated from the OpenAPI schema, but CLI commands, Workers Bindings, wrangler.jsonc config, Agent Skills, and documentation were still maintained by hand.
Wrangler was originally designed as a Workers-centric CLI, and many Cloudflare products never got Wrangler commands at all. With hundreds of engineers, manually reviewing CLI naming conventions is a losing battle. One command uses get, another uses info — inconsistencies pile up, and AI agents start calling nonexistent commands.
TypeScript schema-driven code generation
A new TypeScript schema can define interactive CLI commands and their arguments, local dev / API request coordination, Workers Bindings RPC-style APIs, Agent Skills, and documentation context all in one place. It also supports reverse output to OpenAPI schemas, and consistency rules are now enforced at the schema layer.
| Target | Correct | Forbidden |
|---|---|---|
| Info retrieval | get | info, show, describe |
| Skip confirmation | --force | --skip-confirmations, --yes |
| JSON output | --json | --format, --output |
These three rules alone make a big difference across 100 products and 3,000 operations.
The technical preview released this week only covers a subset of products. Full API coverage is being tested internally, but Cloudflare opted for an early public release to gather feedback. The plan is to merge this with existing Wrangler into the next-generation CLI.
# Global install
npm install -g cf
# Or run the latest version each time
npx cf
The local-vs-remote consistency trap
Wrangler has local simulation for D1, R2, KV, Durable Objects, and Workflows. Miniflare provides the same APIs locally using a local SQLite backend — great for offline development and fast testing.
The trap: an agent thinks it’s modifying a remote D1 database, but it’s actually hitting the local simulation, and those changes don’t show up in the local dev server. The new CLI adds a --local flag to make the target explicit, and command output now clearly states which environment is being operated on.
Local Explorer for visualizing local data
Until now, checking “what’s in D1 right now” during local development meant digging through the .wrangler/state directory or using third-party tools.
Local Explorer is a local UI triggered by pressing e in a Wrangler or Cloudflare Vite plugin dev session. It shows all bindings attached to the current Worker and lets you inspect and manipulate the data in each. Covers KV, R2, D1, Durable Objects, and Workflows.
# After starting Wrangler → press e to open Local Explorer
wrangler dev
# → [e] to open Local Explorer
The Local Explorer API is available at /cdn-cgi/explorer/api with an OpenAPI spec. Point an agent at this address and it can autonomously manage local resources — schema inspection, test data seeding, DROP TABLE resets. The remote and local API shapes match, so passing --local is all it takes to switch a command from remote to local.