Chapitres

DOC-05 / Référence technique · Chapitre 03

Le cœur agentique : Atlas et les agents

Décrit l'architecture du moteur agentique : classification LLM des emails entrants, spawn headless de Claude Code via pseudo-TTY, injection des personas agents et orchestration post-spawn (deploy, QA, récap).

Le cœur agentique : orchestrateur et agents

À quoi sert cette section — Elle décrit le moteur qui transforme un email entrant (ou une tâche de chantier) en action exécutée : classification d'intention par LLM, lancement d'un session de code en mode headless via un pseudo-terminal, injection de la persona de l'agent désigné, et orchestration post-exécution (déploiement préprod, contrôle qualité, email récapitulatif).

1. Vue d'ensemble

Le cœur agentique repose sur deux briques principales :

  1. L'orchestrateur (agent de direction, codename public : Atlas). Il n'a pas de runtime propre : « être Atlas » consiste à démarrer une session de code assisté par LLM avec un prompt système dédié. Son pilotage est assuré par un ensemble de modules Python et un script Node coordinateur.
  2. Les agents spécialisés (une trentaine au total, répartis en familles fonctionnelles) : des personas — identité, cadre cognitif, périmètre métier — injectées dans le contexte d'un LLM au moment d'exécuter une tâche.

Le flux principal, appelé pipeline Atlas Inbox, se déroule comme suit :

Email forwardé → boîte Atlas
        │
        ▼  (ingestion → table messages + ligne de suivi status='received')
 ┌──────────────────────────┐
 │ Classificateur d'intent  │  intent ∈ {run, chantier, question,
 │  (LLM via façade)        │           noise, negociation, conseil}
 └──────────────────────────┘  → status='classified'
        │                        + matérialise run / question /
        │                          negociation / conseil selon l'intent
        ▼
 ┌──────────────────────────┐
 │ Module de spawn          │  whitelist {run, chantier, question, negociation}
 │  → coordinateur Node     │  lance session code headless via pseudo-TTY
 │    (stream JSON)         │  + prompt système Atlas (CODE + COMMIT uniquement)
 └──────────────────────────┘
        │  La session écrit un fichier résultat temporaire puis se termine
        ▼
 ┌──────────────────────────┐
 │ Orchestration post-spawn │  déploiement préprod → QA Playwright →
 │                          │  email récap Atlas → UPDATE 'actioned'
 └──────────────────────────┘  (re-spawn max 3 itérations si QA échoue)

Étape d'ingestion — La transition « email reçu → ligne de suivi » est assurée par un module de scrutation IMAP. Ce module interroge la boîte Atlas via la façade de messagerie du vaisseau-mère, effectue une recherche des messages non lus, puis pour chaque message : insère une ligne dans la table de messages (opération idempotente grâce à une contrainte d'unicité sur l'identifiant de message) et crée une ligne de suivi au statut received, avant de marquer le message comme lu. Il enchaîne automatiquement le scan antivirus puis la classification — c'est cet enchaînement, et non un scheduler externe désactivé, qui boucle le pipeline de bout en bout.

2. Modèle d'agents

2.1 Stockage

Les agents sont stockés dans une table centrale du schéma principal. Une vue de compatibilité, portant un nom hérité d'une migration antérieure (pattern Strangler Fig), expose les mêmes colonnes. Le code Python lit via cette vue ; l'interface web lit directement la table via l'ORM. Les deux surfaces sont strictement équivalentes.

2.2 Colonnes clés

Colonne Type Rôle
codename varchar(64) Clé de lookup, kebab-case unique (ex. orchestrator, backend, securite, seo-technique).
nickname varchar(64) Alias humain public (ex. Atlas, Gauss, Mitnick, Otlet).
role varchar(255) Intitulé de poste (ex. « SEO Technical Officer »).
group_name varchar(64) Famille fonctionnelle : direction, cadrage, execution, validation.
orbite integer Anneau d'orbite (1, 2 ou 3). ⚠️ Ne coïncide pas toujours avec l'anneau affiché visuellement (voir section 7).
heritage varchar(255) Ancrage géographique/historique de la persona (ex. « Grèce antique » pour Atlas, « États-Unis, 1963 » pour Mitnick).
cognitive_frame text « Façon de penser » — bloc injecté en priorité dans le briefing (les 4 dimensions cognitives).
personality, quote, inspiration, proof text Identité riche (tier full uniquement).
job_mission, job_perimeter, job_key_checks text Scope métier (tier metier et supérieur).
content_md text Profil markdown long, consommé par le module d'appel agent legacy.
active integer 1 = agent recrutable.
prompt_domains varchar(255) CSV de domaines de prompt (contenu, faq, cover…) pour le routage vers les agents de génération.
auto_spawn, spawn_count, error_count integer Compteurs runtime.

