Quand l'optimisation de tokens coûte plus cher que les tokens eux-mêmes

Outils


Quand on utilise un agent IA pour programmer, avec Claude Code, OpenCode ou Cursor par exemple, chaque token transmis dans le contexte est facturé. Un git log verbeux, un npm install de 500 lignes, un pytest -v qui liste 600 tests : tout cela entre dans la fenêtre de contexte et augmente le coût. L’idée de comprimer ces sorties avant qu’elles n’atteignent le LLM est donc séduisante. C’est exactement ce que fait RTK.

Mais notre analyse de sessions réelles montre un résultat contre-intuitif : lorsqu’une information utile disparaît, les tentatives supplémentaires de l’agent peuvent annuler les économies attendues.

TL;DR : pour les lecteurs et les agents LLM

  • Cette analyse a été réalisée avec RTK 0.30.0.
  • La compression est utile pour les sorties longues, répétitives et structurellement stables.
  • Pour diagnostiquer une erreur ou réutiliser une valeur exacte, comme un traceback, un token, un identifiant ou une réponse JSON, préférer la commande sans filtre.
  • Si un résultat RTK est vide, ambigu ou inattendu, relancer une fois la commande sans RTK avant de modifier le code.
  • Mesurer le nombre total de tours de l’agent, pas seulement les tokens économisés sur chaque commande.

RTK : l’idée est bonne

RTK (Run Tool Kit) est un proxy CLI qui intercepte les commandes système et résume leur sortie. Au lieu de renvoyer 200 lignes de logs pytest, il produit une ligne : ✓ Pytest: 17 passed. Au lieu d’un ls -la verbeux, il affiche un arbre compact avec les tailles.

Sur le papier, c’est exactement ce qu’il faut. Un output pytest de 8 000 caractères (~2 000 tokens) est remplacé par 30 caractères (~8 tokens). Multiplié par des centaines de commandes, l’économie est réelle.

Le cas réel : une boucle de 28 messages

Nous avons analysé une session de programmation en binôme avec un outil d’observation de sessions IA. Les chiffres :

  • 848 messages, 103M tokens input, coût estimé : $12.90
  • 261 commandes bash, dont 206 via RTK (79%)
  • L’agent utilisait rtk pytest pour quasiment chaque exécution de tests

Tout se passe bien jusqu’à la fin de la session. L’agent lance :

rtk pytest tests/backend/unit/ -x -q

RTK répond :

Pytest: No tests collected

L’agent retente. Même résultat. Il retente. Encore. Quatre fois de suite. Puis il change de flag : -v au lieu de -q. Et là :

✓ Pytest: 561 passed, 1 failed

Les tests étaient bien collectés et exécutés depuis le début. L’option -q modifie le format de sortie de pytest, et RTK interprétait mal ce format en résumant “No tests collected”.

Un second problème apparaît lorsque l’agent passe aux tests d’intégration :

rtk pytest tests/backend/integration/test_api.py -x -v

Encore “No tests collected”. Sauf que cette fois, c’est un vrai problème : une ImportError dans le code Python empêche la collecte des tests. Mais RTK résume le traceback complet en une seule ligne anodine. L’agent ne voit jamais l’erreur réelle.

L’agent enchaîne alors 17 tentatives : changer les flags, cibler des classes spécifiques, ajouter --tb=short, utiliser --co, sans obtenir le traceback qui lui indiquerait quoi corriger.

Ce n’est que lorsque l’agent abandonne RTK et lance directement python -c "from myapp.routes...", qu’il voit enfin :

ImportError: cannot import name 'router' from 'myapp.routes.session_routes'

Les chiffres du paradoxe

Voici l’estimation initiale :

Métrique Valeur
Contexte moyen pendant la boucle ~163 000 tokens
Messages dans la boucle 28
Tokens input facturés pendant la boucle ~4.6M
Équivalent au plein tarif d’entrée ($3/M) ~$14

En face, les économies de RTK sur toute la session (pas juste la boucle) : en comprimant les outputs de 261 commandes, RTK a probablement économisé $2 à $3 de tokens.

