Guía10 min

Presupuestos y cuotas por tenant en agentes: tres techos, degradar antes de cortar

Resumen

Un agente multi-tenant no se frena con el spend limit del proveedor. Tres techos por tenant (minuto, día, mes) anclados al precio real, escalera de degradación antes del 429 y el tenant sale de auth, no del prompt. Distinto de rate limits, del kill switch y de la noticia de spend limits de Cloudflare.

OpenAICloudflare
Tres techos de presupuesto por tenant — minuto, día y mes — con una escalera de degradación antes del corte

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

El spend limit del proveedor protege tu tarjeta, no a Ana de Bruno. OpenAI limita org y proyecto, no tu tenant_id. Anthropic, al cap del tier (Start $500, Build $1,000, Scale $200,000), pausa toda la org hasta el día 1 00:00 UTC. Cloudflare parte por metadata, pero si el id no sale de auth, el bucket es compartido.

Contrato: tres techos por tenant (minuto / día / mes), en dólares. Degrada antes de cortar. Tenant desde auth. Cuota no se reintenta.

No es reintentos (por llamada). No es el kill switch (apaga el agente). No es el circuit breaker (una dependencia). Tampoco sustituye la noticia de spend limits de Cloudflare: aquella es el producto; esta es la cuota tuya por inquilino.

Tres techos, no un número mágico

Un solo cap mensual es tarde: el loop ya quemó el día. Un solo cap por request es sordo: 200 llamadas baratas no equivalen a 2 con visión. Tres ventanas, el mismo tenant_id:

TechoVentanaPara quéSi se pasa
Minutosliding 60 sloops, fan-out, tool storms429 inmediato, sin retry
DíaUTC 00:00 → 00:00un tenant “productivo” que se va de madredegradar modelo / tools
Mescalendario o rolling 30 dunit economics del plancorte duro + aviso a billing

El techo es el precio. OpenAI mira tracked spend, no RPM. Cloudflare: spend limits track actual dollar cost. Si el plan es $29 y el margen de tokens $8, el diario del tenant no puede ser “hasta que pegue el 429 del proveedor”.

Fórmula mínima, sin inventar cents:

cost = (inTokens / 1e6) * inputUsdPerM + (outTokens / 1e6) * outputUsdPerM;

Guarda inTokens/outTokens de la respuesta, no un estimado previo. El estimado sirve para reservar el techo de minuto (fail-closed si no cabe); el real asienta día y mes. Cloudflare documenta eventual consistency: un burst concurrente puede pasarse un pelo. Tu reserva evita ese pelo en el tenant caro.

De dónde sale el tenant

Nunca del prompt. Nunca de metadata.user_id que el cliente manda sin firmar.

  1. El webhook / API valida la sesión (JWT, firma Slack, secret_token de Telegram).
  2. El tenant_id sale de esa identidad: org, workspace, bot_id+from.id mapeado a cuenta.
  3. Ese id viaja en el span, en la clave de cuota y —si usas AI Gateway— en metadata server-side.

Cloudflare, con Access, inyecta cf.user_id desde el JWT; service-token requests do not include cf.user_id. Si tu agente habla con un token de servicio, pones el tenant después de auth. Si lo pone el modelo (“el usuario dijo ser acme”), el techo no existe.

Escalera antes del corte

El 429 es el último peldaño. El plan $29 merece un agente más barato, no un silencio.

PeldañoQué cambiaCuándo
0Modelo y tools del planuso < 50 % del techo diario
1Cache exacto + semántico umbral alto50–80 %
2Modelo más barato; tools de efecto en cola80–100 %
3Solo lectura: buscar, no enviar, no pagar100 % día
4429 tenant_quota_exceeded + CTA de upgrade100 % mes o minuto

El peldaño 1 es cache semántico. El 2 declara el cambio (“modo ahorro”). El 4 no llama al proveedor. OpenAI: Don't retry quota, billing, or other errors that require you to take action. Anthropic, al cap de tier, el 429 no trae retry-after.

Escalera de degradación de un agente por tenant antes de cortar con 429

Distingue códigos. Mezclarlos convierte un tope de tenant en un retry storm:

SeñalCódigo típicoRetryQuién actúa
Rate limit transitorioOpenAI 429 + Retry-After; Anthropic retry-aftersí, con toperuntime
Ramp / slow_downOpenAI 429 slow_downsí, más lentoruntime
Spend hard limit proveedorOpenAI organization_spend_limit_exceeded / project_spend_limit_exceedednohumano / billing
Cap de tier Anthropicenforced_spend_limit_reachednohumano
Spend limit que pusiste (Anthropic)HTTP 400 invalid_request_errornohumano
Tu cuota de tenanttenant_quota_exceeded (tuyo)noupgrade o espera ventana