Des colonnes d'internationalisation EN sont présentes pour les champs narratifs : role_en, heritage_en, cognitive_frame_en, etc.

2.3 Tables satellites

Plusieurs tables satellites gravitent autour de la table principale des agents : activité, événements, heartbeats, relations inter-agents, compétences, outils, expérience et historique d'expérience. Certaines de ces tables disposent d'une vue de compatibilité héritée (activité, heartbeat, relations, expérience et historique) ; d'autres n'en ont pas (événements, compétences, outils). Le manifeste du domaine agents liste la surface complète des tables et routes API exposées.

2.4 Chargement de la persona

Avant qu'un LLM exécute une tâche, il « devient » l'agent désigné via un mécanisme d'assemblage de contexte en trois couches :

  • Identité + cadre cognitif + scope métier issus de la table des agents.
  • Cicatrices pertinentes par recherche sémantique vectorielle (RAG pgvector) sur la table des embeddings, en priorisant les cicatrices propres à l'agent concerné avant de compléter avec les cicatrices globales. Objectif explicite : éviter qu'un agent voie en priorité les leçons d'un autre.
  • Lettre de mission du chantier si le chantier est résolvable depuis la tâche.
  • Préférences utilisateur (profil opérateur, tranche core).

Trois tiers de rendu sont calibrés par budget tokens, afin de limiter la dilution d'attention et de maximiser le hit du cache prompt (TTL ~5 min) :

Tier Budget approx. Contenu inclus Usage typique
Core ~150 tokens Cadre cognitif seul + identité minimale Fast-path sans code, sous-agents de type Task
Métier ~350 tokens Core + rôle + scope métier Défaut pour le spawn Atlas complet et le worker de tâches
Full ~600 tokens Métier + heritage + citation + personnalité + inspiration + preuve Revue, drill, lettre de mission

Un alias de rétrocompatibilité expose le tier métier sous le nom générique format_briefing pour les appels anciens.

2.5 Note sur le module d'appel agent legacy

Un module d'appel agent plus ancien permet d'invoquer un agent en subprocess et de journaliser l'échange dans le réacteur. ⚠️ Ce module porte une dette technique notable : ses helpers de chargement des cicatrices et des doctrines se connectent encore à une base de données MySQL héritée — vestige antérieur à la migration vers PostgreSQL. Les exceptions y sont silencieusement avalées, ce qui signifie que l'appel ne plante pas mais peut retourner des profils sans cicatrices ni doctrines.

Point d'attention à confirmer — Ce chemin legacy est-il encore invoqué en production, ou a-t-il été supplanté par le pipeline de spawn Atlas associé au module de persona actuel ? Le commentaire interne du module indique une migration en cours vers la table d'agents PostgreSQL, mais les deux helpers de cicatrices/doctrines pointent toujours l'ancienne source MySQL.

Atlas l'orchestrateur — le moteur de spawn

Rôle restreint de l'agent spawné

Le prompt système transmis à l'agent spawné cadre strictement son périmètre d'action : investigation, écriture de code et commit. L'agent ne déclenche pas de déploiement, n'envoie pas d'email, et ne met pas à jour le registre des messages entrants — ces opérations sont toutes orchestrées par le moteur Python après la fin de l'exécution de l'agent.

Des garde-fous de périmètre sont inscrits dans le prompt système : les modifications sont autorisées uniquement dans les zones hub, back-office et tenant concerné ; jamais dans un composant partagé public.

Instruction « checkpoint tôt » (introduite mi-2026) : le prompt système impose désormais à l'agent d'écrire un fichier de résultat intermédiaire le plus tôt possible — dès le premier diagnostic partiel — et de l'écraser après chaque avancée majeure, plutôt que d'attendre la fin complète du travail. La raison est simple : si l'agent est interrompu par le délai d'expiration, le dernier état persisté sert de salvage pour prévenir l'équipe. Un fichier partiel vaut mieux que rien. Cette instruction fait suite à un incident où le fichier de résultat n'était pas encore écrit au moment où le processus a été tué.

Sélection des candidats au spawn

Le moteur de sélection interroge le registre des messages entrants et retient uniquement les entrées qui réunissent les trois conditions suivantes :

  • statut classified ;
  • intention classifiée parmi : run, chantier, question, negociation ;
  • absence d'audit de spawn récent (démarré, terminé ou échoué dans la dernière heure) — garantie d'idempotence anti-double-spawn.