Les ~$14 sont un équivalent au plein tarif, pas le coût réellement facturé : ce calcul applique $3/M à tous les tokens sans distinguer les lectures de cache. L’estimation détaillée plus loin ramène les 24 tours observés à environ $1.68, contre $0.21 pour un parcours direct de trois tours. Le surcoût attribuable à cette boucle est donc plus proche de $1.47. Ce cas ne démontre pas à lui seul un ratio de 5:1 ; il montre qu’une seule perte de signal peut absorber une part importante des économies attendues.

Le second cas : RTK + curl, la perte de données utiles

Si le cas pytest illustre la perte de signal d’erreur, le cas curl montre une autre limite : la perte de données utiles pour l’étape suivante.

Nous avons analysé une seconde session (348 messages, 41M tokens input) où l’agent développait une API REST. Naturellement, il testait ses endpoints avec curl. Et naturellement, il préfixait avec rtk :

rtk curl -s http://localhost:8000/api/auth/login -X POST -d '{"email":"test@test.com","password":"pass"}'

La réponse brute du serveur était :

{
  "access_token": "<example-access-token>",
  "token_type": "bearer",
  "user": { "id": 42, "email": "test@test.com" }
}

Ce que RTK renvoyait à l’agent :

{access_token: string, token_type: string, user: {id: number, email: string}}

Le JWT est remplacé par string. Les valeurs réelles disparaissent. RTK a transformé une réponse en un schéma de type, utile pour de la documentation, mais pas pour du débogage.

Le JWT fantôme

La limite devient concrète quand l’agent doit utiliser le token pour authentifier les requêtes suivantes. Il obtient un token via /api/auth/login, mais RTK le masque :

access_***REDACTED***

L’agent n’a plus le token. Il ne peut pas construire le header Authorization: Bearer <token>. Donc il rappelle /api/auth/login. RTK rédige encore. L’agent retente.

22 appels à /api/auth/login dans la session, dont 15 inutiles, causés par RTK qui détruisait le token à chaque fois.

Un diagnostic correct, mais temporaire

À mi-parcours, l’agent écrit :

“rtk truncates the response, let me try without it”

Il passe à curl direct. Ça marche. Il obtient le token, l’utilise, avance.

Mais quelques échanges plus tard, il recommence à utiliser rtk curl. Il avait oublié sa propre conclusion. C’est un trait caractéristique des agents dans de longues sessions : les découvertes locales ne survivent pas aux compactions de contexte. La session avait 6 compactions, des moments où le fournisseur résume l’historique pour rester dans la fenêtre de contexte. À chaque compaction, les leçons apprises disparaissent. La plus longue séquence sans compaction faisait 53 messages, largement assez pour que la leçon “ne pas utiliser RTK avec curl” soit évincée du contexte.

Bilan chiffré

Métrique Valeur
Commandes curl totales 41
Via RTK 14 (34%)
Directes 27 (66%)
Appels à /api/auth/login 22
Dont inutiles (causés par RTK) 15
Compactions de contexte 6

Le paradoxe est le même qu’avec pytest, mais le mécanisme diffère. Avec pytest, RTK masquait les erreurs. Avec curl, RTK supprimait des données de travail, comme les tokens JWT, les identifiants et les valeurs de réponse dont l’agent avait besoin pour enchaîner les requêtes.

Tests reproductibles : une taxonomie des bugs

Les deux cas précédents ne sont pas des anecdotes. Nous avons systématiquement testé RTK v0.30.0 sur neuf commandes différentes pour cartographier précisément ce qui casse et ce qui fonctionne.

🔴 Masquage d’erreurs

rtk pytest -q (flag -q) : quand des tests passent, RTK résume correctement avec -v. Mais avec -q, le format de sortie change et RTK affiche “No tests collected” alors que les tests passent :

# Direct : résultat réel
$ pytest tests/ -q
2 passed, 1 failed in 0.03s

# Via RTK : le signal utile disparaît
$ rtk pytest tests/ -q
Pytest: No tests collected

L’agent voit “No tests collected” et retente. Il ne sait pas que les tests ont déjà passé.

rtk pytest + ImportError : quand un module Python a une erreur d’import, pytest affiche un traceback complet. RTK le résume en “No tests collected”, avec -q et -v :

