Le temps d'une évaluation
Ce qui orchestre ne fait pas. C'est précisément pourquoi il peut coordonner.
Six épisodes — de la vision du système aux scopes de chaque service dans AgentModule. Il est temps de reculer d'un pas. EvaluationModule est ce qui appelle AgentService. C'est lui qui reçoit le signal, qui va chercher la configuration, qui ordonne les passes, qui publie les résultats. La boucle agentique ne connaît pas le concept d'évaluation globale — c'est EvaluationModule qui en porte la responsabilité entière, de la première ligne de message RabbitMQ au dernier événement sortant.
Ce que le message apporte — et ce qu'il tait
Le déclencheur d'une évaluation arrive sur la queue evaluation.execute. Ce message est intentionnellement mince : un identifiant d'évaluation, une référence au candidat, une référence à la configuration. Pas la configuration elle-même.
Ce n'est pas une omission. C'est un choix. Le message est un pointeur, pas un payload. La configuration complète — les passes, les règles, les points de vérification atomiques — vit dans ArangoDB MS. C'est là qu'elle a été construite, validée, versionnée. La transporter dans le message serait la dupliquer sans raison, et créer une forme de couplage entre le gateway qui déclenche et le contenu métier qui évolue indépendamment.
La première chose qu'EvaluationModule fait est donc de demander. Via NATS, il interroge ArangoDB MS et récupère la structure complète de l'évaluation. Ce n'est qu'une fois cette configuration en main qu'il acquitte le message RabbitMQ — non pas à la réception, mais après avoir effectivement démarré le job. Ce détail sur le timing de l'ACK a des conséquences sur la fiabilité du système, mais c'est une question pour un prochain épisode.
De la configuration au mouvement
La configuration récupérée, EvaluationModule entre en mouvement. Les passes sont organisées selon un DAG — certaines peuvent s'exécuter en parallèle, d'autres attendent le résultat d'une passe précédente. Pour chaque passe, chaque règle est soumise à AgentService. Les résultats s'accumulent, les scores sont calculés, et quand toutes les passes sont complètes, EvaluationModule publie le verdict final.
Ce schéma dit l'essentiel : EvaluationModule est le seul composant qui connaît le cycle complet — de la réception du message à la publication du verdict. AgentService ne sait pas qu'il contribue à une évaluation. DAGExecutionOrchestrator ne sait pas ce que font les règles. Chaque composant est ignorant du reste — c'est cette ignorance distribuée qui rend le tout cohérent.
Sept responsabilités
EvaluationModule ne s'organise pas autour d'un service central qui ferait tout — j'ai voulu une décomposition dont chaque pièce puisse être lue, testée et remplacée sans que les autres en sachent quoi que ce soit. Il se décompose en sept composants aux rôles distincts.
Trois use cases définissent les scénarios d'exécution possibles. ExecuteEvaluationUseCase porte le flux principal — une évaluation complète depuis la récupération de la configuration jusqu'à la publication du verdict. ExecuteSelectiveEvaluationUseCase couvre le cas où seul un sous-ensemble de règles doit être réévalué — sans reprendre l'intégralité des passes. ReEvaluateUseCase gère la reprise après interruption : il lit l'état persisté règle par règle dans Redis et ne relance que ce qui n'est pas encore terminé. Trois scénarios, trois use cases — plutôt qu'un seul use case à drapeaux qui porterait les trois à la fois, et dont les tests auraient eu l'air très amusants à écrire.
Deux orchestrateurs divisent l'exécution en deux niveaux de granularité. DAGExecutionOrchestrator raisonne sur l'ensemble des passes et leur ordre d'exécution. PassExecutionOrchestrator raisonne sur une passe unique et l'ensemble des règles qu'elle contient. Chacun ignore ce que fait l'autre niveau.
Trois services de domaine opèrent sur les résultats bruts produits par AgentService. BonusScoringService applique les règles de bonus configurées sur les scores. ConditionEvaluatorService évalue les conditions de passage — un seuil de score, une règle obligatoire, une combinaison de critères. DecisionGeneratorService produit les décisions finales à partir des scores et des conditions évaluées. Ce sont ces trois services qui transforment une collection de verdicts atomiques en un résultat d'évaluation interprétable. AgentService produit des évidences — c'est EvaluationModule qui les traduit en score. Cette frontière m'a pris du temps à formuler clairement, mais une fois posée, elle a rendu les deux côtés plus simples à concevoir.
Ce que le module dit pendant qu'il travaille
EvaluationModule ne travaille pas en silence. Quatre événements rythment le cycle de vie d'une évaluation, publiés sur l'exchange RabbitMQ evaluation-events.
evaluation.started est émis après l'ACK du message entrant — une confirmation que le job a démarré et que RabbitMQ peut considérer le message comme traité. evaluation.progress est émis à la fin de chaque passe — un signal de progression que le front-end peut afficher en temps réel via le gateway. evaluation.completed et evaluation.failed sont les deux issues possibles : le premier porte le verdict complet, le second porte l'erreur qui a interrompu l'évaluation.
Ces événements ont deux destinations. Le gateway les reçoit et les bridge vers les WebSockets du front-end — c'est le canal temps réel pour le candidat et les évaluateurs. ArangoDB MS reçoit uniquement evaluation.completed et evaluation.failed — c'est par ce canal que le graphe de compétences est alimenté.
Pour un développeur junior : un exchange RabbitMQ de type topic permet de router les messages vers plusieurs queues selon leur routing key. Ici,
evaluation.started,evaluation.progress,evaluation.completedetevaluation.failedsont quatre routing keys distinctes sur le même exchangeevaluation-events. La queueevaluation.events.gatewayreçoit tous les événements — elle alimente le bridge WebSocket. La queueevaluation.events.arangoreçoit uniquement les événements terminaux (completedetfailed) — elle déclenche la persistance dans le graphe. Un même message publié une seule fois est consommé par les deux destinations sans couplage entre elles.
Ce que la carte cache encore
Le cycle de vie d'une évaluation est posé. Ce qu'il ne dit pas encore, c'est comment DAGExecutionOrchestrator traduit concrètement un graphe de dépendances en exécution parallèle — quelles passes peuvent tourner ensemble, lesquelles attendent, et comment un résultat partiel peut en débloquer d'autres. C'est la mécanique de l'ordonnancement, et elle mérite son propre épisode.
Il ne dit pas non plus ce qui se passe quand EvaluationModule s'arrête au milieu d'une évaluation — un pod qui redémarre, une instance qui sature. Le message RabbitMQ sera relivré. Or une évaluation à moitié terminée ne doit pas recommencer depuis zéro. Le mécanisme qui garantit la reprise depuis le dernier point stable est la deuxième question que cet épisode ouvre sans la refermer.
La prochaine fois : EvaluationModule orchestre — mais l'ordre dans lequel il fait tourner les passes n'est pas arbitraire. La prochaine fois, on entre dans le DAG : comment un graphe de dépendances devient un plan d'exécution parallèle.