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.

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:
| Techo | Ventana | Para qué | Si se pasa |
|---|---|---|---|
| Minuto | sliding 60 s | loops, fan-out, tool storms | 429 inmediato, sin retry |
| Día | UTC 00:00 → 00:00 | un tenant “productivo” que se va de madre | degradar modelo / tools |
| Mes | calendario o rolling 30 d | unit economics del plan | corte 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.
- El webhook / API valida la sesión (JWT, firma Slack,
secret_tokende Telegram). - El
tenant_idsale de esa identidad: org, workspace,bot_id+from.idmapeado a cuenta. - 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, tú 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ño | Qué cambia | Cuándo |
|---|---|---|
| 0 | Modelo y tools del plan | uso < 50 % del techo diario |
| 1 | Cache exacto + semántico umbral alto | 50–80 % |
| 2 | Modelo más barato; tools de efecto en cola | 80–100 % |
| 3 | Solo lectura: buscar, no enviar, no pagar | 100 % día |
| 4 | 429 tenant_quota_exceeded + CTA de upgrade | 100 % 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.

Distingue códigos. Mezclarlos convierte un tope de tenant en un retry storm:
| Señal | Código típico | Retry | Quién actúa |
|---|---|---|---|
| Rate limit transitorio | OpenAI 429 + Retry-After; Anthropic retry-after | sí, con tope | runtime |
| Ramp / slow_down | OpenAI 429 slow_down | sí, más lento | runtime |
| Spend hard limit proveedor | OpenAI organization_spend_limit_exceeded / project_spend_limit_exceeded | no | humano / billing |
| Cap de tier Anthropic | enforced_spend_limit_reached | no | humano |
| Spend limit que tú pusiste (Anthropic) | HTTP 400 invalid_request_error | no | humano |
| Tu cuota de tenant | tenant_quota_exceeded (tuyo) | no | upgrade 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
fetchal 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
setTimeoutpara “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.

Encaje con el proveedor, sin delegarle el tenant
Usa el proveedor como red de seguridad de la org, no como cuota de producto:
- OpenAI: spend alert antes del hard limit de org/proyecto. El hard limit es el fusible de la empresa, no el del plan Pro.
- 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. - Cloudflare AI Gateway: regla split by value en
tenant_idinyectado 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. - Headers
x-ratelimit-remaining-*alimentan observabilidad, no la cuota del tenant. Remaining tokens de la org no es remaining de Ana.
Checklist
-
tenant_idsale 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_exceededdistinto derate_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”.
Lecturas relacionadas
Sigue explorando AgentOps y otras piezas para builders.

Cache semántico en agentes LLM: exact-match primero, umbral alto, clave con tenant

Agentes en nombre del usuario: OAuth, consentimiento y tokens que no viven en el prompt

LLM-as-judge para agentes: rúbrica, schema y calibración (el juez no es la verdad)
