Skip to content

Workers for Platforms

Multi-tenant worker deployment with native isolation and metering.

Prerequisites: PRINCIPLES.md, PRIMITIVES.md, OVERVIEW.md


Workers for Platforms (WfP) enables multi-tenant worker deployment where each tenant gets isolated compute with usage-based metering. z0 uses WfP to provide tenant isolation at the compute layer.

ConceptWhat It Isz0 Usage
Dispatch NamespaceContainer that holds user workersOne namespace per environment
User WorkerTenant-specific worker codePer-tenant business logic, integrations
Dispatch WorkerRouter that invokes user workersTraffic routing, auth, metering entry point
Script TagsMetadata attached to user workerstenant_id, version, deployment info

Key Insight: WfP gives you SaaS-grade isolation without managing separate deployments per tenant. Tenants share infrastructure but get isolated execution.


Incoming Request
┌─────────────────────────────────────────────────────────┐
│ Dispatch Worker (z0-controlled) │
│ - Authenticate request │
│ - Resolve tenant from request │
│ - Check tenant status (active, suspended) │
│ - Route to appropriate user worker │
└─────────────────────────────────────────────────────────┘
│ dispatch_namespace.get(script_name)
┌─────────────────────────────────────────────────────────┐
│ Dispatch Namespace │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ User Worker │ │ User Worker │ │ User Worker │ │
│ │ tenant_a │ │ tenant_b │ │ tenant_c │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
Response (metered by WfP)
WfP Conceptz0 ConceptNotes
Dispatch NamespaceEnvironment (prod, staging)One namespace per environment
User WorkerTenant deploymentCustom logic, webhooks, integrations
Script Nametenant_idDirect mapping for O(1) lookup
Script TagsEntity metadataversion, deployed_at, config_version

A dispatch namespace is a collection of user workers that can be invoked by a dispatch worker.

// wrangler.toml (dispatch worker)
[[dispatch_namespaces]]
binding = "TENANT_WORKERS"
namespace = "z0-prod-tenants"
EnvironmentNamespacePurpose
Productionz0-prod-tenantsLive tenant code
Stagingz0-staging-tenantsPre-release testing
Developmentz0-dev-tenantsLocal development

z0 Recommendation: One namespace per environment. Avoid per-tenant namespaces (operational overhead, no isolation benefit).


User workers are tenant-specific scripts deployed to a dispatch namespace.

// Deploy tenant-specific worker
await api.workers.scripts.deploy({
account_id: ACCOUNT_ID,
dispatch_namespace: "z0-prod-tenants",
script_name: tenant_id, // e.g., "tenant_acme"
content: workerCode,
metadata: {
tags: [
{ key: "tenant_id", value: tenant_id },
{ key: "version", value: deployment_version },
{ key: "deployed_at", value: new Date().toISOString() }
]
}
});
{tenant_id} // Primary tenant worker
{tenant_id}-webhook // Webhook handler
{tenant_id}-integration-{name} // Third-party integration

User workers can have bindings scoped to their tenant:

// User worker bindings (set at deployment time)
{
bindings: [
{ type: "kv", name: "TENANT_KV", namespace_id: tenantKvId },
{ type: "d1", name: "TENANT_DB", database_id: tenantD1Id },
{ type: "secret_text", name: "API_KEY", text: encryptedApiKey }
]
}

z0 Pattern: User workers get read-only access to their tenant’s Config and Facts. Write operations go through the dispatch worker to enforce invariants.


The dispatch worker is the entry point. It handles routing, authentication, and metering.

export default {
async fetch(request, env) {
// 1. Authenticate and resolve tenant
const tenant_id = await resolveTenant(request, env);
if (!tenant_id) {
return new Response("Unauthorized", { status: 401 });
}
// 2. Check tenant status
const tenant = await env.TENANT_DO.get(
env.TENANT_DO.idFromName(tenant_id)
).fetch("/status");
if (tenant.status !== "active") {
return new Response("Tenant suspended", { status: 403 });
}
// 3. Get user worker from namespace
const userWorker = env.TENANT_WORKERS.get(tenant_id);
if (!userWorker) {
return new Response("Tenant not deployed", { status: 404 });
}
// 4. Dispatch request (metering happens automatically)
return userWorker.fetch(request);
}
};
// Dispatch worker adds context headers
const userRequest = new Request(request.url, {
method: request.method,
headers: new Headers(request.headers),
body: request.body
});
userRequest.headers.set("x-z0-tenant-id", tenant_id);
userRequest.headers.set("x-z0-request-id", crypto.randomUUID());
userRequest.headers.set("x-z0-trace-id", traceId);
return userWorker.fetch(userRequest);

WfP provides multiple isolation boundaries:

Each user worker runs in its own V8 isolate. A tenant cannot access another tenant’s memory, variables, or state.

┌───────────────────────────────────────────────────────┐
│ V8 Isolate: tenant_a │
│ - Own memory space │
│ - Own global scope │
│ - Cannot access tenant_b's data │
└───────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────┐
│ V8 Isolate: tenant_b │
│ - Completely separate │
│ - No shared state │
└───────────────────────────────────────────────────────┘

Each user worker gets tenant-specific bindings. tenant_a cannot access tenant_b’s KV, D1, or secrets.

Per-request CPU limits prevent one tenant from monopolizing resources:

LimitDefaultz0 Configuration
CPU time per request50ms50ms (standard), 15min (Workflows)
Subrequest limit10001000
Memory128MB128MB

User workers can only make outbound requests. They cannot:

  • Open arbitrary ports
  • Access other workers directly (must go through dispatch)
  • Access internal Cloudflare infrastructure