Constantes de sécurité appliquées :

  • Intents autorisés : run, chantier, question, negociation — les intents noise et conseil sont exclus du spawn.
  • Maximum de spawns par cycle : 3, pour limiter les coûts en cas de dérive.
  • Délai d'expiration par spawn : 40 minutes. Ce délai a été relevé (depuis 25 minutes) à la suite d'un incident où une investigation sur un environnement de production complexe était coupée juste avant l'écriture du livrable — le spawn était marqué échoué alors que le correctif était déjà en place. Cette correction a été déployée conjointement avec l'instruction « checkpoint tôt ».
  • Kill-switch : une variable d'environnement permet de désactiver entièrement le moteur de spawn sans toucher au code.
  • Modèle par défaut : Sonnet.

Note de vigilance : le commentaire d'en-tête du module principal est périmé — il annonce encore une liste blanche à deux intents, un délai d'expiration de 10 minutes et un offset cron différent. Ce sont les constantes effectives du code qui font foi : quatre intents autorisés, délai de 40 minutes. Cette page utilise les valeurs réelles ; ne pas se fier au commentaire de tête du fichier.

Anti-race : verrou consultatif de base de données

Pour couvrir la collision entre le cron toutes les 5 minutes et une invocation manuelle simultanée, le moteur acquiert un verrou consultatif au niveau session sur l'identifiant du message traité avant tout spawn. Ce verrou est maintenu via une connexion interactive distincte et libéré automatiquement à sa fermeture ou à la mort du processus. Si le verrou est déjà tenu par une autre instance, le message est ignoré pour ce cycle et un audit spawn_skipped_lock_held est enregistré.

Construction du prompt

Le prompt transmis à l'agent est construit à partir de trois sources :

  • l'email forwardé (expéditeur, sujet, corps tronqué à 6 000 caractères) ;
  • l'identité du tenant, résolue par heuristique de domaine (table de correspondance explicite en priorité, puis repli sur le contact enregistré) ;
  • la sortie du classificateur d'intents (intention, niveau de confiance, raisonnement du modèle).

En cas d'échec du contrôle qualité (QA), un prompt de re-spawn est construit en ajoutant les commits précédents et les erreurs détectées (erreurs HTTP niveau couche applicative, erreurs navigateur niveau interface).

Livrable contractuel de l'agent

Avant de terminer son exécution, l'agent doit impérativement écrire un fichier de résultat structuré contenant : le statut (ok ou non), le tenant concerné, les routes à soumettre au QA, la liste des commits réalisés, un résumé en markdown et un brouillon de réponse à l'utilisateur. Ce fichier constitue le contrat lu par l'orchestrateur Python pour décider des actions post-spawn.


Invocation programmatique via pseudo-TTY

Règle fondamentale du harness : un agent Claude lancé programmatiquement passe obligatoirement par un vrai pseudo-TTY via un wrapper Node.js dédié — jamais par un appel subprocess Python direct.

Pourquoi : lancer claude en mode non-interactif depuis un subprocess Python provoque un blocage silencieux — deadlock de buffer et/ou détection du mode non-TTY qui refuse le prompt en argument. Un pseudo-TTY de type xterm-color contourne les deux pièges.

Chaîne d'appel

Le moteur Python écrit le prompt et le prompt système dans des fichiers temporaires, puis délègue l'exécution au wrapper Node.js en lui passant les chemins de ces fichiers, l'identifiant du spawn, le modèle cible et le délai d'expiration. L'appel ressemble schématiquement à :

node un composant interne \
  --id <ID> \
  --prompt-file <fichier-prompt> \
  --system-prompt-file <fichier-système> \
  --model sonnet \
  --stream-log <fichier-log> \
  --add-dir <répertoire-projet> \
  --timeout-sec 2400

Le wrapper Node

Le wrapper Node.js (dépendance node-pty) gère les points suivants :

  • Localisation du binaire Claude sur la machine hôte.
  • Construction des arguments passés à Claude : mode non-interactif, répertoire de travail autorisé, sans persistance de session, format de sortie stream-JSON verbeux avec messages partiels et événements de hooks, modèle et prompt système en append.
  • Ordre critique des arguments : l'option --add-dir doit impérativement être placée avant les autres flags — sinon son comportement varargs « avale » le prompt comme répertoire. Cette contrainte a coûté 30 minutes de débogage lors d'un incident passé.
  • Spawn du pseudo-TTY avec des dimensions généreuses (200 colonnes × 50 lignes).
  • Injection des variables d'environnement nécessaires aux hooks Stop de l'agent (coordonnées base de données, contexte de worker) — le mot de passe de base de données est hérité de l'environnement parent, jamais codé en dur.
  • Émission de chaque événement stream-JSON en JSONL sur stdout (avec tee vers un fichier de log), détection de l'événement de résultat final.
  • Codes de sortie : 0 si succès, 1 si erreur, 2 si arguments invalides. Un événement wrapper final de type node_pty_exit est émis pour le processus Python parent.

