F FourIA GitHub ↗

FourIA Cloudflare Capacity Report

On this page

Scope: Tenant provisioning pipeline (apps/lerma/lib/lerma/provisioning/tenant_provisioner.ex), Cloudflare adapters (apps/lerma/lib/lerma/cloudflare/), IaC (apps/fouria/iac/, apps/lerma/iac/tf/), cloud/local E2E suites (apps/fouria/test/e2e/, apps/lerma/test/e2e/), and live account state.

Method: Code inspection of the provisioning and E2E paths, Cloudflare documentation cross-check (docs current on 2026-08-17), and a read-only live inventory of account 35afea16440634aa2350331d2a736eec (Paso4) via the Cloudflare API/MCP suite.

Inventory timestamp: 2026-08-17 ~20:31 UTC. No mutations were performed.

Security note: apps/fouria/test/e2e/.e2e.vars contains what appear to be live credentials (API token, R2 keys, AI Gateway key). These are committed in the working tree and should be rotated and removed from version control.


Table of Contents

  1. Executive Summary
  2. Per-Tenant Provisioning Footprint
  3. Documented Cloudflare Limits
  4. Capacity Model
  5. Live Account Inventory
  6. Actual Provisioning Capacity (Current State)
  7. Container Capacity
  8. E2E Resource Lifecycle & Leak Analysis
  9. E2E Cleanup Debt (Live)
  10. Services That Should Be Grouped
  11. Required Operational Fixes
  12. Live-State Discrepancies vs Repository Assumptions
  13. Final Answer

1. Executive Summary

The exact number of clients FourIA can provision is bounded by Cloudflare AI Gateway account limits, not by R2, WFP user Workers, or Access applications. The current implementation creates one AI Gateway per client environment, and the account already holds 5 gateways against a documented cap of 10 (Free) / 20 (Paid).

With the account’s current inventory:

ScenarioFree AI Gateway planPaid AI Gateway plan
New production-only clients515
New clients with production + dev27
New clients with production + dev + staging (3 envs)15

The account reports free_tier=false and num_allowed_scripts=500 (Workers Paid / Workers for Platforms), so the Paid = 20 gateway column is the most likely correct figure. The AI Gateway subscription itself could not be independently verified (subscriptions endpoint was permission-denied), so both columns are provided.

After grouping AI Gateways (one gateway per platform stage or billing tier instead of one per client), the AI Gateway quota stops bounding the client count and the next limiting quota becomes Access groups:

  • 254 additional clients under the current one-group-per-client model (300 limit − 46 used).
  • More if Access applications/groups are also consolidated.

The largest E2E cleanup lever is 27 retained moltbot-e2e-* service tokens — only 22 service-token slots remain, and each additional failed/cancelled cloud E2E run can consume one for a full year.


2. Per-Tenant Provisioning Footprint

The pipeline is defined in tenant_provisioner.ex:52-63. Each step creates the following resources:

#StepResource createdRef
1:ai_gatewayAI Gateway fouria-{routable_slug}tenant_provisioner.ex:390-404, ai_gateway.ex:22-37
2:access_policyPer-instance Access application FourIA - {hostname} + closed group Client Users - {hostname}tenant_provisioner.ex, access_policy.ex
3:r2_bucketR2 bucket fouria-backup-{routable_slug}tenant_provisioner.ex:426-439, r2.ex:18-41
4:fetch_bundleNone (R2/registry reads only)worker_bundle.ex
5:dispatch_deployWFP user Worker script + Container application <script>-sandbox + SQLite Sandbox DO namespacetenant_provisioner.ex:465-510, dispatch_namespace.ex:111-182, containers.ex:372-415
6:worker_tagsTags on the script (customer_id, plan, env, environment — 4 of the 8 allowed)tenant_provisioner.ex:512-527, tags.ex:19-51
7:create_instanceDB record onlytenant_provisioner.ex:529-537
8:invalidate_cacheKV entry invalidation (HTTP, not CF API)tenant_provisioner.ex:770-784
9:health_pollPublic HTTPS probe (not CF API)health_poller.ex
10:production_transitionDB status fliptenant_provisioner.ex:575-582