On top of WfP isolation, z0 adds:

LayerEnforcementPurpose
tenant_id on FactsDO ledger appendPrevent cross-tenant writes
Config scopeConfig lookupTenant sees only their Configs
D1 shardingDatabase per tenantQuery isolation

WfP provides usage data that flows directly into z0’s economic model.

MetricGranularityz0 Usage
RequestsPer user workerInvocation Facts
CPU timePer requestCost calculation
DurationPer requestLatency tracking
SubrequestsPer requestExternal call costing
// Cloudflare API: Get usage for a user worker
const usage = await api.workers.scripts.usage({
account_id: ACCOUNT_ID,
dispatch_namespace: "z0-prod-tenants",
script_name: tenant_id,
start: startDate,
end: endDate
});
// Returns:
{
requests: 1_234_567,
cpu_time_ms: 45_678,
duration_ms: 123_456,
subrequests: 234_567
}

Platform infrastructure costs are tracked differently from tool invocations. Workers for Platforms is infrastructure, not a Tool entity. Tools are external capabilities (Twilio, OpenAI, etc.) that agents invoke—infrastructure costs like compute are platform overhead.

// WfP usage becomes z0 platform cost Facts
Fact {
type: "cost",
subtype: "platform_compute",
tenant_id: tenant_id,
// NO tool_id - this is infrastructure cost, not tool invocation
from_entity: tenant_account_id,
to_entity: "platform_z0", // Platform account
amount: calculateCost(cpu_time_ms, requests),
currency: "USD",
data: {
compute_provider: "cloudflare_wfp", // metadata, not entity
cpu_time_ms: usage.cpu_time_ms,
requests: usage.requests,
period_start: startDate,
period_end: endDate
}
}
WfP Usage → z0 platform cost Facts → Tenant charges → Invoice
1. WfP tracks compute usage per tenant
2. z0 records platform cost Facts (subtype: platform_compute)
3. Pricing Config determines markup
4. Charges flow to tenant invoices

ScenarioUseReason
Tenant-specific business logicWfPIsolation + metering
Tenant-specific webhooksWfPCustom code per tenant
Shared z0 infrastructureRegular WorkersNo tenant variation
RTB routing logicRegular WorkersSame logic for all tenants
Tenant integrations (CRM, etc.)WfPTenant-specific credentials
Analytics aggregationRegular WorkersOperates across tenants
Dispatch/gatewayRegular WorkersRouting layer, not tenant code
Does the code vary per tenant?
├── No → Regular Worker
└── Yes → Does it need tenant secrets/credentials?
├── No → Regular Worker with tenant_id parameter
└── Yes → WfP User Worker

z0 Guideline: Use WfP when tenants need to run their own code or have tenant-specific integrations. Use regular Workers for shared z0 infrastructure.


All tenant logic lives in the dispatch worker. User workers are placeholders for metering.

Dispatch Worker
├── All business logic
├── Tenant resolution
└── Calls user worker for metering only

Use when: Tenants have identical logic, but you need per-tenant metering.

Dispatch worker handles auth/routing. User workers handle all business logic.

Dispatch Worker
├── Auth
├── Tenant resolution
└── Delegates everything to user worker
User Worker
├── Business logic
├── External integrations
└── Tenant-specific behavior

Use when: Tenants have significantly different logic or integrations.

Dispatch worker handles core z0 logic. User workers extend with tenant-specific behavior.

Dispatch Worker
├── Auth
├── Tenant resolution
├── Core z0 logic (Facts, Configs)
└── Calls user worker for tenant-specific extensions
User Worker
├── Webhook transformations
├── CRM-specific logic
└── Custom qualification rules

Use when: You want standard z0 behavior with tenant customization points.


z0_wfp_dispatches_total{tenant_id, script_name, status}
z0_wfp_dispatch_duration_ms{tenant_id}
z0_wfp_user_worker_cpu_ms{tenant_id}
z0_wfp_user_worker_errors_total{tenant_id, error_type}
z0_wfp_deployments_total{tenant_id, status}
// Dispatch worker creates parent span
const span = tracer.startSpan("z0.wfp.dispatch", {
attributes: {
tenant_id,
script_name,
request_path: request.url
}
});
// Pass trace context to user worker
request.headers.set("x-z0-trace-id", span.traceId);
request.headers.set("x-z0-span-id", span.spanId);
const response = await userWorker.fetch(request);
span.end();
ConditionAlert LevelAction
User worker deploy failedWarningCheck deployment pipeline
User worker error rate > 5%CriticalPage on-call
Dispatch latency p99 > 500msWarningReview user worker performance
CPU limit exceededWarningOptimize user worker or increase limit

Before using WfP for a tenant feature:

  • Dispatch namespace created per environment
  • Dispatch worker deployed with namespace binding
  • Tenant resolution logic implemented
  • Script naming convention documented
  • User worker deployment pipeline ready
  • Tenant-scoped bindings (KV, D1, secrets) configured
  • Metering → cost Fact pipeline implemented
  • trace_id propagation from dispatch to user workers
  • Observability metrics and alerts configured
  • Tenant suspension logic in dispatch worker

QuestionAnswer
What is WfP?Multi-tenant worker deployment with isolation and metering
Why use it?Tenant isolation + native usage tracking + per-tenant code
When to use?Tenant-specific logic, credentials, or customization
How does z0 use it?Dispatch worker + user workers per tenant
How does billing work?WfP usage → cost Facts → charges → invoices

Workers for Platforms turns Cloudflare Workers into a SaaS platform primitive. z0 uses it to provide tenant isolation while maintaining a single deployment pipeline and unified economic model.