Persistance live des événements

Côté Python, chaque ligne JSONL reçue est parsée et persistée en temps réel dans le journal des événements de spawn. Chaque enregistrement porte : un numéro de séquence, un type d'événement (sous-typé selon qu'il s'agit d'un événement de stream, d'un événement système ou d'un résultat d'outil), le nom de l'outil éventuellement impliqué, et le payload complet en JSON.

Note d'architecture : la table de journal des événements est en réalité une vue simplement actualisable posée sur une table physique sous-jacente — le même pattern Strangler-Fig que pour le registre des agents. Les insertions à travers la vue fonctionnent normalement sur les colonnes exposées.

Cette persistance en temps réel permet de diagnostiquer un blocage (le dernier événement visible en base révèle où l'agent est bloqué) et d'agréger coût, durée et tokens depuis l'événement de résultat final. Tout est également audité dans le journal d'audit central.


Classification des intents

Énumération des intents

Le classificateur reconnaît six intents valides. Le tableau suivant décrit leur sémantique et leur effet en aval :

Intent Sens Effet en aval
run Tâche atomique exécutable en une seule commande Création ou mise à jour d'un run dans le registre des runs
chantier Mission structurée multi-étapes (squelette + agents) Création d'un brouillon de chantier
question Demande d'avis ou d'analyse, réponse en brouillon Création ou mise à jour d'une question dans le registre
noise Newsletter, spam, rien d'actionnable Aucune action
negociation Demande commerciale entrante (prospect, devis) Insertion d'une négociation dans le registre commercial
conseil Demande d'avis ou d'expertise client sur un élément externe existant Insertion d'un conseil dans le registre dédié

Chaque matérialisation est mutuellement exclusive et idempotente : des index uniques et des gardes conditionnels empêchent la création de doublons même en cas d'appel répété. L'identifiant créé est tracé dans l'enregistrement du message d'origine.

Moteur LLM

La classification s'appuie sur la façade de fournisseur IA — couche d'abstraction centralisée — jamais sur un appel SDK direct. Le routage du modèle est résolu dynamiquement par configuration ; le fournisseur et le modèle par défaut sont Mistral / mistral-small-latest. La sortie est demandée en JSON strict, avec un délai d'expiration de 30 secondes.

Garde-fous

  • Anti-prompt-injection : le corps de l'email est sandwiché entre deux délimiteurs explicites et précédé d'un avertissement indiquant au modèle qu'il s'agit de données email, non d'instructions. Le corps est tronqué à 8 000 caractères.
  • Énumération stricte : tout intent hors des six valeurs reconnues est rejeté, quelle que soit la réponse du modèle. Tout niveau de confiance hors de l'intervalle [0, 1] est également rejeté.
  • Auto-floor confiance : une confiance inférieure à 0,5 force l'intent à noise.
  • Seuil d'incertitude : une confiance inférieure à 0,7 déclenche un audit classification_uncertain.
  • Court-circuit pré-LLM : si le fondateur re-forwarde un récapitulatif Atlas (sujet commençant par Fwd: [Atlas]…), l'intent est forcé à noise sans appel LLM, pour éviter un spawn coûteux à vide.
  • Scan antivirus en premier : la classification est bloquée si une pièce jointe présente un verdict autre que clean — doctrine « scanner avant d'ouvrir ».
  • Garde-fou faux-noise avec pièce jointe du fondateur : si un email est classé noise mais provient du fondateur et comporte des pièces jointes, l'intent est reclassifié en question. Rationale : le contenu actionnable peut se trouver dans une capture d'écran que le modèle textuel ne voit pas. Un audit noise_override_founder_attachment est enregistré.
  • Garde-fou client existant : si un email est classé negociation ou conseil mais que l'adresse expéditrice correspond à un tenant actif, l'intent est rabaissé en run scopé au tenant concerné. Les emails clients ne sont pas des nouvelles négociations commerciales. Un audit existing_client_guard est enregistré.

En fin de classification, le registre des messages entrants est mis à jour avec l'intent, le niveau de confiance, le modèle utilisé, le raisonnement du classificateur et le statut classified. Un audit classified est enregistré.

Enrichissement OCR des pièces jointes

Avant l'appel LLM, si des pièces jointes sont présentes et que le verdict antivirus est clean, un module d'extraction de texte par reconnaissance optique de caractères (OCR) est invoqué sur le répertoire temporaire des pièces jointes. Le moteur utilisé est Tesseract en local — aucun appel cloud.

Le texte extrait est injecté dans le contexte du prompt sandwich, entre ses propres délimiteurs (IMAGE_OCR_TEXT_START / END), après le corps de l'email. La configuration du moteur OCR est résolue par le système de routage dynamique.

Important — données personnelles : le texte OCR extrait n'est jamais persisté en base de données (risque de données personnelles — usage mémoire uniquement dans le cycle de classification). Seul un audit ocr_extracted est enregistré, portant le nombre de caractères extraits et le nombre approximatif d'images traitées.

Raison d'être : le modèle textuel ne voit pas les captures d'écran jointes aux emails. Sans OCR, des tickets contenant des captures d'écran de problèmes étaient faussement classés noise, bloquant tout traitement automatique. L'OCR Tesseract local extrait le texte visible sans coût supplémentaire ni fuite de données vers un service tiers.

Les deux routeurs IA parallèles

Le système fait tourner deux moteurs de routage IA multi-fournisseur distincts, qui ne partagent ni code ni configuration. Ce n'est pas un doublon accidentel : ils servent deux environnements d'exécution différents. Il faut savoir lequel modifier selon le contexte de développement.

Axe Routeur Python (automates) Routeur TypeScript (hub Nuxt)
Runtime Automates planifiés (crons, rêve, rappel, classifieur, apprentissage…) Runtime Nitro/Nuxt du hub (points d'entrée API serveur)
Surface exposée embed(…) + complete(…) Génération de contenu + résolution du fournisseur par client
Fournisseurs Embeddings : Mistral / Voyage / OpenAI ; complétion : Claude / Mistral / OpenAI Mistral / Anthropic / OpenAI
Source du routage Fichier YAML de configuration de routage IA Par requête (fournisseur explicite) ou par configuration client
Défaut Embeddings → Mistral ; complétion → Claude Mistral (souveraineté données FR/EU)
Appel au fournisseur Claude Délégué au mécanisme d'invocation d'agent (sous-processus) Requête HTTP directe via une fonction utilitaire dédiée
Doctrine Passage obligatoire par la façade de routage validée Service interne du module Nuxt, sans façade gardée par hook

Le détail complet du routeur Python (embeddings, complétion, fichier YAML, relances, garde-fous) est documenté dans le chapitre Mémoire & apprentissage. Cette section décrit le pendant TypeScript, symétriquement.

La fonction de génération — point d'entrée unique du runtime Nuxt

La fonction de génération de contenu est le point d'entrée unique du runtime Nuxt pour produire du texte via IA. Elle accepte en entrée : un prompt, un systemPrompt optionnel, un provider optionnel, un model optionnel, un nombre maximum de tokens (défaut 4096) et une température (défaut 0,7). La réponse est normalisée et expose : le contenu généré, le fournisseur qui a effectivement répondu après éventuel basculement, le modèle utilisé, le décompte de tokens (entrée/sortie) et la durée en millisecondes.

  • Fournisseur par défaut = Mistral — choix de souveraineté (données FR, API européenne), cohérent avec le défaut embeddings du routeur Python.
  • Modèles par défaut : Mistral → mistral-large-latest ; Anthropic → claude-sonnet-4-6 ; OpenAI → gpt-4o.

Basculement automatique (ordre souverain)

L'ordre de basculement est : Mistral → Anthropic → OpenAI. À l'appel, le système construit l'ordre effectif en plaçant le fournisseur demandé en tête, puis itère selon la logique suivante :

générerContenu(requête)
   fournisseur effectif = requête.provider ?? 'mistral'
   ordre = [fournisseur, ...ordre_souverain \ fournisseur]
        │
        ▼   pour chaque fournisseur de l'ordre :
   clé API absente ? ──oui──► passer (fournisseur suivant, silencieux)
        │ non
        ▼
   appelerFournisseur()  ──succès──► retour { provider, durationMs, … }
        │ exception
        ▼
   dernier de l'ordre ? ──oui──► erreur « Tous les fournisseurs IA sont indisponibles »
        │ non
        ▼
   journaliser le basculement → fournisseur suivant

Deux conditions déclenchent le passage au fournisseur suivant :

  • Clé API absente : le système saute silencieusement le fournisseur concerné.
  • Exception à l'appel : le système bascule sur le suivant et journalise l'événement.

Lors d'un basculement, le modèle utilisé est réalligné sur le modèle par défaut du fournisseur de secours — et non sur le modèle demandé pour le fournisseur initial. Le fournisseur retourné dans la réponse reflète toujours qui a réellement répondu, ce qui est utile à journaliser côté appelant.

Les trois fournisseurs sont appelés via des fonctions distinctes : Mistral et OpenAI par requête HTTP directe (timeout 120 s, format messages identique avec system optionnel + user) ; Anthropic via une fonction utilitaire dédiée.

Point à confirmer : la fonction utilitaire Anthropic est référencée dans le code mais son implémentation n'a pas été localisée dans le périmètre du module. Soit elle est résolue par auto-import Nitro depuis un utilitaire non indexé, soit c'est une dette latente qui ferait échouer la branche Anthropic au runtime. Le basculement Mistral → Anthropic n'est donc pas garanti opérationnel sans vérification préalable.

Clés API (anti-fuite)

Les clés API sont résolues exclusivement par nom de variable d'environnement ; aucune valeur n'apparaît dans le code ni dans cette documentation. Les variables concernées sont : la clé API Mistral, la clé API Anthropic et la clé API OpenAI — toutes stockées dans les fichiers d'environnement exclus du contrôle de version.

Une clé absente n'est pas une erreur bloquante : elle entraîne simplement le saut silencieux du fournisseur correspondant dans la boucle de basculement.

Choix du fournisseur par client — résolution de la configuration tenant

La fonction de résolution par client lit la configuration IA stockée dans la table de configuration des clients (colonne JSON) et retourne le couple { provider, model }. La logique d'extraction est :

  • Fournisseur IA configuré → défaut mistral si absent ;
  • Modèle IA configuré → défaut sur le modèle par défaut du fournisseur si absent.

Toute erreur (client inconnu, JSON invalide, helper en échec) retombe silencieusement sur Mistral avec son modèle par défaut. Le filtre active=1 garantit qu'un client inactif n'est jamais retourné, même si sa configuration renseigne un fournisseur.

État observé : la majorité des clients ont le champ fournisseur IA absent de leur configuration (→ Mistral souverain par défaut) ; seuls quelques-uns le renseignent explicitement.

Point à confirmer : la fonction de résolution par client n'a aucun appelant identifié dans le périmètre du module. Les deux consommateurs réels de la fonction de génération passent le fournisseur en dur dans la requête et n'interrogent jamais la configuration client. Le routage par-tenant est donc câblé mais pas encore branché sur le flux de génération.

Appelants réels de la fonction de génération

Deux points d'entrée Nuxt consomment aujourd'hui le gateway IA :

Point d'entrée Usage Fournisseur demandé
Génération du brouillon de réponse client (pipeline Atlas) Rédige la réponse client d'une run Atlas terminée (vouvoiement, 3–6 phrases, signature d'équipe) Mistral / mistral-small-latest
Espace de dialogue multi-agents (client) Perspectives multi-agents sur des notes de support, puis réponse du lead Notes : Mistral / mistral-small-latest ; réponse lead : Anthropic

Lecture importante : le brouillon de réponse Atlas n'utilise pas le routeur Python — bien qu'il soit la pièce finale du pipeline Atlas Inbox, il est servi par un endpoint Nuxt, donc par le gateway TypeScript. C'est l'illustration concrète de la frontière :

  • Classification et déclenchement = routeur Python (automates) ;
  • Rédaction du brouillon depuis le cockpit hub = routeur TypeScript (Nuxt).

L'espace de dialogue multi-agents est le seul appelant qui mélange volontairement deux fournisseurs dans une même requête HTTP (Mistral pour les notes d'analyse, Anthropic pour la voix finale du lead).

