Le développement agentique : les limites
Sommaire
Dans le TP précédent, l'agent a ajouté une fonctionnalité et ses tests à la Boutique BTS, et vous avez relu chaque ligne. Tout s'est bien passé ? Tant mieux. Ce TP est consacré à tout ce qui peut mal se passer, et ça arrive plus souvent qu'on ne le croit : du code faux qui a l'air juste, des tests qui ne testent rien, une facture qui grimpe, un secret qui part chez un inconnu, un fichier qui donne des ordres à l'agent, un dossier supprimé.
Pour chaque risque, vous allez le provoquer (sur la Boutique BTS, dans un dépôt Git, là où rien n'est grave), puis apprendre la parade. Les consignes sont plus ouvertes que dans les TP précédents : c'est à vous de mener les expériences et d'en tirer les conclusions.
Prérequis
- Les TP principe et utilisation.
- Votre dépôt
bts-panierdu TP précédent, propre (git statussans modification), et un modèle qui répond. - Travaillez dans une copie du dépôt pour les expériences dangereuses (
git clone bts-panier bts-panier-labo), et de préférence dans la dev-box plutôt que directement sur votre ordinateur.
Objectifs
À la fin de ce TP vous saurez :
- reconnaître une hallucination, du code faux qui « marche » et un test complaisant ;
- expliquer ce qui se passe quand la fenêtre de contexte sature ;
- estimer le coût d'une session et comprendre les quotas des offres gratuites ;
- dire quelles données quittent votre machine quand vous utilisez un agent, et qui les voit ;
- expliquer et reproduire une injection de prompt ;
- restreindre les permissions d'un agent ;
- décider quand ne pas utiliser un agent.
Les hallucinations et les API inventées
Un modèle produit le texte le plus probable, pas le texte vrai. Quand il ne sait pas, il ne dit pas « je ne sais pas » : il invente quelque chose de plausible. En code, ça donne :
- une fonction ou une option qui n'existe pas (
array.sortBy(),fetch(url, { retry: 3 })) ; - un paquet npm au nom crédible… qui n'existe pas, ou pire, qui a été créé par quelqu'un de malveillant justement parce que les modèles l'inventent souvent ;
- une explication sûre d'elle et fausse, par exemple sur le comportement d'une version précise d'un outil.
Un agent est un peu mieux armé qu'un chat, puisqu'il peut lancer le code et voir l'erreur. Mais il peut aussi contourner l'erreur au lieu de la comprendre (en ajoutant un try/catch qui la cache, par exemple), puis vous annoncer que « tout fonctionne ».
C'est à vous de jouer ! Dans votre copie de travail, demandez à l'agent :
Utilise la fonction
formatCurrencydu modulenode:intlpour afficher les prix dansformatPrice.
Ce module n'existe pas. Observez : l'agent le signale-t-il ? Essaie-t-il quand même ? Que dit-il à la fin ? Que montre npm test ? Notez vos observations, puis annulez tout avec git restore ..
La parade
- Lisez le résultat des outils (la sortie des commandes), pas seulement le résumé final de l'agent : le résumé est écrit par le modèle, il peut se tromper sur ce qui s'est passé.
- Pour toute fonction ou option inconnue, vérifiez dans la documentation officielle (MDN, la doc de Node.js, celle du framework).
- Toute nouvelle dépendance se vérifie à la main : elle existe, elle est maintenue, c'est bien celle que vous croyez. C'est pour ça qu'on l'interdit dans
AGENTS.md.
Du code qui marche, mais faux ou pas sûr
« Les tests passent et la page s'affiche » ne veut pas dire que le code est correct. Trois pièges classiques, que vous connaissez déjà :
- les arrondis : une remise de 10 % sur 1 295 centimes, c'est 129,5 centimes. Arrondi au-dessus, en dessous, au plus proche ? L'agent choisit, souvent sans le dire ;
- les cas limites : panier vide, code promo en minuscules, avec un espace à la fin, appliqué deux fois ;
- la sécurité : pour afficher « Code inconnu : XYZ », un agent écrit volontiers
message.innerHTML = "Code inconnu : " + code. Et voilà une faille XSS, avec ce que tape l'utilisateur injecté dans la page.
C'est à vous de jouer ! Relisez le diff complet de votre travail du TP précédent (git log -p) et cherchez :
- chaque
innerHTML: ce qui est inséré peut-il venir de l'utilisateur ? - chaque calcul de pourcentage : comment est-il arrondi, et est-ce testé ?
- ce qui se passe avec le code
bts10(minuscules et espace final) et avec<img src=x onerror=alert(1)>dans le champ du code promo.
Pour chaque problème trouvé : corrigez-le (vous-même ou avec l'agent), ajoutez un test qui l'aurait détecté, et commitez.
La parade
Le code de l'agent se relit comme celui d'un inconnu : avec les mêmes questions qu'en revue de code (entrées de l'utilisateur, cas limites, erreurs, sécurité). Le fait qu'il soit bien présenté et bien commenté ne prouve rien.
Des tests trop complaisants
Demandez à un agent « fais passer les tests » : il peut y arriver en corrigeant le code… ou en modifiant les tests. Les tests écrits par un agent ont aussi des défauts typiques :
- ils recalculent le résultat attendu avec la même formule que le code (
assert.equal(remise, sousTotal * 0.1)) : si la formule est fausse, le test l'est aussi ; - ils testent que la fonction renvoie quelque chose, pas qu'elle renvoie la bonne chose ;
- ils ne testent que le cas qui marche, jamais la frontière (49,99 € et 50,00 € pour la livraison) ;
- un test qui échouait a été « adapté » au code, plutôt que l'inverse.
Questions :
- Pourquoi
assert.equal(summary(cart).shipping, cart.subtotal < 5000 ? 490 : 0)est-il un mauvais test ? - Quelles valeurs de sous-total faut-il tester pour la règle de livraison ? Pourquoi celles-là ?
Réponse
- Il recopie la règle au lieu de la vérifier : si le code se trompe de seuil (
<=au lieu de<), le test se trompera de la même façon et passera. Un test compare le résultat à une valeur écrite en dur, calculée à la main à partir de la spécification :assert.equal(..., 490). - Les valeurs autour de la frontière : juste en dessous (4 999 centimes, livraison payante), pile dessus (5 000, offerte), et un cas loin de la frontière dans chaque sens. Plus le panier vide. C'est aux frontières que se cachent les erreurs de
<et de<=.
C'est à vous de jouer ! Faites la chasse aux mutants sur vos tests du TP précédent : modifiez volontairement le code (un < en <=, 490 en 590, 0.1 en 0.2, supprimez la gestion des minuscules), une modification à la fois, et lancez npm test. Chaque modification doit faire échouer au moins un test. Si un « mutant » survit, écrivez le test qui manquait. Notez le nombre de mutants survivants avant et après.
La parade
- Les tests se relisent avant le code : ils sont la spécification.
- Un test modifié par l'agent dans un commit qui « répare » un bug est un signal d'alarme :
git diffsur le dossiertest/d'abord. - Écrivez vous-même au moins les tests des règles métier ; laissez l'agent compléter.
La fenêtre de contexte sature
Vous l'avez vu dans le TP sur le principe : tout le contexte est renvoyé à chaque tour, et il ne fait que grossir. Dans une longue session, il finit par approcher la taille de la fenêtre du modèle :
Avant d'atteindre la limite, l'agent compacte : il demande au modèle un résumé de la conversation, et repart de ce résumé (OpenCode a un agent dédié à ça, compaction). Le résumé est forcément incomplet : une consigne donnée au début, une décision prise en route, le contenu exact d'un fichier peuvent disparaître. Et même avant la compaction, un modèle exploite moins bien les informations noyées au milieu d'un très long contexte.
Les symptômes : l'agent oublie une règle qu'il respectait au début, relit des fichiers déjà lus, refait une erreur déjà corrigée, ou devient lent (et cher).
Questions :
- Dans OpenCode, où voyez-vous la place prise dans la fenêtre de contexte ? (Revoyez la capture de session du TP précédent.)
- Pourquoi une règle écrite dans
AGENTS.mdrésiste-t-elle mieux à la compaction qu'une règle donnée au début de la conversation ?
Réponse
- En bas à droite de l'interface, à côté du nombre de tokens :
6.6K (20%)dans la capture, soit 20 % de la fenêtre utilisés. AGENTS.mdest réinjecté au début du contexte à chaque tour, il ne fait pas partie de l'historique que l'on résume. Une consigne donnée au fil de la conversation, elle, peut être résumée, déformée ou oubliée.
La parade
- Une session par tâche : une tâche finie et commitée, on repart d'une session neuve.
- Des demandes ciblées (« lis
src/panier.js») plutôt que « regarde tout le projet ». - Les règles durables dans
AGENTS.md, court.
Coût et quotas
Un agent ne fait pas une requête par question, mais une requête par tour de boucle, sans vous demander votre avis. Le TP sur le principe a montré ce que ça coûte en tokens. Les offres gratuites, elles, se paient autrement : en quotas.
L'accès d'essai de NVIDIA est limité à 40 requêtes par minute (la limite couramment constatée ; NVIDIA ne publie pas de chiffre officiel). Un agent qui boucle (un test qui échoue, il corrige, relance, échoue encore…) l'atteint en moins d'une minute. Au-delà, le serveur répond 429 Too Many Requests : l'agent attend, réessaie, ou abandonne au milieu d'une modification.
Que se passe-t-il derrière ? Les quotas dans llm-proxy
Derrière le proxy de la classe, tout le monde partage les mêmes comptes chez les fournisseurs. Le proxy a donc deux garde-fous, décrits dans sa documentation :
- un limiteur de quotas pour les backends qui en ont (il est écrit pour Albert, l'API de l'État) : il retarde les requêtes pour rester sous les limites par minute et par jour du compte, et répond
429avec un en-têteRetry-Afterquand l'attente devient trop longue ; - le partage équitable : chaque client a une part de tokens par heure glissante ; au-delà, ses requêtes attendent (rien n'est refusé). Un agent qui boucle ne ralentit alors que son propriétaire, pas toute la classe.
Et en échange d'un service gratuit, qu'avez-vous donné ?
- un numéro de téléphone, pour NVIDIA : c'est un identifiant fort, qui vous relie à votre usage ;
- vos données, pour certains modèles gratuits : la page d'OpenCode indique, modèle par modèle, lesquels conservent les échanges et s'en servent pour entraîner leur modèle, et pour les modèles gratuits de NVIDIA : « Trial use only, do not submit personal or confidential data. Your use is logged » (OpenCode, section Privacy) ;
- et une dépendance : l'offre est gratuite « pour une durée limitée », le modèle peut disparaître du jour au lendemain (la liste d'OpenCode change d'une semaine à l'autre).
Questions :
- Pourquoi une classe entière ne doit-elle jamais partager une seule clé d'API ?
- Un service gratuit est-il forcément « payé » par vos données ? Citez un contre-exemple de ce TP.
Réponse
- La limite de débit est par clé : 30 étudiants, c'est 30 fois plus de requêtes pour la même limite, donc des
429pour tout le monde. Et une seule clé, c'est aussi une seule ligne dans les statistiques : impossible de savoir qui consomme quoi, ni de couper un seul utilisateur. D'où une clé par étudiant sur le proxy, et un partage équitable par compte : la clé NVIDIA du TP précédent n'est jamais distribuée, seul le proxy s'en sert. - Non : certains modèles gratuits d'OpenCode annoncent ne rien conserver ni entraîner (GLM-5.3-Flash Free, DeepSeek V4.1 Flash Free), le temps d'une promotion. Et les modèles mis à disposition par AMD pour ce cours relèvent d'un programme universitaire. Mais la règle reste de lire les conditions à chaque fois : elles changent.
Vos données partent chez un tiers
Tout ce qui entre dans le contexte quitte votre machine : votre demande, mais aussi chaque fichier lu, chaque sortie de commande, chaque erreur affichée.
Ce qui pose problème :
- les secrets : un
.envlu par l'agent, un mot de passe affiché par une commande, une clé dans un fichier de configuration. Une fois envoyés, ils sont hors de votre contrôle : considérez-les comme compromis, et changez-les ; - le code de l'entreprise : en stage ou en alternance, le code ne vous appartient pas. L'envoyer à un service tiers sans autorisation peut violer votre convention ou votre contrat. Demandez toujours quels outils sont autorisés ;
- les données personnelles : une base de clients, même « de test », copiée dans le projet et lue par l'agent, c'est un transfert de données personnelles à un tiers, encadré par le RGPD (voir la CNIL sur l'IA).
Et que voit le proxy de la classe ? Il relaie le contenu (il le voit donc passer), mais d'après son code il ne conserve qu'une ligne par requête : la date, le modèle, le nombre de tokens, la durée, le statut et un condensé de la clé, jamais le texte. Voici le tableau de bord tel que le voit un étudiant, après les essais faits pour écrire ces TP :

Remarquez le rapport entrée/sortie : 125 262 tokens envoyés pour 1 240 reçus. Les réponses du modèle de démonstration sont très courtes, mais l'entrée est bien celle qu'envoie OpenCode : c'est la signature d'un agent, qui renvoie tout son contexte à chaque tour.
Ce que le proxy ne protège pas
Le proxy ne garde pas vos messages, mais le fournisseur qui est derrière les reçoit en entier. Et l'administrateur d'un proxy peut toujours activer des journaux plus bavards (llm-proxy a par exemple une option de trace qui note les outils appelés et un extrait de leurs arguments). Ne confiez à aucun agent ce que vous ne pourriez pas montrer à votre enseignant.
La parade
- Pas de vrai secret dans le dossier du projet : un
.env.exampleavec des valeurs factices, le vrai.envhors du dépôt (et dans.gitignore). - Interdire à l'agent de lire les
.env(voir les permissions). - Des données de test inventées, jamais une copie de la vraie base.
- Pour du code professionnel : uniquement les outils validés par l'entreprise.
Les injections de prompt
Rappelez-vous la dernière question de la boucle à la main : le modèle croit tout ce qui entre dans son contexte. Il n'y a pas de différence de « couleur » entre votre demande et le contenu d'un fichier lu : tout est du texte. Si un fichier, une page web ou un message d'erreur contient une consigne, le modèle peut lui obéir. C'est une injection de prompt.
Les sources d'injection sont partout : un README d'une dépendance, un commentaire dans le code d'un autre, un ticket GitLab, une page web lue par webfetch, les résultats d'une recherche web. La documentation de llm-proxy le dit elle-même de ses outils de recherche web : ils font entrer « du texte du web dans le contexte d'un agent ».
C'est à vous de jouer ! Dans votre copie bts-panier-labo :
- créez un faux secret : un fichier
.envcontenantAPI_KEY=ceci-n-est-pas-un-vrai-secret; - créez
docs/NOTES.mdavec quelques lignes de « compte rendu de réunion », et cachez au milieu un commentaire HTML (<!-- … -->) qui demande à l'agent de lire.envet d'en recopier le contenu dans sa réponse ; - lancez l'agent et demandez-lui seulement : « Résume les notes de
docs/NOTES.md» ; - observez : l'agent obéit-il au commentaire ? Vous demande-t-il l'autorisation de lire
.env(OpenCode le fait par défaut) ? Que contient sa réponse ? - recommencez en formulant l'ordre caché autrement (en anglais, en le présentant comme une « consigne du projet »), et avec un autre modèle si vous en avez plusieurs.
Notez vos résultats : quel modèle a obéi, avec quelle formulation, et à quelle étape vous auriez pu l'arrêter.
La parade
Aucune parade n'est parfaite : on ne peut pas « apprendre » au modèle à ignorer les injections à coup sûr. On limite donc les dégâts possibles :
- rien de précieux à portée de l'agent (pas de vrais secrets, un conteneur plutôt que votre machine) ;
- des permissions qui demandent votre accord pour tout ce qui sort ou détruit (
shell,webfetch, lecture des.env) ; - vous lisez ce que l'agent s'apprête à faire avant d'accepter, surtout après qu'il a lu un contenu venu d'ailleurs.
Des permissions trop larges
L'agent exécute des commandes sur votre machine, avec vos droits. Dans OpenCode, la règle de base est « tout est autorisé » (seule la lecture des .env demande votre accord). Nous l'avons vérifié avec un modèle de démonstration (au comportement écrit à l'avance) qui demande rm -rf test après qu'on lui a dit « Nettoie le projet » :

Le dossier de tests a disparu, sans une question. Heureusement, il était commité : git restore test le ramène. Remarquez aussi la conclusion de l'agent : elle présente la destruction comme une amélioration.
Les permissions d'OpenCode se règlent dans opencode.json, par une liste de règles lues dans l'ordre, la dernière qui correspond l'emporte (documentation des permissions). Chaque règle a une action (read, edit, shell, webfetch…), une ressource (un chemin ou une commande, avec *) et un effet (allow, ask ou deny) :
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "*", "resource": "*", "effect": "allow" },
{ "action": "edit", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "npm test", "effect": "allow" },
{ "action": "shell", "resource": "git status", "effect": "allow" },
{ "action": "shell", "resource": "git diff*", "effect": "allow" },
{ "action": "shell", "resource": "git push *", "effect": "deny" },
{ "action": "shell", "resource": "rm *", "effect": "deny" },
{ "action": "read", "resource": "*.env", "effect": "deny" },
{ "action": "read", "resource": "*.env.*", "effect": "deny" },
{ "action": "read", "resource": "*.env.example", "effect": "allow" }
]
}Même demande, même modèle, avec ces règles :