Resource count per client

ResourceProduction tenantDev tenant (additional)Per client (prod + dev)
AI Gateway112
Access application112
Access group112
R2 backup bucket112
WFP user Worker script112
Durable Object namespace112
Container application112
Worker tags448
Worker bindings~13–22~13–22~26–44
DB records1+1+2+
DNS records000 (wildcard routing)

Key details:

  • AI Gateway is per-environment. routable_slug includes the -dev environment suffix and the platform suffix (-pre/-staging), so production, dev, and staging each get a distinct gateway (tenant_provisioner.ex:1042-1044).
  • Access is per-environment. Production uses {slug}.fouria.io; dev uses {slug}-dev.fouria.io. Each has a separate application and validated-user group, preventing production invitations from granting dev access and vice versa.
  • R2 is per-environment (fouria-backup-{slug} vs fouria-backup-{slug}-dev).
  • WFP scripts are unlimited by Cloudflare documentation — the ordinary 500-Worker account limit does not apply to scripts deployed into dispatch namespaces.
  • Bindings per Worker are well under the 128/env-var limit (Workers Paid) and are not a constraint (tenant_provisioner.ex:788-900).

3. Documented Cloudflare Limits

All limits below are from official Cloudflare documentation current on 2026-08-17.

ServiceDocumented limitEffect on FourIASource
AI Gateway gateways10/account (Free), 20/account (Paid)Immediate client-count bottleneckAI Gateway limits
AI Gateway metadata5 custom metadata entries/requestShared gateways can identify tenants, but dimensions are boundedAI Gateway limits
AI Gateway spend limits20 rules/gatewayPer-tenant budget rules on a shared gateway are capped at 20Spend limits
AI Gateway log storage100,000/account (Free); 10M/gateway (Paid); 500 logs/s/gatewayCost telemetry should live in Analytics Engine, not gateway logsAI Gateway limits
Access applications500/accountSecond-order bottleneck (~478 remaining live)Cloudflare One account limits
Access rule groups300/accountFirst Access bottleneck (~254 remaining live)Cloudflare One account limits
Access service tokens50/accountAcute E2E risk — only 22 remaining liveCloudflare One account limits
R2 buckets1,000,000/account; unlimited objects & storage per bucketNot a practical limitR2 limits
R2 API50 bucket-mgmt ops/s; REST API 1,200 req/5 minBulk provisioning/cleanup must be pacedR2 limits
WFP scriptsUnlimitedThe per-tenant WFP model is correct for scaleWorkers for Platforms limits
WFP DO namespacesNo limitWorkers for Platforms limits
WFP tags8/scriptCurrent 4 tags are fine; keep taxonomy boundedWorkers for Platforms limits
Ordinary Workers100 (Free) / 500 (Paid)Applies to standalone platform/E2E Workers, not WFP user scriptsWorkers limits
Containers concurrent1,500 vCPU / 6 TiB mem / 30 TB diskActive-instance capacity, not application countContainers limits
Container image storage50 GB/account totalOld image tags must be prunedContainers limits
Durable Objects10 GB per SQLite DO; no WFP namespace-count limitPer-tenant state must be bounded; R2 is the bulk layerDO SQLite GA changelog
Browser RunFree: 3 concurrent; Paid: 120 concurrent, 1 new/sShared runtime concurrency, not a provisioning resourceBrowser Run limits
Analytics Engine20 blobs + 20 doubles + 1 index per datapoint; 250 datapoints/invocation; 16 KB blobs; 3-month retentionShared dataset is correct; retention caps cost dashboardsAnalytics Engine limits
Secrets Store1 store/account; 100 secrets/accountMust not be used per-tenantSecrets Store changelog
Cloudflare API1,200 req/5 min per user/account token; 50 user / 500 account tokens~13–18 management calls per new tenant; ~66–85 tenants per 5-min windowError 429 docs

4. Capacity Model

usable_clients =
  min(
    remaining_ai_gateways / gateways_per_client,
    remaining_access_apps,
    remaining_access_groups,
    remaining_r2_buckets / buckets_per_client,
    active_container_capacity,
    account-specific limits
  )

