NOPE LinkedIn

Catégories:
IA

Comment on l'a construit : développement piloté par sous-agents et revue adversariale

Comment on l'a construit : développement piloté par sous-agents et revue adversariale image

Rubrique: IA Tag: IA Tag: génie logiciel Tag: test-first Tag: code review Tag: DevSecOps

🇬🇧 English version 💻 Code source (AGPL) : github.com/patlegu/cyber-agent-engine

La rigueur comme livrable

Les cinq articles prĂ©cĂ©dents de cette sĂ©rie ont dĂ©crit des frontières techniques : jetons plutĂ´t que valeurs rĂ©elles, politique fail-closed, topologie hors-boĂ®te, audit bornĂ© et packaging reproductible. Ce dernier article change d’angle. Il ne parle pas de ce que cyber-agent-engine fait, mais de comment il a Ă©tĂ© construit — et pourquoi ce comment fait partie du livrable au mĂŞme titre que le code.

La thèse est simple Ă  Ă©noncer et plus difficile Ă  tenir : sur un projet oĂą un LLM finit par obtenir des droits d’exĂ©cution sur un pare-feu de production, la qualitĂ© ne peut pas ĂŞtre un rattrapage. Elle se prouve Ă  chaque Ă©tape, ou elle ne se prouve pas du tout. Ça s’est traduit par un choix structurant fait avant d’Ă©crire la première ligne de core/ : dĂ©couper le produit en sous-projets indĂ©pendants, chacun portĂ© par sa propre chaĂ®ne spec → plan → exĂ©cution, plutĂ´t que d’avancer par gros commits opportunistes sur un unique fil de dĂ©veloppement.

Concrètement, le dĂ©pĂ´t contient un dossier docs/superpowers/specs/ et son pendant docs/superpowers/plans/, oĂą chaque sous-projet laisse une trace Ă©crite avant d’ĂŞtre codĂ©. On y trouve, par exemple, 2026-07-22-coeur-confiance-surete-design.md (le socle dĂ©crit dans l’article 1 et 2 de cette sĂ©rie — politique, tokenisation, audit), 2026-07-22-portabilite-modeles-runtime-design.md et 2026-07-22-assemblage-runtime-design.md (articles 4 et 5 — sortir le LLM de la boĂ®te, assembler le runtime multi-serveur), ou encore 2026-07-23-durcissement-exploitation-design.md et 2026-07-23-ci-release-design.md, qui couvrent le durcissement opĂ©rationnel et la chaĂ®ne de release. Chaque design a son plan d’exĂ©cution correspondant dans plans/ — un document dĂ©coupĂ© en tâches numĂ©rotĂ©es, avec pour chacune la liste des fichiers touchĂ©s, les interfaces attendues et les commandes de vĂ©rification Ă  faire tourner avant de committer. Cette sĂ©rie d’articles elle-mĂŞme a suivi le mĂŞme circuit : 2026-07-23-vitrine-blog-series-design.md puis 2026-07-23-vitrine-blog-series-fr.md dans plans/. La mĂ©thode ne s’applique pas qu’au runtime ; elle s’applique aussi Ă  sa propre vitrine.

Ce dĂ©coupage en sous-projets n’est pas cosmĂ©tique. Il fixe une frontière de contexte : un sous-agent qui implĂ©mente la tâche 3 du plan « cĹ“ur de confiance » n’a pas besoin de connaĂ®tre le dĂ©tail de la chaĂ®ne CI ou du packaging Docker pour bien faire son travail — il a le plan de son sous-projet, la tâche courante, et le code dĂ©jĂ  en place. Ça rĂ©duit la dĂ©rive : moins de contexte Ă  porter, moins de raccourcis pris par fatigue de fenĂŞtre de contexte.

La boucle : implémenter, relire, corriger, relire encore

Le cĹ“ur de la mĂ©thode est une boucle rĂ©pĂ©tĂ©e Ă  chaque tâche d’un plan. Elle se lit ainsi :