Orbites : Direction, Cadrage, Exécution, Validation

Le modèle de données

Les agents actifs sont répartis selon deux axes qui cohabitent et qu'il faut distinguer :

  • La famille fonctionnelle — source de vérité métier, qui correspond aux quatre rôles structurants : direction, cadrage, exécution, validation.
  • L'anneau numérique (1/2/3) — un attribut stocké en base qui ne mappe pas 1:1 sur la famille fonctionnelle (par exemple, la famille cadrage apparaît en anneau 1 et en anneau 2).

Répartition observée sur les agents actifs :

 anneau | famille    | nombre
--------+------------+--------
   1    | direction  |   4     ← Atlas, Hill, Colbert, Winnicott
   1    | cadrage    |   3     ← Montesquieu, Gauss, Clausewitz
   2    | cadrage    |   2     ← Marco Polo, Socrate
   2    | validation |   8     ← Otlet, Mitnick, Itten…
   3    | exécution  |  12
   3    | validation |   1

Le rendu visuel du réacteur

La page de visualisation du réacteur ne lit pas la colonne d'anneau en base. Elle recalcule le ring d'affichage à partir de la famille fonctionnelle via une table de correspondance locale :

const ringMap = { direction: 1, cadrage: 2, execution: 2, validation: 3 }
  • Ring 1 — Direction (rotation 60 s)
  • Ring 2 — Cadrage + Exécution (90 s, sens inverse)
  • Ring 3 — Validation (120 s)