Definitions:

G  = 10 (Free) or 20 (Paid) AI Gateways
G0 = current AI Gateways (live: 5)
A0 = current Access apps (live: 22)
R0 = current Access groups (live: 46)
B0 = current R2 buckets (live: 25)
e  = number of distinct per-client environments (1, 2, or 3)
N_gateway    = floor((G - G0) / e)
N_access_app = 500 - A0
N_access_grp = 300 - R0
N_r2         = floor((1,000,000 - B0) / e)

The current Access implementation uses one app + one group for production and dev because access_policy.ex:235 hardcodes the base slug domain. If Access is made environment-specific, the app/group terms would also need division by e.


5. Live Account Inventory

Read-only inventory of account 35afea16440634aa2350331d2a736eec (Paso4). All endpoints below returned 200 OK unless noted.

ResourceUsedRemainingNotes
AI Gateway gateways55 Free / 15 Paid1 shared moltworker + 4 tenant/staging
Access applications224784 platform + 18 tenant (FourIA - *)
Access groups462541 root + 45 Client Users - *
Access service tokens28221 platform + 27 moltbot-e2e-* (valid until 2027)
R2 buckets25999,9755 platform + 18 fouria-backup-* + 2 empty E2E
WFP dispatch namespaces4Unlimitedfouria-tenants (3 scripts), fouria-tenants-staging (6 scripts), 2 stray empty
WFP dispatch scripts9UnlimitedUnlimited per Cloudflare docs
Ordinary Workers5495 (Paid)fouria-dispatch*, mila-gemini-proxy, 2 E2E
Container applications13No published app-count limit11 tenant + 2 E2E; all max_instances=3
Durable Object namespaces11No WFP count limit9 tenant + 2 E2E
Secrets Store1 store, 0/100 secrets100default_secrets_store
Analytics Enginefouria_cost_metrics confirmedShared~191 rows/24h (adaptive sample)
Browser Run usageUnavailableNo read-only endpoint exposed
Workers plan tierfree_tier=false, num_allowed_scripts=500Workers Paid / WFP inferred
AI Gateway subscriptionUnknownSubscriptions endpoint permission-denied

Gateway classification (live)

GatewayKind
moltworkerPlatform / shared
fouria-arua-stagingStaging tenant
fouria-tio-stagingStaging tenant
fouria-nocf-8Test/E2E-looking
fouria-test2-dev-stagingTest/staging tenant

Container instance-type mix (live)

Instance typevCPU / Mem / DiskApps
lite1/16 / 256 MiB / 2 GB7
basic1/4 / 1 GiB / 4 GB4 (incl. 2 E2E)
standard-11/2 / 4 GiB / 8 GB1
standard-21 / 6 GiB / 12 GB1

6. Actual Provisioning Capacity (Current State)

New production-only clients

Each new production client consumes 1 AI Gateway.

AI Gateway planAvailableNew clients
Free (10 − 5 used)55
Paid (20 − 5 used)1515

New clients with production + dev

Each client consumes 2 gateways (prod + dev), but shares one Access app/group.

AI Gateway planNew clients
Free (5 / 2)2
Paid (15 / 2)7

New clients with production + dev + staging (3 distinct environments)

AI Gateway planNew clients
Free (5 / 3)1
Paid (15 / 3)5

Answer (likely Paid plan): the current account can fully provision 15 additional production-only clients, or 7 additional clients with both production and dev, before the AI Gateway quota is exhausted. This is the current-state limit; the theoretical earlier figures of 20/10 did not subtract the 5 gateways already in the account.

If the two E2E-looking gateways (fouria-nocf-8, fouria-test2-dev-staging) are confirmed test-only and cleaned, capacity rises to 17 new production-only (Paid) and 8 new prod+dev (Paid) clients.

After grouping AI Gateways

