Comment on l'a construit : développement piloté par sous-agents et revue adversariale
🇬🇧 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 :
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 :
- Article 1 — un LLM avec les droits d’admin sur ton firewall — la thèse gĂ©nĂ©rale que cette mĂ©thode de construction avait pour mandat de tenir de bout en bout.
- Agents en Production — migration Qwen2.5 —
la lignĂ©e d’expĂ©riences LoRA/agents qui a prĂ©cĂ©dĂ©
cyber-agent-engine, et où une bonne partie des leçons de rigueur en amont ont été apprises à la dure.