Point à confirmer : la divergence entre la colonne d'anneau en base et la table de correspondance de l'interface est-elle intentionnelle ou une dette ? Le rendu visuel étant piloté exclusivement par la famille fonctionnelle, il est considéré comme fiable ; la colonne d'anneau semble sous-utilisée côté front.

Routage par domaine de prompt

En parallèle des anneaux, les agents sont sélectionnables par domaine via un attribut CSV. Un endpoint dédié permet d'interroger les agents compatibles avec un domaine donné (contenu, faq, cover, podcast, linkedin, reels), avec proxy cross-tenant vers le Vaisseau Mère si l'appel provient d'un VPS client distant.

Calibration du classifieur Atlas

Un script de reporting mensuel agrège les données de classification et les journaux d'audit pour produire un rapport de santé du classifieur. Il calcule les métriques suivantes :

  • Volumétrie par intention : nombre de classifications, confiance moyenne et écart-type.
  • Taux de succès proxy : par intention, ratio chantiers non-annulés / total des brouillons générés. Un brouillon annulé signifie que le responsable a refusé la suggestion.
  • Taux d'incertitude : proportion de classifications marquées incertaines sur le total.
  • Échecs : décompte des classifications échouées et des blocages pour pièces jointes non sûres.
  • Seuil de passage en Phase 3 (multi-tenant) : le critère de promotion est un taux de succès supérieur à 90 % sur au moins 30 chantiers internes. Tant que ce seuil n'est pas franchi, Atlas reste cantonné à l'usage interne.