Routing all tenant traffic through one shared tenant gateway consumes 2 gateway slots total (platform + shared tenant). Gateway count then stops bounding the client count, and the next constraints become:

  • Access groups: 254 remaining clients under the current one-group-per-client model.
  • Access apps: 478 remaining.
  • Containers: effectively unlimited at current sizing.
  • The new per-gateway constraints are log storage (10M logs/gateway on Paid), 20 spend-limit rules/gateway, 10 datasets/gateway, 5 metadata entries/request.

7. Container Capacity

The provisioner defaults to lite and max_instances=3 (tenant_provisioner.ex:974,1011-1021). Container applications start at instances: 0, so provisioning an application does not reserve its full max_instances capacity; these figures are worst-case simultaneous-live-instance ceilings.

Instance typeOne environment per clientProd + dev (worst case)
lite5,0002,500
basic2,0001,000
standard-1512256
standard-2341170

Live account maximum configured envelope (13 apps × max_instances=3, by type):

DimensionMax configuredAccount limitHeadroom
vCPU~8.81,500~1,491
Memory~47 GiB6 TiB~5.95 TiB
Disk~150 GB30 TB~29.85 TB

Container capacity is orders of magnitude above the AI Gateway bottleneck. The more immediate container concern is the 50 GB shared image-storage limit: all tenant applications should reuse a small number of shared image tags and prune old tags.


8. E2E Resource Lifecycle & Leak Analysis

Lerma dashboard E2E (apps/lerma/test/e2e)

client_provisioning.txt runs one real provisioning through the full pipeline. Not run in CI; run with --sequential (mix test.e2e).

A successful run creates: 1 AI Gateway, 1 Access app, 1 Access group, 1 R2 bucket, 1 WFP script, 1 DO namespace, 1 container app.

Teardown is incomplete. DELETE /api/clients/:id is a plain Repo.delete (clients.ex), and decommission_instance/1 deletes only the dispatch script (tenant_deprovisioner.ex:144-163). The full decommission/1 (Access + AI Gateway + R2 + archive, tenant_deprovisioner.ex:28-52) is never called by production code. Therefore every successful manual Lerma E2E run permanently leaks:

  • AI Gateway
  • Access application + group
  • R2 bucket
  • Container application
  • DO namespace

Each leaked Lerma E2E run consumes one AI Gateway slot permanently — on a 20-gateway cap this is the fastest path to quota exhaustion.

Fouria cloud E2E (apps/fouria/test/e2e)

Runs on develop pushes only in CI. A fresh run creates: 1 ordinary Worker, 1 container app, 1 R2 bucket, 1 Access service token (8760h), 1 DO namespace, plus a retained registry image. The Access application is intentionally skipped (workers.dev hostname + DEV_MODE).

Cleanup behavior:

ResourceCleaned on success?
WorkerYes
Container applicationYes
Service tokenYes
R2 bucketUsually nowrangler r2 bucket delete fails on non-empty buckets; terraform-destroy (which empties first) is never invoked
Failed/cancelled runsLeak everything (worker + container + bucket + token)
Cache evictionRemoves only the local cache JSON, never the Cloudflare resources

Local E2E (E2E_LOCAL=true) uses wrangler dev, Docker, Miniflare, and local R2 emulation — no equivalent Cloudflare tenant-resource quota consumed.

Dispatch and staging suites

  • provision_and_dispatch.txt: creates/deletes one Supabase clients row, no Cloudflare tenant resources.
  • staging_redeploy/worker_redeploy.txt: re-deploys the persistent test2 instance in place, 0 new resources.

Parallelism & caching

  • Lerma E2E: sequential.
  • Fouria cloud E2E: one suite, files sequential; cache forced off on develop merges, so CI provisions fresh each push.
  • No concurrency group exists in the workflows, so overlapping develop pushes can provision several temporary Workers concurrently (unique run-IDs prevent collisions, but quotas stack).
  • Local cloud runs can preserve cached Workers/containers/buckets/tokens indefinitely.

9. E2E Cleanup Debt (Live)

Retained test/E2E-prefixed resources found in the live inventory (none cleaned — read-only pass):

