Tetti di spesa per utente su un coach AI: BudgetGuard di Zeno
Zeno mette un tetto alla spesa AI: 50 USD al giorno in totale, 2 per utente, gate pre-chiamata e 429 a tetto. Il meccanismo, dal diff del 16 aprile 2026.
In breve. Tre giorni dopo l'approvazione sull'App Store, Zeno si dà un tetto di spesa: 50 USD al giorno in totale, 2 per utente, con un cancello che blocca le chiamate prima che partano. Il meccanismo sta in un commit del 16 aprile 2026: due cap, una cache da 60 secondi, un 429 che dice «riprova domani».
Il quadro generale — prezzo, quota e tetti come tre righe del conto mensile — sta nell'hub quanto costa davvero un agente AI al mese; tre giorni prima di questo commit, tre rifiuti dell'App Store e l'approvazione del 13 aprile. Qui solo il meccanismo: come il tetto è scritto nel codice, niente altro.
Due cap, un cancello
Il commit 71b77a4 del 16 aprile 2026 introduce due costanti: DAILY_COST_USD_GLOBAL a 50.0 e DAILY_COST_USD_PER_USER a 2.0, dollari per giorno solare UTC. Un cap a 0 spegne il suo cancello: utile in sviluppo, o quando basta un tetto solo.
Il cancello si chiama BudgetGuard e gira prima della chiamata, in cima a reasoning, structured_output e generate_embedding. Controlla i due cap in ordine — prima il globale, poi l'utente: se il globale è saltato, il singolo utente non si guarda nemmeno. Superato uno dei due, alza BudgetExceeded e la chiamata non parte.
La cache da 60 secondi
La spesa del giorno è una SUM(cost_usd) sulla tabella token_usage, avvolta in una cache Redis da 60 secondi (_CACHE_TTL_SEC = 60). Il globale usa una chiave condivisa, l'utente una chiave sua. Sessanta secondi di cache vogliono dire una cosa precisa: nel peggiore dei casi si sfora di circa un minuto di chiamate a picco. È il prezzo scelto per tenere il cancello fuori dall'hot path.
E se cache o database si rompono? Il cancello si apre: «a broken cache must never block the app's main flow». Far passare le chiamate quando il contatore è cieco è una scelta scritta nel codice, non una dimenticanza — meglio un giorno senza tetto che un'app ferma per un Redis down.
Il breakdown: reasoning e cached separati
Prima di questo commit, Zeno schiacciava tutto in due totali: input e output. Il commit aggiunge due colonne dedicate su token_usage — reasoning_tokens e cached_input_tokens — perché le API Responses li riportano già, e sommarli voleva dire perdere due informazioni: quanto si spende in reasoning e quanto la prompt cache sta funzionando.
Il cached si paga al 10% del listino input (CACHED_INPUT_DISCOUNT = 0.10), come costante e non per modello, così resta in pari se i prezzi cambiano. Il costo di una chiamata diventa: input non-cached a prezzo pieno, cached al 10%, output e reasoning per le loro tariffe.
Il 429 che dice riprova domani
A tetto raggiunto, il sistema non risponde 500: un handler dedicato traduce BudgetExceeded in 429, prima dell'handler generico, con messaggio «Daily cost cap reached. Try again tomorrow». Client e retry logic lo trattano come un rate limit, non come un guasto del server.
È un dettaglio che cambia il comportamento di chi chiama: un 500 dice «riprova subito, forse passa», un 429 con «domani» dice «smetti di spendere, torna quando il contatore si azzera».
La riconciliazione
L'ultimo pezzo confronta i conti interni con quelli del fornitore: reconcile_usage.py legge token_usage e lo mette accanto a /v1/organization/usage di OpenAI, segnalando ogni modello con scarto sopra il 5%. Il tetto guarda avanti e blocca, la riconciliazione guarda indietro e verifica che il contatore dica il vero.
Fatti e limiti
FACT. Il 16 aprile 2026 Zeno introduce BudgetGuard con tetto globale di 50 USD al giorno e tetto di 2 USD al giorno per utente, in dollari per giorno solare UTC, con 0 che spegne il cancello (71b77a4).
FACT. Il cancello gira pre-chiamata in cima a reasoning, structured_output e generate_embedding, controlla prima il globale poi l'utente, e a tetto alza BudgetExceeded senza far partire la chiamata (71b77a4).
FACT. La spesa è una SUM(cost_usd) su token_usage con cache Redis da 60 secondi, chiave condivisa per il globale; su errori DB o Redis il cancello si apre (71b77a4).
FACT. Il commit aggiunge le colonne reasoning_tokens e cached_input_tokens, prezza il cached al 10% del listino input e traduce BudgetExceeded in 429 con «Daily cost cap reached. Try again tomorrow» (71b77a4).
FACT. reconcile_usage.py confronta token_usage con /v1/organization/usage e segnala scarti sopra il 5% per modello (71b77a4).
INTERPRETATION. La spesa massima si decide prima, non dopo: due numeri in un file di config valgono più di un alert a cose fatte. È la regola che applico da aprile 2026, non un dato misurato.
Limiti: un prodotto, un commit, un giorno. I 50 e 2 USD sono valori di configurazione di un coach AI, non un benchmark. Niente dati di spesa degli utenti, niente misure di quanti tetti sono mai scattati: questo pezzo descrive il meccanismo, non i suoi effetti.
Domande frequenti
Quanto può spendere un utente di Zeno al giorno?
Al massimo 2 USD al giorno, dentro un tetto globale di 50 USD al giorno per tutto il prodotto. Oltre il tetto le chiamate non partono e il sistema risponde 429 fino al giorno dopo (UTC).
Perché il cancello sta prima della chiamata e non dopo?
Perché dopo è un contatore, prima è un tetto. BudgetGuard gira in cima a ogni entry point OpenAI e blocca la chiamata quando la spesa del giorno ha raggiunto il cap: a cose fatte puoi solo registrare il danno.
Cosa succede se il contatore si rompe?
Le chiamate passano. Su errori di database o Redis il cancello si apre di proposito: una cache rotta non deve mai fermare il flusso principale dell'app. Il rischio accettato è un giorno senza tetto, non un'app ferma.