# Direct : le traceback est visible
$ pytest tests/ -v
ImportError while importing test module '/tmp/test_api.py'
ModuleNotFoundError: No module named 'myapp.routes'

# Via RTK : l'erreur disparaît
$ rtk pytest tests/ -v
Pytest: No tests collected

L’agent ne peut pas diagnostiquer l’erreur et poursuit ses tentatives sans comprendre qu’il s’agit d’un problème d’import.

🔴 Perte de données utiles

rtk curl (réponses JSON) : RTK remplace les valeurs JSON par leurs types :

# Direct : données utilisables
$ curl -s https://httpbin.org/get
{"headers": {"Host": "httpbin.org", "User-Agent": "curl/8.7.1"}, "origin": "1.2.3.4"}

# Via RTK : schéma de types, pas des données
$ rtk curl -s https://httpbin.org/get
{headers: {Host: string, User-Agent: string}, origin: string}

rtk json (fichiers JSON) : même comportement que curl. Un fichier JSON avec "token": "abc123" devient token: string.

rtk env (variables d’environnement) : masquage incohérent. AWS_SECRET est partiellement masqué (AK****LE), mais API_KEY et DATABASE_URL disparaissent complètement de la sortie, même pas masqués, simplement absents.

🟢 Fonctionne correctement

Commande Comportement
rtk go test (échecs) Message d’erreur préservé intégralement
rtk go build (erreur de compilation) Erreur préservée
rtk gh api (avec --jq) JSON passé correctement
rtk read (fichiers) Contenu complet préservé

La tendance observée est claire : RTK fonctionne bien pour les outils dont la sortie est structurellement stable (Go, gh). Il devient moins fiable lorsque la sortie dépend du format (pytest -q vs -v), contient des données de travail (curl, JSON), ou subit un masquage trop agressif (env).

Une tendance récurrente : 168 séquences de tentatives sur 40 sessions

Les deux études de cas et les tests reproductibles pourraient encore passer pour des cas isolés. Pour vérifier, nous avons analysé 40 sessions de programmation assistée réelles via l’API d’un outil d’observation de sessions IA.

Les chiffres bruts

Métrique Valeur
Sessions analysées 40
Messages totaux 17 000+
Commandes bash 20 593
Séquences de tentatives détectées 168

Une “séquence de tentatives” est définie comme la même commande (ou une variante mineure) exécutée 3 fois ou plus consécutivement sans changement de résultat.

Les commandes les plus touchées

Commande RTK Sessions affectées Plus longue séquence
rtk pytest ~20 sessions 26 tentatives consécutives
rtk tsc --noEmit ~12 sessions 3× la même commande
rtk git commit ~12 sessions jusqu’à 8×
rtk grep ~5 sessions 5×

La plus longue séquence compte 26 tentatives consécutives de rtk pytest dans une seule session. L’agent relançait la même commande avec des variations mineures d’options, sans obtenir l’information dont il avait besoin.

Ce n’est pas un incident isolé

168 séquences sur 40 sessions, c’est 4.2 séquences par session en moyenne. Chaque séquence, dans un contexte de 100K à 200K tokens, peut ajouter entre $1 et $5 de tokens sans faire progresser la tâche. En cumulé sur ces 40 sessions, le surcoût associé est estimé entre $200 et $800 pour des sessions qui totalisent environ $400 à $500 de coût normal.

Les mêmes tendances apparaissent de façon récurrente : commandes similaires, erreurs masquées et nouvelles tentatives. Elles apparaissent parfois aux mêmes indices de messages dans différentes sessions, car certaines sessions partagent un historique commun issu d’une branche ou d’un fork.

Pourquoi chaque tentative supplémentaire coûte cher

Pour comprendre le paradoxe, il faut comprendre comment on paie un agent IA. L’intuition “un message = un appel API” est fausse. Quand vous écrivez “lance les tests et corrige les erreurs”, voici ce qui se passe :