%%{init: {"flowchart": {"htmlLabels": false}} }%% flowchart LR Plan["Plan par tâches"] --> Impl["Sous-agent implémenteur"] Impl --> Rev["Revue par tâche
spec + qualité"] Rev -->|findings| Fix["Sous-agent de correction"] Fix --> Rev Rev -->|clean| Next{"tâches restantes ?"} Next -->|oui| Impl Next -->|non| Final["Revue finale de branche
(adversariale)"] Final --> Merge["Merge + push"]

Chaque tâche du plan est confiĂ©e Ă  un sous-agent implĂ©menteur qui dĂ©marre sur un contexte frais — le plan, la tâche, le code existant, rien de plus. C’est dĂ©libĂ©rĂ© : un contexte qui s’accumule au fil de dizaines de tâches finit par contenir plus de bruit que de signal, et un agent fatiguĂ© de contexte a tendance Ă  couper des coins qu’un agent frais ne coupe pas.

Une fois la tâche implĂ©mentĂ©e, elle passe en revue — pas la revue finale, une revue par tâche, qui vĂ©rifie deux choses distinctes : la conformitĂ© au spec (est-ce que ce qui a Ă©tĂ© codĂ© correspond Ă  ce que le plan demandait, ni plus ni moins) et la qualitĂ© intrinsèque (nommage, complexitĂ©, gestion d’erreurs, tests). Quand cette revue remonte des findings, ils ne sont pas corrigĂ©s par l’agent qui vient d’Ă©crire le code — un sous-agent de correction dĂ©diĂ© reprend les findings un par un, applique les fixes, et le tout repasse en revue. La boucle se referme quand la revue ne trouve plus rien Ă  redire, et on passe Ă  la tâche suivante.

Cette sĂ©paration implĂ©menteur / relecteur / correcteur reprend un principe de revue de code classique — celui qui Ă©crit le code n’est pas le mieux placĂ© pour juger s’il est bon, parce qu’il a dĂ©jĂ  rationalisĂ© ses propres choix pendant qu’il Ă©crivait. AppliquĂ© Ă  des sous-agents, ce principe devient mĂ©canique plutĂ´t que disciplinĂ© : la revue est un rĂ´le distinct, convoquĂ© systĂ©matiquement, pas une Ă©tape qu’on peut sauter un jour de retard.

Une fois toutes les tâches d’un sous-projet closes, il reste une dernière Ă©tape avant merge : une revue finale de branche, volontairement adversariale. Elle ne relit pas tâche par tâche — elle prend la branche entière comme un tout et cherche activement ce qui a pu passer entre les mailles des revues locales : une rĂ©gression introduite par la tâche 7 sur un invariant posĂ© Ă  la tâche 2, une incohĂ©rence entre deux modules qui, pris isolĂ©ment, avaient l’air corrects. C’est la diffĂ©rence entre relire une lettre phrase par phrase et la relire en entier une fois terminĂ©e : les deux passes trouvent des choses diffĂ©rentes.

Test-first et garde-fous Ă  l’exĂ©cution

La boucle de revue attrape ce qu’un humain (ou un agent) sait chercher. Elle n’attrape pas structurellement les dĂ©rives qu’on ne pense pas Ă  vĂ©rifier Ă  chaque passage. C’est pour ça que le projet s’appuie aussi sur des tests d’enforcement — des tests qui ne valident pas un comportement mĂ©tier, mais une propriĂ©tĂ© structurelle du code, en la scannant directement via son arbre syntaxique (AST) plutĂ´t qu’en la devinant Ă  l’Ĺ“il.

Trois exemples concrets, tous dans tests/ :

tests/test_spdx_headers.py vĂ©rifie que chaque fichier source first-party (core/, coordinator/, agents/, clients/, server.py) porte bien l’en-tĂŞte SPDX-License-Identifier: AGPL-3.0-or-later dans ses trois premières lignes. Le test parcourt l’arborescence, collecte les fichiers manquants, et Ă©choue avec la liste prĂ©cise si un seul en manque — pas de vĂ©rification manuelle Ă  refaire Ă  chaque nouveau fichier créé par un sous-agent.

tests/test_runtime_messages_english.py est plus intĂ©ressant parce qu’il ne cherche pas une chaĂ®ne fixe mais un motif structurel. Le projet impose que les messages opĂ©rateur runtime (ce qui part dans un raise, dans un appel de logging, dans un print, ou dans les champs reason=/error= d’une rĂ©ponse) soient en anglais. PlutĂ´t que de grepper des mots français, le test parse chaque fichier en AST, parcourt les nĹ“uds Raise et les appels aux mĂ©thodes de logging (debug, info, warning, error, exception, etc.), et signale toute constante chaĂ®ne littĂ©rale contenant un caractère accentuĂ© français dans ces positions prĂ©cises. Le fichier documente lui-mĂŞme sa limite : le français sans accent (« timeout serveur ») n’est pas dĂ©tectĂ© — le test garde la rĂ©gression du cas courant, il ne remplace pas le sweep initial qui a assurĂ© la complĂ©tude. C’est un garde-fou assumĂ© comme partiel, pas comme une preuve totale.

tests/test_lint_surface_consistency.py protège contre un type de rĂ©gression plus insidieux : le drift silencieux de configuration. La liste des chemins couverts par ruff check apparaĂ®t Ă  trois endroits diffĂ©rents — ci.yml, release.yml, et [tool.mypy].files dans pyproject.toml. Rien n’empĂŞche mĂ©caniquement quelqu’un (humain ou agent) d’ajouter un module Ă  core/ sans l’ajouter aux trois. Le test lit pyproject.toml en TOML et chaque workflow en YAML, extrait la commande ruff check de chacun, et compare l’ensemble de chemins aux fichiers mypy.files. Si un des trois dĂ©rive, le test Ă©choue — un drop silencieux de chemin devient un test rouge, pas un trou de couverture qu’on dĂ©couvre six mois plus tard.

Le point commun de ces trois garde-fous : aucun n’attend qu’un humain (ou un agent) se souvienne de vĂ©rifier une propriĂ©tĂ© transversale. Ils la transforment en assertion exĂ©cutable, jouĂ©e Ă  chaque pytest -q, y compris en CI. C’est le complĂ©ment mĂ©canique de la revue par tâche : la revue attrape ce que quelqu’un a pensĂ© Ă  chercher sur cette tâche ; le test d’enforcement attrape ce que personne n’a besoin de repenser Ă  chaque tâche, parce que la propriĂ©tĂ© est vĂ©rifiĂ©e structurellement sur tout le dĂ©pĂ´t.

Ce que ça apporte

Mis bout Ă  bout, ces trois mĂ©canismes — sous-projets spec → plan → exĂ©cution, boucle implĂ©menteur/relecteur/correcteur par tâche suivie d’une revue adversariale de branche, garde-fous AST — visent le mĂŞme objectif : une qualitĂ© Ă©levĂ©e dès le dĂ©part, plutĂ´t qu’un audit de rattrapage une fois le produit « fini ». Trois effets concrets s’en dĂ©gagent.

D’abord, un contexte frais par tâche limite la dĂ©rive qu’un contexte surchargĂ© finit toujours par produire — moins de choses Ă  tenir en tĂŞte, moins de compromis pris pour aller vite. Ensuite, la revue systĂ©matique (par tâche et finale) sĂ©pare structurellement l’Ă©criture du jugement, plutĂ´t que de compter sur l’autodiscipline de celui qui vient d’Ă©crire le code. Enfin, les garde-fous AST rendent la non-rĂ©gression prouvĂ©e plutĂ´t qu’espĂ©rĂ©e sur les quelques propriĂ©tĂ©s qui comptent le plus pour ce projet prĂ©cis : licence AGPL respectĂ©e fichier par fichier, surface anglaise du runtime, cohĂ©rence des configurations de lint.

Les limites, honnĂŞtement

Cette mĂ©thode n’est pas une preuve de correction. C’est une discipline qui augmente la probabilitĂ© de dĂ©tecter un problème avant qu’il n’atteigne main, pas une garantie qu’aucun problème n’existe. Trois limites mĂ©ritent d’ĂŞtre dites clairement plutĂ´t que glissĂ©es sous le tapis.

D’abord, elle coĂ»te en itĂ©rations. Une tâche qui revient deux ou trois fois dans la boucle implĂ©menteur → revue → correction avant d’ĂŞtre « clean » prend plus de temps qu’une tâche codĂ©e une fois et jugĂ©e bonne sans vĂ©rification. Ce coĂ»t est assumĂ© — c’est le prix payĂ© pour ne pas le payer plus tard, en incident — mais il est rĂ©el, et il n’a pas de raison de disparaĂ®tre avec l’expĂ©rience du projet.

Ensuite, la qualitĂ© du rĂ©sultat dĂ©pend directement de la qualitĂ© du plan et du spec de dĂ©part. Une revue par tâche vĂ©rifie la conformitĂ© au plan ; si le plan lui-mĂŞme a un trou, un angle mort de conception, une hypothèse implicite fausse, la boucle peut produire du code parfaitement conforme Ă  un plan imparfait. La revue finale adversariale de branche existe en partie pour rattraper ce cas — regarder l’ensemble plutĂ´t que chaque brique — mais elle ne remplace pas un travail de spec sĂ©rieux en amont.

Enfin, les garde-fous AST ne couvrent que ce qu’on a pensĂ© Ă  encoder. Le test sur les messages runtime le dit lui-mĂŞme dans son docstring : il dĂ©tecte l’accentuation française, pas l’absence totale de tournures françaises. Un garde-fou protège une propriĂ©tĂ© prĂ©cise contre la rĂ©gression ; il ne protège rien contre ce qu’on n’a pas anticipĂ© qu’il fallait protĂ©ger. C’est une mĂ©thode, pas une garantie — et le dire explicitement fait partie de la rigueur qu’elle prĂ©tend apporter.


Ceci clĂ´t la sĂ©rie sur cyber-agent-engine. Le premier article posait les cinq principes de la frontière de confiance ; les articles suivants ont dĂ©taillĂ© la tokenisation (article 2), la politique fail-closed (article 3), la topologie hors-boĂ®te (article 4) et l’assemblage exploitable en confiance (article 5). Celui-ci referme la boucle en montrant comment ces cinq briques ont elles-mĂŞmes Ă©tĂ© construites.

Pour aller plus loin :