Note importante : la « calibration » décrite ici est de l'observabilité et du pilotage de seuil, pas un réentraînement de modèle ni un ajustement automatique de poids. La calibration automatique du prompt système Atlas (apprentissage par exemples, amélioration continue) est une fonctionnalité en feuille de route, non encore livrée.

Orchestration post-spawn : déploiement, QA et notification

Une fois le spawn de l'agent terminé, le système enchaîne automatiquement trois phases : déploiement en pré-production, vérification qualité, puis notification par e-mail.

Lecture du contrat de spawn

L'orchestrateur post-spawn commence par lire le contrat JSON produit par l'agent. Ce contrat peut contenir une correction d'intention décidée par l'agent lui-même : il est autorisé à déclasser l'intention initiale vers une intention sans production de code (question, négociation, run). Cette correction est possible parce que l'agent a lu le fil de conversation complet, alors que le classificateur d'entrée n'avait accès qu'à un extrait tronqué.

Branche sans code

Lorsque l'intention finale est de type question, négociation ou run, le système court-circuite entièrement les phases de déploiement et de QA. Il envoie directement un récapitulatif et marque le dossier comme traité.

Branche code (chantier)

Lorsque l'intention est un chantier, la séquence complète s'enclenche :

  1. Déploiement en pré-production via le script de déploiement standard, avec un délai d'expiration de 15 minutes.
  2. Passage de la suite de tests QA automatisés (Playwright) sur chaque route concernée.
  3. Selon le résultat de la QA, l'orchestrateur prend l'une des deux décisions décrites ci-dessous.

Boucle d'itération QA

Si la QA échoue, l'orchestrateur tente un nouveau cycle de spawn enrichi du contexte d'erreur, jusqu'à un maximum de trois itérations. Au-delà, il déclenche une escalade humaine. Si la QA passe, il envoie le récapitulatif et clôt le dossier.

spawn ok ──► deploy préprod ──► QA route(s)
                                    │
              ┌── QA OK ────────────┴── QA FAIL ──┐
              ▼                                    ▼
        notification Atlas                  itération < 3 ?
        dossier 'actioned'          ┌── oui ──┴── non ──┐
                                    ▼                    ▼
                          re-spawn (contexte    escalade humaine
                          erreur QA)            (max iter atteint)

Notification e-mail

La notification finale transite par la façade d'envoi d'e-mail du système. Elle est toujours adressée à l'équipe interne Synedre — jamais directement au demandeur externe. Cette règle est une doctrine fondamentale : l'IA ne contacte jamais le client final de sa propre initiative. Les identifiants de connexion à la boîte d'envoi sont lus depuis les variables d'environnement du serveur et ne transitent jamais en clair dans le code.


Boucle de déclenchement du pipeline

Le pipeline Atlas est piloté par des tâches planifiées système classiques. Chaque tâche passe par un composant de supervision qui gère les verrous et les journaux d'exécution.