Vous: "Lance les tests et corrige les erreurs"

  → Appel API #1: [tout le contexte] → Claude réfléchit → tool_call: bash("pytest")
  → Appel API #2: [contexte + résultat pytest] → Claude réfléchit → tool_call: edit(fichier)
  → Appel API #3: [contexte + résultat edit] → Claude réfléchit → tool_call: bash("pytest")
  → Appel API #4: [contexte + résultat pytest] → Claude réfléchit → "C'est corrigé !"

Chaque tool call est un appel API séparé. À chaque appel, tout le contexte est renvoyé : tous les messages précédents, tous les résultats de tool calls et tout le system prompt. On paie à chaque tour :

  • Input tokens : tout le contexte accumulé. Avec le prompt caching, les tokens déjà vus coûtent ~$0.30/M au lieu de $3/M (Sonnet). Mais les tokens nouveaux à chaque tour (le résultat du dernier tool call) sont à plein prix.
  • Tokens de sortie : ce que le modèle génère (raisonnement + appel d’outil). Dans le modèle tarifaire observé, ils sont facturés $15/M et ne sont pas mis en cache.

Même avec le cache, les tokens cachés ne sont pas gratuits : ils coûtent 10% du prix normal. Et le contexte grandit à chaque tour.

Le coût concret d’une séquence de tentatives

Prenons un contexte de 160K tokens (typique en milieu de session) :

Tour normal Tour supplémentaire
Input (caché) 160K × $0.30/M = $0.048 Idem
Input (nouveau) ~2K × $3/M = $0.006 Idem
Output ~1K × $15/M = $0.015 Idem
Total ~$0.07 ~$0.07
Progression ✅ avance ❌ zéro

$0.07 par tour sans progression. Mais une nouvelle tentative ne se limite pas à relancer la commande. L’agent utilise aussi des tours pour analyser le résultat, essayer des variantes et chercher l’origine du problème. Une relance visible peut ainsi générer 3 à 5 appels API.

Voici le scénario réel quand rtk pytest retourne “No tests collected” alors que les tests passent :

Tour 1:  rtk pytest -q → "No tests collected"           $0.07
Tour 2:  Claude réfléchit, essaie -v                      $0.07
Tour 3:  rtk pytest -v → "No tests collected" (ImportError masquée)
Tour 4:  Claude essaie --tb=short                          $0.07
Tour 5:  rtk pytest --tb=short → "No tests collected"     $0.07
  ... 17 tours plus tard ...
Tour 22: Claude abandonne RTK → pytest direct → TRACEBACK  $0.07
Tour 23: Claude lit l'erreur → edit le fichier             $0.07
Tour 24: pytest → ça passe                                 $0.07

Coût total : ~24 × $0.07 = $1.68. Sans RTK, le traceback serait apparu au tour 1, corrigé au tour 3. Coût : $0.21. Ratio : 8:1.

Et cela ne concerne qu’une seule boucle. Avec 168 séquences observées sur 40 sessions et un contexte qui dépasse parfois 200K tokens en fin de session, le surcoût peut s’accumuler rapidement.

La formule du vrai coût

Le coût d’un agent n’est pas tokens × prix. C’est :

coût = Σ (taille_contexte_au_tour_i) × prix_par_token
       pour chaque tour i

RTK optimise taille_contexte en comprimant les outputs. Mais quand il masque une erreur, il multiplie le nombre de tours. Et dans un contexte large, le nombre de tours domine. C’est le paradoxe de la compression agressive : plus le contexte est grand, plus chaque tour gaspillé coûte cher, et plus il est critique que l’agent voie les erreurs en clair.

Il n’existe pas de solution unique

La tentation est forte de chercher la solution : un meilleur algorithme de compression, un fallback automatique, un mode intelligent qui saurait quand comprimer et quand pas. Mais les données montrent que le problème n’a pas de solution unique.

Chaque piste a ses limites :

Catégoriser les commandes (“ne jamais comprimer pytest, curl, xcodebuild”) fonctionne pour les cas connus. Mais les bugs de RTK touchent aussi grep, npm, git, des commandes qu’on voudrait comprimer. Et chaque nouvel outil, chaque nouveau format de sortie, est un nouveau cas potentiel de faux positif.