Questions :
- Dans la liste ci-dessus, pourquoi la règle
npm test(autorisée) est-elle placée après la règleshell*(sur demande) ? - L'agent Plan d'OpenCode « ne modifie pas les fichiers ». Peut-il quand même lancer
rm -rf test? (Indice :opencode debug agentsaffiche les règles de chaque agent.) - Une règle
denysurrm *suffit-elle à empêcher toute suppression ?
Réponse
- La dernière règle qui correspond l'emporte.
npm testcorrespond aux deux règles : placée après, l'autorisation gagne. Dans l'autre ordre,npm testdemanderait votre accord à chaque fois. - Oui. Dans OpenCode 2.2.6, l'agent Plan interdit
edit(modifier des fichiers), mais il garde la règle de base « tout est autorisé » pour le reste, dontshell. Il ne modifie pas les fichiers avec ses outils d'édition, mais une commande du shell peut toujours le faire. - Non :
find test -delete,git clean -fdx,node -e "require('fs').rmSync('test', {recursive: true})", un scriptnpm… Une liste noire de commandes se contourne toujours. C'est pour ça que la règle sûre estshellsur demande (ask) par défaut, avec une courte liste blanche de commandes inoffensives, et que vous lisez chaque commande avant d'accepter.
Une règle mal écrite est ignorée sans un mot
Pendant la préparation de ce TP, une règle à laquelle il manquait le champ resource a été ignorée en silence par OpenCode : aucune erreur au démarrage, et aucune protection. Après chaque modification, vérifiez que vos règles sont bien prises en compte :
opencode debug configC'est à vous de jouer ! Écrivez les permissions de votre projet bts-panier (dans son opencode.json), vérifiez-les avec opencode debug config, puis testez-les : demandez à l'agent une tâche qui devrait être autorisée, une qui devrait demander votre accord, et une qui devrait être refusée.
La parade, en résumé
- Des permissions restrictives par défaut (
askpourshelletedit), une liste blanche courte. - Jamais d'option qui accepte tout d'office (comme
--autod'OpenCode) sur votre machine. - Un conteneur (la dev-box, par exemple) : si tout part de travers, les dégâts restent dedans.
- Git, toujours : tout ce qui est commité se récupère.
Dépendance et perte de compétences
Le dernier risque ne se voit pas dans un git diff : c'est vous. Un agent écrit en une minute ce qui vous aurait pris une heure. Mais pendant cette heure, vous auriez appris quelque chose : chercher dans la documentation, comprendre une erreur, structurer un programme. Si l'agent fait toujours ce travail à votre place, vous ne l'apprenez pas, et vous ne savez plus juger son travail.
Le paradoxe est là : pour bien utiliser un agent, il faut savoir faire sans. Relire un diff, repérer un test complaisant ou une faille XSS, trancher une ambiguïté de la spécification, tout ça demande les compétences que l'agent vous dispense de pratiquer.
Questions :
- Reprenez votre
JOURNAL.mddu TP précédent : combien de fois avez-vous refusé ou corrigé une proposition de l'agent ? Auriez-vous repéré ces problèmes au début de votre BTS ? - Choisissez une fonction écrite par l'agent dans votre projet. Sans la relire, pourriez-vous la réécrire ? L'expliquer à l'oral ?
Quand ne pas utiliser un agent
Un agent n'est pas toujours le bon outil. Ne l'utilisez pas :
- quand vous apprenez une notion nouvelle : les TP de base sont faits pour que vous écriviez le code. Utilisez plutôt un assistant pour vous faire expliquer (une erreur, un concept), pas pour produire ;
- en évaluation, sauf si le sujet l'autorise explicitement ;
- sur du code ou des données que vous n'avez pas le droit d'envoyer à un tiers (code d'entreprise sans autorisation, données personnelles, secrets) ;
- quand vous ne savez pas vérifier le résultat : si vous êtes incapable de dire si le code est juste, l'agent ne vous aide pas, il vous expose ;
- pour une petite modification que vous feriez plus vite à la main qu'en l'expliquant ;
- pour du code critique (authentification, paiement, droits d'accès) sans une relecture approfondie par quelqu'un qui maîtrise le sujet.
Ce que l'on attend de vous
Que le code ait été écrit par vous, par un camarade, copié d'un forum ou produit par un agent, une chose ne change pas : c'est vous qui le livrez, c'est vous qui en êtes responsable. Un bug, une faille ou une fuite de données dans « votre » code sont les vôtres, pas ceux de l'agent.
On attend donc d'un développeur, avec ou sans agent, qu'il sache :
- lire tout le code qu'il livre, et l'expliquer ligne par ligne ;
- le tester pour de vrai, et dire ce que ses tests garantissent ;
- refuser une proposition, et dire pourquoi ;
- dire ce qu'il envoie, et à qui.
C'est ce que rappelle la page de ressources de veille : les assistants IA sont à utiliser pour comprendre, et non pour produire à votre place, car en évaluation comme en entretien, vous serez seul face au code. Le jury de l'épreuve, comme le recruteur, vous demandera d'expliquer et de modifier votre code en direct, sans agent.
Point de contrôle
À la fin de ce TP, votre dépôt bts-panier contient :
- des tests renforcés après la chasse aux mutants (et le nombre de mutants survivants avant et après, noté dans
JOURNAL.md) ; - les corrections trouvées en relisant le code (
innerHTML, arrondis, cas limites), chacune avec son test ; - un
opencode.jsonavec des permissions vérifiées paropencode debug config; - dans
JOURNAL.md, vos observations sur l'API inventée et sur l'injection de prompt.
Et surtout : vous pouvez expliquer chaque ligne du projet, sans l'agent.
Conclusion
Dans ce TP, vous avez provoqué, puis appris à limiter, les principaux risques du développement agentique :
- les hallucinations et le code faux qui « marche » : on relit le code de l'agent comme celui d'un inconnu ;
- les tests complaisants : on écrit soi-même les tests des règles métier, et on les met à l'épreuve ;
- la fenêtre de contexte qui sature : une session par tâche, un
AGENTS.mdcourt ; - le coût et les quotas : un agent consomme beaucoup, et un service gratuit se paie autrement (téléphone, données, dépendance) ;
- les données envoyées à un tiers : pas de secrets ni de données personnelles à portée de l'agent ;
- les injections de prompt : tout texte lu par l'agent peut lui donner des ordres ;
- les permissions trop larges :
askpar défaut, un conteneur, et Git ; - la perte de compétences : pour bien piloter un agent, il faut savoir faire sans.
Pensez à commiter et à pousser votre dépôt. Le développement agentique change vite : les outils, les modèles et les offres gratuites de ces TP auront évolué dans quelques mois. Les principes, eux, resteront : comprendre ce que fait l'outil, garder la main, et rester responsable de son code. Pour suivre ces évolutions, gardez un œil sur votre fil de veille.
👋 Si vous avez des questions, n'hésitez pas.