El kill switch sigue existiendo: si todos los tenants disparan a la vez y el org hard limit de OpenAI está a punto, apagas el webhook. La cuota por tenant evita llegar ahí por un solo cliente.

Implementación mínima (un store, tres contadores)

Un store atómico quota:{tenant}:{window} basta: memoria+TTL en un proceso, Redis INCRBYFLOAT+EXPIRE en varios.

type Window = "minute" | "day" | "month";

async function reserve(tenant: string, estimateUsd: number, limits: Record<Window, number>) {
  for (const w of ["minute", "day", "month"] as const) {
    const used = await getUsed(tenant, w);
    if (used + estimateUsd > limits[w]) {
      const err = new Error("tenant_quota_exceeded");
      (err as Error & { window: Window }).window = w;
      throw err;
    }
  }
  await addUsed(tenant, "minute", estimateUsd);
}

async function settle(tenant: string, actualUsd: number, estimateUsd: number) {
  await addUsed(tenant, "day", actualUsd);
  await addUsed(tenant, "month", actualUsd);
  await addUsed(tenant, "minute", actualUsd - estimateUsd); // corrige la reserva
}

Reglas que evitan el teatro:

  • Reservar antes de fetch al modelo. Si el minuto no cabe, ni abras el socket.
  • Asentar después, con tokens reales. Si la llamada falló, no asientes costo de output.
  • Fail-closed si el store está caído: un agente sin cuota es una tarjeta abierta.
  • No uses setTimeout para “resetear el día”. La clave lleva la fecha UTC (quota:acme:day:2026-09-06).
  • El techo del plan vive en billing, no en un const del repo. Staging replica los mismos nombres con montos de juguete.

Tres contadores por tenant — minuto, día y mes — asentando costo real después de la llamada

Encaje con el proveedor, sin delegarle el tenant

Usa el proveedor como red de seguridad de la org, no como cuota de producto:

  1. OpenAI: spend alert antes del hard limit de org/proyecto. El hard limit es el fusible de la empresa, no el del plan Pro.
  2. Anthropic: spend limit propio por debajo del cap del tier; el 400 de “specified API usage limits” es más claro que esperar el 429 de enforced_spend_limit_reached.
  3. Cloudflare AI Gateway: regla split by value en tenant_id inyectado server-side. Máximo 20 reglas por gateway: no intentes un techo por modelo × tenant × ambiente ahí; deja minuto/día/mes en tu store y el gateway como tope grueso.
  4. Headers x-ratelimit-remaining-* alimentan observabilidad, no la cuota del tenant. Remaining tokens de la org no es remaining de Ana.

Checklist

  • tenant_id sale de auth, no del body ni del modelo.
  • Tres ventanas: minuto, día, mes. Precios del modelo en config versionada.
  • Reserva con estimado; asiento con tokens reales.
  • Escalera 50 / 80 / 100 documentada en el mensaje al usuario.
  • tenant_quota_exceeded distinto de rate_limit_error. Cero retry.
  • Store caído → fail-closed, no fail-open.
  • Hard limit de OpenAI/Anthropic/CF por encima de la suma de tenants, como fusible.
  • Ensayo: un tenant al 100 % diario no tumba a los demás.

FAQ

¿Por qué no basta el spend limit de OpenAI por proyecto? Porque un proyecto suele ser “prod”, no “cliente 4821”. El 429 project_spend_limit_exceeded apaga a todos.

¿Puedo usar solo requests/día? No. Una tool de visión o un max_tokens alto rompe el unit economics sin tocar el RPM.

¿El techo de minuto no es un rate limit? Mide dólares, no llamadas. 1 request de $0.40 y 40 de $0.01 no son el mismo minuto.

¿Qué hago en el 80 %? Degrada. Cache, modelo barato, tools de efecto en cola. El corte es el mes o el minuto, no el primer susto.

¿Y si el proveedor está eventually consistent? Reserva local. Cloudflare lo admite: el request actual se registra después. Tu reserve() es la consistencia que te importa.

La cuota entra después de auth y tools: instalar un agente. En seguridad, coste y operación conviven kill switch, breaker y esta cuota: tres palancas, ningún techo “el de la org”.