
Le burn silencieux : un agent fantôme a tourné 4 jours avant que je le voie
Pourquoi un crash bruyant est plus facile à attraper qu'un crash discret
Un agent LLM que j'avais oublié a relancé 44 fois la même requête en 96 heures. Aucune alerte, aucun signal. Voici l'autopsie, le coût dans les timelines alternatives, et pourquoi le burn silencieux est plus dur à détecter que celui de 3h du matin.
Token burn rate in a zombie retry loop
Cumulative spend ($) over time - alert threshold shown in red
Une fenêtre PowerShell que j'avais réduite il y a 5 jours. Une ligne orange en haut. Un timestamp avec un hostname que je n'utilise plus, parce que j'avais renommé ma machine de dev entre-temps.
C'est comme ça que j'ai découvert qu'un agent LLM local tournait en boucle de retry depuis quatre jours pleins. Quarante-quatre timeouts identiques, déclenchés par le heartbeat interne de l'agent toutes les 30 minutes, dans un contexte 32K qui débordait à chaque tentative. Le gap médian entre deux échecs : 1800 secondes, pile. Pas un humain. Une boucle.
Si cette boucle avait pointé vers une API payante au lieu d'un modèle local, j'écrirais un autre post. Avec un plus gros chiffre.
L'autopsie
L'agent tournait sur ma propre machine, en appelant un modèle 32B local via Ollama. Toutes les 30 minutes, un heartbeat partait. Toutes les 30 minutes, il envoyait un prompt qui saturait le contexte. Toutes les 30 minutes, le modèle expirait avant de générer la moindre réponse utile. L'agent loggait une erreur interne et abandonnait. Puis attendait 30 minutes. Puis recommençait.
Voici le pattern d'échec extrait des logs :
| Métrique | Valeur |
|---|---|
| Total de timeouts | 44 |
| Durée totale | 96 heures (4 jours) |
| Gap médian entre événements | 1800 s, pile |
| Générations réussies | 0 |
| Tokens envoyés au modèle | ~1,4 M (prefill input, zéro output) |
| Cron jobs configurés | 0 |
| Connexions clientes actives | 0 |
| Tâches tracées dans la DB des runs | 0 |
Zéro connexion, zéro run réussi, zéro tâche. L'agent exécutait un rituel privé pour lui-même. Quarante-quatre fois.
Le coût qu'il aurait eu sur une API
Les mêmes 44 appels - même taille de prompt, même pattern de retry - aux prix typiques de 2026, sans optimisation :
| Backend | Prix input | Coût sur 4 jours |
|---|---|---|
| Petit modèle bon marché (~$1/M input) | $1,40 | ~$1,43 |
| Mid-tier (~$3/M input) | $4,20 | ~$4,28 |
| Top-tier (~$15/M input) | $21,00 | ~$21,38 |
Maintenant, active le prompt caching natif côté provider :
| Backend | Sans cache | Cache natif (~80% hit) |
|---|---|---|
| Mid-tier | $4,28 | ~$1,20 |
| Top-tier | $21,38 | ~$5,80 |
Et maintenant, active le semantic cache au niveau gateway. C'est là que le calcul devient intéressant. Les 44 retries étaient quasi-identiques : le même heartbeat envoyant le même prompt toutes les 30 minutes. Un semantic cache devant le provider renvoie la première réponse à tous les appels suivants qui matchent, sans jamais aller jusqu'au modèle :
| Setup | Coût mid-tier sur 4 jours |
|---|---|
| API directe, sans cache | $4,28 |
| API directe + cache natif | $1,20 |
| Semantic cache gateway (1 vrai call + 43 cachés) | ~$0,15 |
Soit 96% de réduction par rapport au setup direct et naïf. Sur un workload qui, par définition, était une boucle serrée sur le même prompt - exactement le workload pour lequel le semantic cache a été pensé.
Maintenant, imagine cette boucle non pas 4 jours sur un modèle local gratuit, mais 4 mois sur une API payante. Les 96% deviennent la différence entre une erreur à $20 et une erreur à $500.
Le burn bruyant est facile. Le burn silencieux, c'est le vrai problème.
On a déjà raconté la nuit où un agent a brûlé $200 en 3 heures. C'est la version bruyante. Le seuil provider se déclenche, ton téléphone vibre, tu te connectes en SSH, tu tues le process, tu encaisses le coût.
La version silencieuse est plus dure.
Le burn silencieux ne déclenche jamais de seuil. Le burn rate horaire reste faible - quelques centimes par heure, max. Aucune alerte ne part. Le provider voit du trafic normal. Tes dashboards (si tu ne regardes que les totaux) affichent une ligne plate. Le seul signal que quelque chose cloche, c'est la forme des requêtes : même prompt, même taille de tokens approximative, même timeout, sur une cadence parfaite de 30 minutes, pendant des semaines.
Un humain qui regarde un ou deux de ces appels isolément ne lèverait pas le drapeau. Sauf que :
- Personne ne regarde les appels un par un en prod. Personne ne lit la 14 000ᵉ requête du jour.
- Les résumés quotidiens et hebdomadaires moyennent ça en bruit. Une boucle qui coûte $0,40/jour disparaît dans la variance du trafic légitime à côté.
- L'anomalie, c'est l'absence de variation. La détection d'anomalie standard cherche des spikes. Le burn silencieux, c'est l'inverse : le même tambourin ennuyeux, à l'infini.
L'agent à $200 à 3h du matin m'a réveillé. Le zombie de 4 jours, non, et ne m'aurait jamais réveillé.
Ce que la couche infra peut faire que ton code ne peut pas
Le fix, ce n'est pas plus de logique de retry au niveau applicatif. Le fix, c'est une couche qui voit la forme du trafic et qui réagit aux formes suspectes, pas juste aux totaux suspects.
Trois patterns marchent, et ils s'empilent :
Déduplication sémantique. Identifier le fragment d'intention du prompt et le comparer aux appels récents. Si le même intent se répète de manière suspecte, la gateway court-circuite avec la réponse cachée, rejette, ou alerte. La difficulté est de bien isoler l'intention réelle : trop large, tu rates les matches ; trop étroit, chaque variation brise la détection. Bien calibrée, cette couche fait passer le cache hit rate sur les workloads chargés en retries de "occasionnel" à "presque tout".
Alertes loop-detection. Indépendamment du cache, déclenche une alerte quand la même empreinte se répète en cadence régulière. Sur mon agent, les hashes identiques à intervalles fixes auraient alerté très tôt - dans l'heure ou deux qui suivent le démarrage, pas après 4 jours.
Audits silencieux planifiés. Un cron hebdomadaire qui t'envoie par mail le top 10 des empreintes de requêtes les plus répétées sur toutes tes clés. L'abus bruyant apparaît dans le graphe des spikes. L'abus silencieux apparaît ici. Personne ne regarde jamais le bas de la distribution parce qu'il n'y a rien qui brûle.
Rien de tout ça n'a besoin de vivre dans ton appli. Sa place est dans la couche infra, devant chaque requête, avec une visibilité globale sur toutes les clés et tous les clients.
Pour finir
J'ai fermé la fenêtre PowerShell après avoir tué le process. L'agent est en pause, pas supprimé ; il reviendra quand j'aurai un vrai job pour lui. Mais j'ai ajouté une note au runbook : si ce truc redémarre, il passe par un check d'empreinte avec alerte à chaque match, avant d'avoir le droit d'appeler quoi que ce soit.
Le burn bruyant t'achète une leçon à $200 et un coup de fil à 3h du matin. Le burn silencieux ne t'achète même pas ça. Il tourne, c'est tout.
Si tu déploies des agents LLM en prod en 2026, c'est la version bruyante qu'on raconte sur les blogs. La silencieuse, c'est celle qui devrait te faire peur.
Aucune carte bancaire requise
À lire aussi : On a vu un agent IA brûler $200 à 3h du matin, Prompt Caching Hit Rate.
Cet article t'a servi ?
Commentaires
Sois le premier à commenter.