There's a product shape that got very common in 2026: the app builder. A user describes what they want, an agent writes the code, and your platform runs it — Lovable, Bolt, v0, Replit Agent and a hundred vertical clones. Executing the generated code is now a solved-ish problem. The part that stays awkward is storage: each generated app needs to persist state, and you cannot safely hand AI-written code a binding to your whole database.

On August 3, 2026, during Agents Week, Cloudflare shipped a primitive aimed squarely at this: Durable Object Facets for Dynamic Workers. The one-line version from Cloudflare's own title: give each AI-generated app its own database.

The trust problem, stated plainly#

Say an agent writes a little CRM. It needs a place to keep contacts. Your options before Facets were both bad:

  1. Give the generated code a Durable Object namespace binding. Now AI-written code you didn't audit can address any object in that namespace — every other tenant's data included. That's a data-isolation hole, not a feature.
  2. Provision a real database per app — a Postgres branch, a fresh D1, whatever. Correct isolation, but now you're standing up and billing a database service for every ephemeral app a user vibe-codes into existence. That doesn't scale to "thousands of tiny apps."

Facets thread the needle: isolated, real, persistent storage for the generated code, with none of the namespace access.

How a facet works: supervisor and child#

A facet is a Durable Object class loaded dynamically from a Dynamic Worker and run as a child of one of your own Durable Objects (docs). Two roles:

So the generated CRM code gets a genuine SQLite database it can read and write with normal storage.sql calls — but it never receives a namespace binding, and every capability it has is one the supervisor chose to expose. The shape looks like this:

// Supervisor: a Durable Object YOU wrote and trust.
export class Tenant extends DurableObject {
  async runGeneratedApp(appModule, request) {
    // Load the AI-written class from a Dynamic Worker and run it as a
    // facet — a child with its OWN isolated SQLite database.
    const app = this.ctx.facets.get("app", () => ({
      // dynamically-loaded, untrusted class
      class: appModule.AppDurableObject,
    }));

    // The child persists to its own DB via normal DO storage APIs.
    // It has NO handle to the Tenant namespace — only what we pass in.
    return app.fetch(request);
  }
}

The generated app writes to storage.sql as if it owned a database, because — inside its facet — it does. What it can't do is reach sideways into another tenant. The supervisor is the only thing holding namespace-level access, which makes it the one place you enforce quotas, kill a runaway app, or revoke a tenant. That's the multi-tenant isolation model most people bolt on with careful query discipline — here it's structural.

Facets vs a database-per-tenant#

This is a genuine decision, not a slam dunk. Use the table above, but the short version:

Facets aren't trying to be your customers' database. They're trying to be the safe, cheap, isolated scratch space for the apps your agent builds — which is a different job.

The caveat, and the pattern to copy#

Facets are in beta, available immediately on the Workers Paid plan as of August 3. Usable for real building today, but wrap the supervisor/facet calls behind your own thin interface so a pre-GA API change doesn't ripple through your product — the same discipline that keeps any Cloudflare primitive from becoming your architecture.

Even if you never touch Cloudflare, the pattern is the takeaway: untrusted, generated code should run as a supervised child that holds only its own storage, never a handle to the namespace. Facets make that a platform primitive instead of something you invent. Paired with @cloudflare/computer — one persistent computer for the agent — it's the two halves of the same Agents Week bet: give the thing that runs generated code a real filesystem, and give the thing it generates a real, isolated database.