Le repli automatique (si RTK détecte “No tests collected”, relancer sans filtrage) ne fonctionne que pour les erreurs connues. Le problème des ImportErrors masquées, par exemple, ne retournait pas “No tests collected” de manière systématique : RTK pouvait produire une sortie vide ou un résumé tronqué. Généraliser cette détection sans introduire de faux négatifs reste difficile.

Le prompt caching atténue le coût par tour mais ne change pas la dynamique fondamentale. Même à 10% du prix, 24 tours gaspillés dans un contexte de 200K tokens coûtent plus cher que 3 tours à plein prix.

Les instructions dans AGENTS.md sont la solution la plus robuste, mais elles ont aussi leurs limites. L’agent peut les suivre, mais dans une session longue avec des compactions de contexte, même les instructions système peuvent être résumées ou évincrées. Et maintenir une liste exhaustive de commandes à exclure pour chaque projet est un travail continu.

Ce qui fonctionne en pratique, c’est la combinaison de plusieurs approches complémentaires :

1. Des règles claires dans AGENTS.md. Deux instructions simples couvrent 80% des cas :

1. Éviter RTK pour : pytest, curl, xcodebuild, python -c
2. Si une commande via RTK échoue ou retourne un résultat inattendu,
   la relancer sans RTK pour obtenir la sortie complète.

2. De l’observation post-session. Un outil d’analyse peut identifier les séquences de tentatives et quantifier leur coût après coup. L’objectif n’est pas de corriger en temps réel, mais de rendre ces comportements visibles afin d’ajuster les règles et les outils. C’est en analysant 40 sessions qu’on a observé que rtk pytest retournait “No tests collected” 51% du temps.

3. De la discipline. Ne pas ajouter un proxy de compression par défaut sur toutes les commandes. Commencer sans, mesurer, et n’activer la compression que pour les commandes où les données montrent un bénéfice net.

Le problème de fond, c’est que l’optimisation des tokens et la fiabilité de l’agent sont en tension. La compression peut supprimer une information potentiellement utile. Parfois cette information est redondante, comme la 200e ligne d’un npm install. Parfois, c’est le traceback qui aurait permis de résoudre le problème en un tour. Il est difficile de le savoir à l’avance.

Conclusion

L’optimisation de tokens est un réflexe sain quand on travaille avec des agents IA. Mais comme toute optimisation, elle obéit à la loi des rendements décroissants. Dans ce cas, les rendements deviennent négatifs bien plus vite qu’on ne le pense.

Les observations convergent : deux études de cas, neuf scénarios de test et 40 sessions analysées :

  • Masquage d’erreurs (pytest) : 51% des exécutions via RTK retournent “No tests collected”. L’agent ne peut plus diagnostiquer.
  • Perte de données utiles (curl, JSON) : les valeurs sont remplacées par des schémas de types. L’agent ne peut plus enchaîner les étapes.
  • À l’échelle : 168 séquences de tentatives, 100 abandons de RTK par l’agent lui-même, 204 expirations et 363 sorties vides.

Les compactions de contexte peuvent renforcer ce phénomène. Nos données contiennent 9 moments où l’agent écrit “let me try without rtk”, puis revient plus tard à la commande filtrée. La mémoire de travail d’un agent est volatile ; des instructions persistantes dans AGENTS.md peuvent aider à conserver cette règle.

Il n’existe pas de solution unique. La compression des sorties n’est ni bonne ni mauvaise : c’est un compromis. Elle demande de mesurer l’information perdue autant que les tokens économisés. Sur RTK 0.30.0, les données de 40 sessions, 20 000 commandes et 168 séquences montrent que le bénéfice net pouvait devenir négatif.

La vraie question n’est pas “faut-il comprimer les outputs”. C’est : avez-vous les outils pour savoir quand la compression vous coûte plus cher que les tokens qu’elle économise ?

Mise à jour

Cet article est un retour d’expérience sur RTK 0.30.0, publié le 6 avril 2026. Une vérification distincte sur RTK 0.45.0 a été publiée le 24 août 2026. Elle détaille ce qui a été corrigé et le cas pytest qui reste à surveiller : RTK 0.45 : ce qui a changé depuis notre analyse.