Aller au contenu principal

Avant les modules, le mouvement

Un mécanisme n'a pas d'intention. C'est précisément ce qui lui permet d'en servir plusieurs.

Le framework est choisi. Le langage aussi. Ce qui manque encore, c'est la réponse à une question plus urgente : qu'est-ce que ce microservice fait, à chaque instant où il tourne ? La question n'est pas rhétorique. Impossible de tracer des frontières de modules sur un système dont on n'a pas encore cartographié le mouvement. J'ai donc commencé là — par les flux, les acteurs, ce qui circule. Pas par le code.

Ce que j'ai trouvé n'était pas exactement ce que j'attendais.

Trois intentions actées

AgenticLayer est conçu pour accueillir trois types d'agents. Cette décision est actée — elle a précédé la première ligne de code, elle a précédé les modules, elle a précédé le choix du framework. Elle a pesé sur tout ce qui a suivi.

Le premier est l'agent d'évaluation. Il reçoit une configuration — passes, règles, points de vérification atomiques — et explore le code source d'un candidat pour rendre un verdict structuré. C'est le plus défini. C'est le seul qui tourne en production. Un seul agent, sur trois conçus.

Le second est l'agent d'assistance. Activé pendant qu'un candidat travaille, il observe, répond, oriente sans révéler. Son horizon : une session. Une difficulté. Une impasse à débloquer sans donner la réponse.

Le troisième est l'agent de montée en compétence. Son horizon est plus long — plusieurs évaluations, un graphe de compétences, des patterns de progression dans la durée. Pas de verdict : une lecture de l'histoire d'un individu.

Trois intentions. Trois temporalités. Un seul en production.

Le parcours d'une règle

L'évaluation commence avant l'agent. Un message arrive sur la queue RabbitMQ — evaluation.execute, portant un identifiant d'évaluation, une référence au candidat, une référence à la configuration. AgenticLayer l'acquitte après avoir démarré le job. Pas avant. Si quelque chose échoue au démarrage, le message reste.

La première chose qu'AgenticLayer fait est de ne pas évaluer. Il demande. Via NATS, il interroge le microservice ArangoDB pour récupérer les passes, les règles, les points de vérification. Ce n'est qu'une fois cette configuration en main que le travail commence.

Les passes s'ordonnancent selon un DAG. Pour chaque règle de chaque passe, AgentService démarre et la boucle s'ouvre : prompt compilé, appel LLM, tool calls, résultats, nouveau tour. Elle s'arrête quand la confiance des évidences dépasse le seuil — ou quand le nombre maximal de tours est atteint.

Parcours d'une règle — de RabbitMQ à l'index sémantique

Ce flux, je l'ai tracé en premier. Il semblait complet. Il ne l'était pas.

Pour un développeur junior : un DAG (Directed Acyclic Graph) est un graphe orienté sans cycle — une représentation des dépendances entre tâches. Une passe peut dépendre du résultat d'une autre : si la passe A évalue la qualité des tests et que B évalue la couverture globale, B attend A. Le DAG garantit l'ordre d'exécution et permet de paralléliser ce qui peut l'être. L'acyclicité est la contrainte clé : A dépend de B dépend de A est un interblocage sans résolution possible — une configuration que le système doit rejeter avant de démarrer, pas au moment où elle bloque.

Ce qui apparaît quand on trace le second

J'ai commencé à cartographier l'agent d'assistance. Contexte différent — pas de dépôt complet, mais l'état courant du projet d'un candidat. Déclencheur différent — pas RabbitMQ, une connexion temps réel via le gateway. Sortie différente — une réponse, pas un verdict.

J'ai écrit le flux. L'agent reçoit un contexte. Il compile un prompt. Il appelle un LLM. Si le LLM demande à lire un fichier ou chercher un pattern dans le code en cours : il exécute le tool. Il vérifie qu'il ne tourne pas en boucle. Il s'arrête quand la réponse est suffisamment construite.

Je me suis arrêté.

C'était le même cycle. Pas la même configuration — les tools différaient, le prompt était différent, la condition d'arrêt était différente. Mais le cycle : identique.

L'agent de progression a confirmé le constat. Contexte plus riche — plusieurs évaluations passées, un graphe de compétences, des patterns sur la durée. Tools différents : ArangoDB plutôt que Qdrant, agrégation de signaux plutôt que lecture de fichiers. Même structure. Reçoit un contexte, compile un prompt, appelle un LLM, exécute des tools, vérifie les boucles, s'arrête quand la confiance est atteinte, produit une sortie.

Trois agents, trois configurations. Un seul cycle.

Attention : ce cycle partagé n'est pas une coïncidence — c'est la conséquence d'une question posée avant de tracer le premier flux. YAGNI, You Ain't Gonna Need It, est le réflexe le mieux nommé en ingénierie : la résistance à la conception prématurée est saine. Mais il y a un cas où ce réflexe trahit : quand l'architecture du présent referme les possibilités futures sans que personne ne le remarque. Concevoir l'agent d'évaluation sans jamais demander "est-ce que ce mécanisme pourrait servir l'assistance ?" aurait conduit à un service spécialisé, non générique — et la convergence qu'on vient de tracer n'aurait eu aucune raison d'exister. La question n'était pas "est-ce que je construis les deux autres maintenant ?" — c'était "est-ce que le présent les rend possibles ?"

La boucle sans intention

Ce qui reste constant quand on superpose les trois flux n'est pas les tools — chaque agent en utilise un ensemble différent. Ce n'est pas le prompt. Ce n'est pas le cache vectoriel. C'est le mouvement lui-même.

La boucle sans intention

Recevoir un contexte. Compiler un prompt. Appeler un LLM. Si le LLM demande des tools : les exécuter, vérifier la boucle, recommencer. Sinon : vérifier la condition d'arrêt. Si atteinte : extraire la sortie. Sinon : recommencer.

La boucle n'a pas d'opinion sur ce qu'elle évalue. Elle n'a pas d'opinion sur ce à quoi elle répond, ni sur comment elle mesure sa confiance. Le sens — ce que signifie "évaluer une règle", "assister un candidat", "recommander une progression" — lui est entièrement extérieur. C'est précisément pour cette raison qu'elle peut servir les trois.

Ce que la boucle ne sait pas

La boucle sait ce qu'elle fait. Elle ne sait pas avec quoi.

Elle ne sait pas quel LLM appeler. Elle ne sait pas quels tools sont disponibles. Elle ne sait pas comment compiler le prompt qu'on lui fournira. Elle ne sait pas comment détecter qu'elle tourne en boucle — elle délègue. Elle ne sait pas où chercher le code qu'un tool lui retourne.

Chacune de ces ignorances est une frontière. De l'autre côté, une responsabilité distincte — quelque chose qui peut évoluer sans toucher la boucle. Un nouveau provider LLM n'a aucune raison de modifier le cycle. Un nouveau tool non plus. Une nouvelle stratégie de prompt, une nouvelle méthode de détection — rien de tout cela ne devrait atteindre le mécanisme lui-même.

Ces responsabilités ont un nom. Les donner, c'est tracer les modules. Mais les tracer à partir de la boucle — et non autour d'elle — change l'ordre dans lequel ils apparaissent. C'est ce changement d'ordre qui a rendu la suite possible.


La prochaine fois : La boucle est l'invariant. Ses ignorances sont les modules. Mais concevoir pour trois agents quand un seul tourne, c'est un pari — et la prochaine fois raconte ce qu'il coûte de le tenir dans la structure des modules.