ResourceRetainedRisk
Access service tokens moltbot-e2e-*27Only 22 slots left (limit 50); each leaked run consumes one for a year
Test-pattern Access apps16Applies pressure on the 500-app limit
Test-pattern Access groups~43Applies pressure on the 300-group limit
E2E-looking AI Gateways2fouria-nocf-8, fouria-test2-dev-staging
E2E R2 buckets2Both empty — safe deletion candidates
E2E container applications2moltbot-sandbox-e2e-*
Standalone E2E Workers2moltbot-sandbox-e2e-*
E2E DO namespaces2moltbot-sandbox-e2e-*_Sandbox
Stray dispatch namespaces2fouria-test-client-322, test-namespace (empty)

10. Services That Should Be Grouped

1. AI Gateway — group immediately

The current one-gateway-per-client-environment design is the binding constraint (5/10 or 5/20 used). Recommended model:

  • One gateway for production, one for dev/staging (or a small pool by billing/security tier).
  • Route tenants through custom metadata (tenant_id, environment, plan, request_id — up to 5 entries/request supported).
  • Keep cost attribution in Analytics Engine / lerma Postgres.
  • Note: only 20 spend-limit rules/gateway — per-tenant budgets on a shared gateway are not unbounded.

2. Access applications — consolidate

One app per client reaches the 500 limit before the container platform does. Evaluate one wildcard application per platform stage with authorization enforced in the dispatch layer and database policy. Requires a security review — do not remove per-tenant authorization until tenant isolation is enforced elsewhere.

3. Access groups — reduce cardinality

300-group limit is the next wall after grouping gateways (~254 clients remaining). Group by organization/plan/role where security allows; otherwise plan tenant-specific groups carefully.

4. E2E service tokens — eliminate or reuse

Cloud E2E already runs with DEV_MODE and skips Access. The Terraform service token is unnecessary for the normal path. Options: remove it from the fixture, reuse one short-lived token, use a separate E2E-only account, and add stale-token cleanup.

5. WFP namespaces — keep as-is (already grouped)

Current model is correct: one namespace per platform stage, many tenant scripts per namespace, one shared bundle, 4 of 8 tags used. Do not create per-tenant namespaces.

6. Analytics Engine — keep shared

Shared dataset with tenant dimensions is correct. Do not create per-client datasets. Audit the staging dataset selection (multiple dataset names exist in code; staging binds the production dataset due to an env-var mismatch).

7. Secrets Store — keep account-level and sparse

The F4E1-encrypted-R2 + secret_text binding design correctly avoids the 100-secret account cap. Do not move to per-tenant Secrets Store entries.

8. R2 — grouping optional

Per-tenant buckets are fine quota-wise (1M limit). Grouping into one bucket with tenant prefixes would reduce bucket-management overhead but requires changes to backup paths, encryption boundaries, bindings, cleanup, and isolation tests. Fix cleanup first.

9. Containers — keep tenant-scoped, manage images

Container applications stay tied to tenant Workers/DOs. Reuse shared image tags, freeze inactive tenants, prune old registry images, and watch the shared 50 GB registry limit.

10. Browser Run — pool runtime sessions

Limit is concurrent browsers (120 on Paid), not bindings. Reuse sessions, call browser.close(), use queues for bursts, and cap per-plan concurrency.