Note : Le moteur de scheduling applicatif intégré au serveur est hors service depuis mai 2026. Le pipeline repose entièrement sur les tâches planifiées système.

Trois tâches forment la boucle de bout en bout :

Fréquence Composant Rôle dans la boucle
Toutes les minutes Collecteur de la boîte Atlas Ingestion et chaînage. Interroge la boîte de réception dédiée, insère les nouveaux messages, puis déclenche en cascade l'analyse antivirus et la classification des intentions. La classification est donc déclenchée ici, dans le même cycle que l'ingestion.
Toutes les 5 minutes (avec décalage de 2 minutes) Orchestrateur de spawn Spawn et orchestration. Récupère les messages dont l'intention est classifiée et éligible, puis lance le spawn et l'orchestration post-spawn (déploiement, QA, notification).
Toutes les 15 minutes Gardien de verrous Filet de sécurité. Émet une alerte si un verrou zombie persiste au-delà de 10 minutes, évitant tout blocage silencieux du pipeline.

L'ordre réel d'exécution est donc : ingestion (minute 0) → analyse + classification (même tick) → spawn (minute 2 du cycle de 5 minutes). Le décalage de deux minutes entre le collecteur et l'orchestrateur de spawn garantit que la classification est terminée avant que le spawn ne cherche des messages éligibles. Un verrou consultatif au niveau base de données couvre la collision résiduelle entre l'exécution planifiée et un déclenchement manuel.

Le collecteur de la boîte Atlas est distinct du collecteur de la boîte générale de la plateforme, qui alimente une table séparée et ne doit pas être confondu avec lui.


Composants du pipeline : rôles et responsabilités

Composants applicatifs principaux

Composant Rôle
Collecteur de boîte Atlas Interroge la boîte de réception dédiée, insère les messages bruts et les entrées de suivi, puis déclenche en cascade l'analyse antivirus et la classification. S'exécute toutes les minutes.
Moteur de classification Classifie l'intention du message via un LLM et matérialise le dossier correspondant (run, question, négociation, conseil, chantier). Depuis juin 2026 : enrichissement OCR des pièces jointes et double garde-fou contre les faux-positifs et les demandes de prospects non encore clients.
Orchestrateur de spawn Prend en charge le spawn de l'agent codeur, le déploiement en pré-production, la boucle QA et la notification finale. Depuis juin 2026 : délai d'expiration du spawn porté à 40 minutes, avec instruction de point de contrôle anticipé dans le prompt système.
Outil de calibration du classificateur Produit le reporting mensuel de qualité de la classification et pilote le seuil de passage à la phase suivante.
Enveloppe de spawn en streaming Composant Node.js obligatoire pour lancer l'agent IA en mode flux JSON interactif via un terminal virtuel.
Chargeur de persona agent Assemble la personnalité de l'agent sur trois niveaux, enrichit le contexte via la recherche vectorielle dans la base de cicatrices, et injecte la lettre de mission.
Client d'invocation agent (héritage) Invocation agent par sous-processus, maintenu pour compatibilité. Les helpers de cicatrices et de doctrines de ce composant reposent encore sur l'ancienne base de données relationnelle.
Routeur IA multi-fournisseur (côté serveur web) Expose une interface unifiée de génération de contenu avec bascule automatique entre fournisseurs IA (ordre de priorité configurable). Résout le fournisseur préféré par client selon la configuration de topologie.
Domaine agents (interface web) Registre des agents, réacteur d'événements, façades base de données et API du tableau de bord central.

Tables et structures de données

  • Registre des agents : table principale des agents déclarés, exposée également via une vue de synthèse.
  • Messages bruts ingérés : stockage des e-mails entrants avant traitement.
  • Suivi Atlas : table de suivi du cycle de vie de chaque message traité par Atlas.
  • Brouillons de réponse : corps de réponse préparés, lus par le classificateur lors de la matérialisation d'une intention de type question.
  • Journal d'audit Atlas : trace de toutes les actions du pipeline.
  • Événements de spawn : vue actualisable sur la table de base des événements de spawn (cf. section sur la vue actualisable).
  • Dossiers de travail : tables dédiées aux runs, questions, négociations, conseils et chantiers.
  • Base RAG : stockage des embeddings vectoriels pour la recherche de cicatrices.
  • Topologie clients : configuration des VPS clients et de leurs préférences IA.

Module externe : OCR

Depuis juin 2026, le classificateur fait appel à un module OCR local (Tesseract) pour extraire le texte des pièces jointes images avant classification. Ce module est indépendant des fournisseurs IA externes.