Agents IA

Agents IA : le signal que l'agent lit après avoir édité.

Un agent autonome n'a pas besoin d'une capture d'écran. Il a besoin d'une réponse courte et fiable à une question précise : « mon édition a-t-elle fait exactement ce qu'elle devait, et rien d'autre ? ».

Pourquoi une boucle de rendu

Après une édition, un agent peut lire un diff de source et croire que tout va bien. Mais le diff ne montre pas l'effet réel : un include partagé modifié, une fonction commune, une requête qui vide une table, une édition concurrente sur la même page. Sur une page legacy sans test, personne ne voit ces effets avant la production.

RenderLoop compare l'état rendu avant et après. Il normalise le HTML, masque le bruit déclaré, puis diffe. L'agent reçoit un résumé compact et un diff unifié. Tout ce qui n'est pas le changement voulu est un signal de régression.

Un agent travaille souvent dans un périmètre étroit : un fichier, un composant, une fonction. RenderLoop lui montre l'effet rendu de son édition, y compris les conséquences indirectes qui sortent de ce périmètre. Grâce à ce retour, il peut identifier une régression sur un sujet qu'il n'avait pas prévu de toucher — un include partagé, un bloc voisin, une valeur affichée ailleurs — au lieu de la découvrir en production.

La boucle à suivre

  1. Découvrir avant de deviner. render-loop -a capabilities -f json renvoie chaque action, ses options requises, la forme de son JSON et la sémantique des codes retour. render-loop -a advise -f json donne le mode opératoire et les pièges.
  2. Capturer avant de toucher au code. render-loop -a capture -u <url> -t before. Sans snapshot « before », il n'y a rien à differ.
  3. Éditer, puis servir le nouveau fichier. Attention à opcache : un recheck immédiat peut lire l'ancienne version et afficher un diff vide.
  4. Re-vérifier. render-loop -a recheck -u <url> -f json. Le diff doit contenir uniquement l'édition voulue.
  5. Déclarer le rayon d'impact. --expect-selector='.price' donne un verdict binaire et sort en erreur si un changement fuit hors du sélecteur.
  6. Cas particuliers. Page JS/SPA : pousser le HTML avec -a store. Fragment alimenté par un AJAX interne : capturer l'endpoint avec --ajax, ou en POST avec --method POST --data.
boucle agent
# 1. contrat machine
$ render-loop -a capabilities -f json

# 2. état avant
$ render-loop -a capture -u "http://localhost:8000/compte" -t before
Snapshot saved: localhost_compte_1792492262.7813

# 3. édition du code

# 4. verdict, avec portée déclarée à l'avance
$ render-loop -a recheck -u "http://localhost:8000/compte" \
    --expect-selector='.balance' -f json
{
  "summary": { "lines_added": 1, "lines_removed": 1, "hunks": 1, "changes": [ … ] },
  "assertion": { "satisfied": true, "inside_scope_changed": true, … },
  "diff": "@@ -12,7 +12,7 @@"
}
exit 0 — rien n'a fui hors de .balance

Un contrat machine, pas du texte d'aide

capabilities décrit la CLI elle-même. Un agent n'a pas à parser l'aide de Symfony ni à deviner les options : il lit un manifeste stable.

{
  "tool": "render-loop",
  "formats": ["table", "json"],
  "exit_codes": { … },
  "actions": [
    {
      "name": "recheck",
      "summary": "…",
      "required": ["url"],
      "optional": ["exit-code", "expect-selector", …],
      "output": "…",
      "exit_code": "…"
    }
  ]
}

advise va plus loin : il renvoie le comment, pas seulement le quoi. Le même contenu est versionné dans AGENTS.md pour les agents qui lisent le dépôt avant d'exécuter quoi que ce soit. En cas de désaccord entre les deux, la CLI fait foi.

{
  "audience": "AI coding agents",
  "one_liner": "…",
  "workflow": ["…"],
  "reading_the_output": ["…"],
  "pitfalls": ["…"]
}

Lire la sortie sans brûler de tokens

  • Lire summary d'abord. lines_added, lines_removed, hunks, tags_touched et changes. Ne déplier diff que si le résumé est non vide.
  • Utiliser changes pour comprendre. Une entrée par changement localisé : kind, selector, context, before, after. Pas besoin de parser le diff unifié.
  • Décider sur le JSON, pas sur le code retour. recheck et compare sortent en 0 même avec un diff. Pour brancher sur le statut, passer --exit-code.
  • Se souvenir que changes est au mieux. Sur du markup sale, une insertion structurelle peut vider le tableau. Le diff unifié est le plancher fiable.

Déclarer le rayon d'impact

Le piège classique d'un agent, c'est de voir un changement inattendu et de le rationaliser après coup. --expect-selector inverse l'ordre : l'intention est déclarée avant de voir le diff.

render-loop -a recheck -u "http://localhost/page.php" \
  --expect-selector='.price' -f json

L'option est répétable. Le bloc assertion isole la fuite dans out_of_scope, et le code retour est non nul si satisfied est faux. C'est un garde-fou par édition, pas un filet anti-régression figé : pour cela, utiliser le golden master.

Pas de faux vert. Un sélecteur faux ou absent donne satisfied: false avec la raison « matched no element ». satisfied: true ne prouve pas que l'édition est correcte, seulement qu'aucun changement visible n'a fui.

Les pièges à éviter

  • Masquage par sélecteur uniquement. Jamais de regex sur le contenu : un prix à 0.00 doit rester visible.
  • Config du projet. Pointer -c vers un fichier versionné dans le projet, pas vers celui de vendor/, en lecture seule.
  • opcache. Vérifier que le serveur sert bien le nouveau fichier avant de conclure qu'il n'y a pas de diff.
  • Cookie de session. Passer la valeur par RENDERLOOP_COOKIE, jamais en argument : ps est public.
  • Hôtes autorisés. La capture est bornée par une liste blanche et ne suit pas les redirections. Garder cette liste étroite.
  • Périmètre. Le rendu seulement : ni base de données, ni export, ni PDF, ni API. Un calcul critique mérite un test unitaire.