L’idée en 30 secondes
Rate limiting est un contrôle runtime qui limite la fréquence des appels externes de l’agent pour éviter les pics et les tempêtes de retries en production.
Quand c’est nécessaire : quand un agent appelle souvent des model/tool API, a une logique de retry, et fonctionne sous charge de pointe.
Le problème
Sans rate limiting, un service instable crée vite une chaîne : retry → appel → encore retry.
En démo, on le voit peu. En production, ce comportement crée des vagues de 429/5xx, des files d’attente et de la latence.
Le pire : l’incident s’amplifie tout seul :
- un utilisateur trop actif consomme les quotas
- un tenant "étouffe" les autres
- un pic global casse les dépendances pour tout le monde
Et chaque minute sans limite de fréquence ajoute des retries, des files et des délais, jusqu’à ce que le système se DDoS lui-même.
Analogie : c’est comme une rampe de régulation à l’entrée d’une autoroute. Sans dosage du flux, même une bonne route se bloque en quelques minutes.
La solution
La solution est d’ajouter une couche policy rate-limit centralisée dans la runtime et le tool gateway.
Chaque appel externe de l’agent est vérifié avec les limites per_user, per_tenant, global et burst_tokens.
La policy renvoie une décision technique : allow ou stop avec raison explicite :
rate_limited_userrate_limited_tenantrate_limited_globalburst_limited
Quand stop est renvoyé, la runtime retourne retry_after_ms au client et n’exécute pas l’appel.
C’est une couche système séparée, pas une partie du prompt ni de la logique du modèle.
Rate limiting ≠ step limits
Ce sont deux niveaux de contrôle différents :
- Rate limiting limite la fréquence des appels externes.
- Step limits limitent la longueur et le comportement de la runtime loop.
L’un sans l’autre ne suffit pas :
- sans rate limiting, les API externes tombent sous les pics et tempêtes de retries
- sans step limits, un run peut boucler longtemps même avec une fréquence modérée
Exemple :
- rate limiting : pas plus de
per_user=6appels par 10 secondes - step limits :
max_steps=18,max_repeat_action=3
Composants du contrôle rate limiting
Ces composants travaillent ensemble sur chaque appel externe de l’agent.
| Composant | Ce qu’il contrôle | Mécaniques clés | Pourquoi |
|---|---|---|---|
| Per-user limit | Comportement d’un utilisateur | per_user quotasliding window | Empêche un utilisateur de consommer toute la capacité |
| Per-tenant limit | Charge d’un tenant | per_tenant quotatenant-scoped keys | Isole les pics entre clients |
| Global limit | Charge totale du système | global capshared limiter | Protège les dépendances externes contre les pics massifs |
| Burst control | Courts "spikes" de trafic | token bucket refill rate | Amortit les sauts immédiats sans arrêt complet |
| Rate-limit observability | Visibilité des décisions policy | audit logs alerts sur les stop spikes | Ne limite pas directement les appels, mais aide à trouver vite la source d’un pic |
Exemple d’alerte :
Slack: 🛑 Support-Agent hit rate_limited_tenant. retry_after=1200ms, tenant=t_42.
À quoi ça ressemble dans l’architecture
La couche policy rate-limit se place entre runtime et model/tool API externes.
Chaque décision (allow ou stop) est enregistrée dans l’audit log.
Chaque appel externe de l’agent passe par ce flow avant exécution : la runtime n’exécute pas directement l’appel, elle demande d’abord une décision à la policy.
Résumé du flow :
- Runtime forme un appel externe de l’agent
- Policy vérifie
per_user,per_tenant,globaletburst_tokens allow→ l’appel est exécutéstop→retry_after_mset réponse partielle sont renvoyés- les deux décisions sont écrites dans l’audit log
Exemple
Un agent de support traite beaucoup de requêtes en parallèle et retry crm.search plusieurs fois.
Avec rate limiting :
per_user = 6 / 10sper_tenant = 120 / minglobal = 50 / sburst_tokens = 5
→ le pic est arrêté au niveau policy avant la chute des dépendances et des files.
Rate limiting stoppe l’incident juste avant l’appel externe, pas après une vague de 429.
En code, ça ressemble à ça
Le schéma simplifié ci-dessus montre le flow principal.
Point critique : le rate-limit check doit être O(1) et atomique (souvent via Redis/Lua ou équivalent), sinon il devient lui-même un goulot en pic.
Après stop(...), la runtime renvoie généralement une réponse partielle au client avec raison explicite et retry_after_ms.
Exemple de configuration rate limit :
rate_limits:
per_user_10s: 6
per_tenant_min: 120
global_rps: 50
burst_tokens: 5
refill_per_second: 2
action = planner.next(state)
action_key = make_action_key(action.name, action.args)
if not action.is_external_call():
# execute_local — helper conditionnel pour actions locales sans API externe.
# Decision.allow — helper conditionnel pour garder un modèle unique outcome/reason.
local_result = execute_local(action)
local_decision = Decision.allow(reason=None)
audit.log(
run_id,
decision=local_decision.outcome,
reason=local_decision.reason,
scope="local",
action=action.name,
action_key=action_key,
result=local_result.status,
)
return local_result
decision = rate_limit.check(
user_id=state.user_id,
tenant_id=state.tenant_id,
action=action.name,
now_ms=clock.now_ms(),
)
if decision.outcome == "stop":
audit.log(
run_id,
decision=decision.outcome,
reason=decision.reason,
scope=decision.scope,
retry_after_ms=decision.retry_after_ms,
action=action.name,
action_key=action_key,
)
alerts.notify_if_needed(run_id, decision.reason, scope=decision.scope)
return stop(decision.reason, retry_after_ms=decision.retry_after_ms)
result = executor.execute(action)
audit.log(
run_id,
decision=decision.outcome,
reason=decision.reason,
scope=decision.scope,
action=action.name,
action_key=action_key,
result=result.status,
)
return result
À quoi ça ressemble pendant l’exécution
Scénario 1 : arrêt sur limite tenant
- Runtime forme l’appel externe
crm.search. - Policy détecte dépassement du quota
per_tenant. - Décision :
stop (reason=rate_limited_tenant). - Runtime renvoie
retry_after_ms. - L’appel n’est pas exécuté, l’événement est écrit dans l’audit log.
Scénario 2 : burst-spike
- Plusieurs runs créent un court pic d’appels au même moment.
- Policy épuise
burst_tokens. - Décision :
stop (reason=burst_limited). - Une partie des appels est rejetée avec
retry_after_ms. - Le système reste stable sans cascade-failure.
Scénario 3 : exécution normale
- Runtime forme un appel externe.
- Policy vérifie les limites : tout est dans les bornes.
- Décision :
allow. - L’appel est exécuté.
- Décision et résultat sont écrits dans l’audit log.
Erreurs typiques
- ne mettre qu’une limite globale sans isolation
per_user/per_tenant - ne pas renvoyer
retry_afteren cas destop - retry sans backoff ni jitter
- vérifier le rate limit dans une seule couche (seulement runtime ou seulement gateway)
- ne pas logger les décisions stop (
reason,scope,retry_after_ms) - pas d’alerting sur les pics
rate_limited_*
Résultat : le système semble contrôlé, mais il se dégrade vite sous un vrai pic.
Auto-vérification
Vérification rapide du rate limiting avant lancement en production :
Progression: 0/8
⚠ Les contrôles de governance de base manquent
Avant la production, il faut au minimum le contrôle d'accès, des limites, des audit logs et un arrêt d'urgence.
FAQ
Q : Avec quelles limites commencer ?
A : Minimum : per_user, per_tenant, global + un petit contrôle de burst. Ajuste ensuite selon les événements stop réels.
Q : Si l’API externe renvoie déjà 429, faut-il notre rate limiting ?
A : Oui. Le rate limiting interne protège la runtime avant le 429 externe et fournit des stop reasons, retry_after et audit contrôlés.
Q : Au dépassement de limite, mieux vaut stop ou queue ?
A : En sync-run, stop + retry_after est généralement préférable. En async-pipeline, on peut ajouter une queue, mais toujours avec limites explicites et timeout.
Q : Rate limiting remplace budget controls ?
A : Non. Rate limiting contrôle la fréquence d’appels, budget controls contrôle la dépense totale du run.
Q : Où stocker les compteurs ?
A : Dans un stockage partagé low-latency avec opérations atomiques (souvent Redis). Sans cela, les limites deviennent incohérentes entre instances.
Où se place Rate Limiting dans le système
Rate limiting est une des couches d’Agent Governance. Avec RBAC, budgets, step limits, approval et audit, il forme un système unifié de contrôle d’exécution.
Pages liées
Suite du sujet :
- Vue d’ensemble Agent Governance — modèle global de contrôle des agents en production.
- Budget Controls — comment limiter la dépense totale d’un run.
- Step limits — comment stopper les loops au niveau runtime.
- Kill switch — comment arrêter des actions d’urgence sans release.
- Audit logs pour agents — comment analyser les stop reasons et les pics de charge.