11. Required Operational Fixes

  1. Wire full tenant deprovisioning (TenantDeprovisioner.decommission/1) into the client deletion path so Lerma E2E runs stop leaking AI Gateways, Access apps/groups, R2 buckets, and container apps. (Fixed — DELETE /api/clients/:id now decommissions and archives the client; see Lerma issue #207.)
  2. Delete container applications when deleting tenant Worker scripts (decommission_instance/1 only deletes the script). (Fixed — decommission_instance/1 deletes the container application alongside the script.)
  3. Make deprovisioning environment-aware (prod/dev/staging suffixes). (Fixed — decommissioning derives all names from Lerma.Instances.Naming and iterates the client’s instances across environments; shared pool gateways are never deleted.)
  4. Drain R2 objects before deleting E2E buckets (invoke the existing terraform-destroy empty-then-delete path).
  5. Add a scheduled cleanup job for stale moltbot-e2e-* / moltbot-sandbox-e2e-* resources.
  6. Add a GitHub Actions concurrency group for the cloud E2E job.
  7. Remove the unnecessary E2E service token from the Terraform fixture or shorten its lifetime.
  8. Fix Access domains/policies for dev/staging isolation (currently shared base-slug app breaks dev-hostname auth; see §12).
  9. Ensure CLOUDFLARE_AI_GATEWAY_API_KEY is sourced from Lerma’s environment as a real AI-scoped runtime credential; the provisioner must never generate a random value for it.
  10. Audit CF_ENVIRONMENT=pre vs "staging" config branches and the Analytics dataset env-var mismatch.
  11. Rotate the live-looking credentials committed in apps/fouria/test/e2e/.e2e.vars and remove them from version control.

12. Live-State Discrepancies vs Repository Assumptions

  1. arua and tio are orphaned. They have Access apps, AI Gateways, backup buckets, and container apps, but no tenant-arua/tenant-tio WFP script and no matching DO namespace (11 tenant containers vs 9 WFP scripts). Appear abandoned/legacy.
  2. CONFIG_BUCKET buckets don’t exist live. fourai-moltlazy-config[-dev] are documented in AGENTS.md but absent; code comments confirm CONFIG_BUCKET was removed.
  3. Worker naming: live account has fouria-dispatch / fouria-dispatch-staging (matches workers/fouria-dispatch/), while wrangler.jsonc top-level name is fouria / fouria-staging (local-dev only or stale).
  4. Per-tenant backup buckets are undocumented: live shows 18 fouria-backup-<slug> buckets while docs describe a single fouria-data bucket.
  5. Repository declares public health Access apps (4, from IaC access.tf), but only 4 recognizable platform apps were found live — IaC and account state should be reconciled.
  6. Analytics dataset drift: repository code references fourai_cost_metrics, fouria_cost_metrics_pre, and fouria_cost_metrics across different files; live confirms fouria_cost_metrics exists. Staging tenants may be binding the production dataset.
  7. Everything else matched: moltworker gateway, fouria-tenants[-staging] namespaces, fouria-data[-staging], fouria_cost_metrics, FourIA Platform (*.fouria.io), fouria-dashboard-local-ci token.

13. Final Answer

Current state (with live inventory, likely Paid AI Gateway plan):

  • 15 additional production-only clients (or 5 on the Free plan).
  • 7 additional clients with production + dev (or 2 on the Free plan).
  • 5 additional clients with production + dev + staging (or 1 on the Free plan).
  • These assume 5 gateways currently in use; if the 2 E2E-looking gateways are cleaned, add 2/1 production-only / prod+dev clients respectively.

After grouping AI Gateways (recommended, immediate action):

  • The gateway quota stops bounding the client count.
  • Next limiting quota: Access groups → 254 additional clients under the current model.
  • Access apps → 478; containers and R2 are effectively unbounded at current sizing.

The immediate operational action is an ownership-reviewed cleanup of the 27 E2E service tokens, ~43 test Access groups, 16 test Access apps, 2 E2E gateways, the orphaned arua/tio chains, and stale E2E containers/DOs — followed by fixing the Lerma teardown path so future E2E runs stop leaking quota.


Appendix: Reproduce the Inventory

# AI Gateway
curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/ai-gateway/gateways" -H "Authorization: Bearer $CF_API_TOKEN"

# Access
curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/access/apps" -H "Authorization: Bearer $CF_API_TOKEN"
curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/access/groups" -H "Authorization: Bearer $CF_API_TOKEN"
curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/access/service_tokens" -H "Authorization: Bearer $CF_API_TOKEN"

# R2
curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/r2/buckets" -H "Authorization: Bearer $CF_API_TOKEN"

# WFP
curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/workers/dispatch/namespaces" -H "Authorization: Bearer $CF_API_TOKEN"

# Containers
curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/containers/applications" -H "Authorization: Bearer $CF_API_TOKEN"

# Secrets Store
curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/secrets_store/quota" -H "Authorization: Bearer $CF_API_TOKEN"