Mes agents IA dérivent en silence. Voici comment j'ai récupéré le contrôle.

Le problème : je ne savais plus si mes agents fonctionnaient

Ça fait maintenant plusieurs mois que je travaille avec des dizaines d'agents IA autonomes en production. Chaque agent a un rôle précis : certains font de la revue de code, d'autres gèrent la RAG, d'autres encore orchestrent des pipelines entiers.

Et pendant longtemps, j'ai eu l'impression que ça marchait.

Puis j'ai commencé à remarquer des choses étranges. Un agent qui produisait d'excellentes analyses la semaine précédente devenait soudainement superficiel. Un workflow qui prenait 30 secondes s'étirait à 5 minutes. Des sorties qui étaient structurées et précises devenaient vagues et inconsistantes.

Mon premier réflexe ? Me dire que j'avais mal formulé mes instructions. Retravailler les prompts, ajuster les paramètres, relancer. Mais le problème revenait.

Ce que j'ai compris plus tard, c'est que les modèles changent de version en arrière-plan. Anthropic met à jour Claude 3.5 Sonnet, Claude Haiku, les capacités évoluent—et quand vous appelez claude-sonnet-4-6 dans un hook, vous n'avez aucune garantie que le comportement sera identique à la semaine précédente. Sans observabilité, impossible de distinguer un problème de prompt d'un changement de modèle.

Le vrai problème n'était pas les agents eux-mêmes. C'était que j'avais aucun moyen de vérifier.


Avant de continuer la lecture, je vous invite à vous inscrire à ma newsletter pour connaître en avant-première les futurs sujets traités chaque semaine.


1. Pourquoi les outils natifs de Claude Code ne suffisaient pas

Mon premier mouvement a été d'utiliser ce que Claude Code propose nativement : les hooks PostToolUse, les logs de session, les fichiers de transcript.

Ces outils sont utiles—je m'en sers encore. Mais ils ont des limites critiques quand on opère à l'échelle :

Les logs de session sont éphémères. Quand une session se ferme, le contexte disparaît. Impossible de comparer ce qu'un agent a produit il y a trois jours avec aujourd'hui.

Les hooks PostToolUse sont synchrones. Ils s'exécutent dans le flux d'exécution de l'agent et ne permettent pas de coordination asynchrone entre agents distincts.

Pas de notion de "tâche" persistante. Claude Code ne sait pas qu'un agent a démarré une tâche à 14h et qu'il doit la reprendre à 14h30 si le contexte est saturé.

Surtout : pas de vue d'ensemble. Quand vous avez 60 agents actifs, vous ne pouvez pas savoir en un coup d'œil combien ont réussi, combien ont échoué, lesquels sont bloqués.

J'avais besoin de quelque chose de plus fondamental : une source de vérité externe, persistante, interrogeable.


2. La table agent_tasks : PostgreSQL comme bus de messages

L'idée centrale est simple. Plutôt que de laisser chaque agent opérer en silo, je centralise toutes les tâches dans une table PostgreSQL. Cette table devient le cœur du système : les agents y lisent leurs instructions, y écrivent leurs résultats, et s'y signalent mutuellement.

Architecture de la table

La table agent_tasks comporte 30 colonnes. Voici les plus importantes :

CREATE TABLE agent_tasks (
  id              SERIAL PRIMARY KEY,
  title           TEXT NOT NULL,
  description     TEXT,
  agent_name      TEXT,                    -- Agent responsable
  status          TEXT DEFAULT 'pending',  -- pending | in_progress | done | failed | cancelled
  task_role       TEXT DEFAULT 'worker',   -- worker | surveillance | team_spec
  task_type       TEXT DEFAULT 'oneshot',  -- oneshot | recurring | executor | scheduled
  executor        VARCHAR(20),             -- linux | windows | any
  priority        INTEGER DEFAULT 3,
  surveillance_score SMALLINT,             -- Score 0-10 de l'agent de validation
  acceptance_criteria JSONB,               -- Critères injectés par trigger
  result_json     JSONB,                   -- Résultat structuré de l'exécution
  correction_count INTEGER DEFAULT 0,      -- Nombre de cycles de correction
  metadata        JSONB,                   -- Enrichissement libre (batch_id, executor_type...)
  created_at      TIMESTAMPTZ DEFAULT NOW(),
  updated_at      TIMESTAMPTZ DEFAULT NOW()
);

Le workflow en pratique

Un pipeline typique ressemble à ça :

  1. L'orchestrateur crée des tâches en INSERT avec status='pending'
  2. Les agents workers font un SELECT sur les tâches qui leur correspondent et les prennent en charge (UPDATE status='in_progress')
  3. À la fin de leur exécution, ils écrivent leur résultat dans result_json et passent à status='done'
  4. Un agent de surveillance vérifie chaque résultat selon les acceptance_criteria et attribue un surveillance_score
  5. Si le score est insuffisant, correction_count s'incrémente et la tâche repasse en pending pour un retry

Ce cycle complet est désormais auditable, persistant, et visible en temps réel.

Des index conçus pour le polling

Ce n'est pas juste une table de logs—c'est une vraie queue de messages. Et PostgreSQL est parfaitement capable de jouer ce rôle avec les bons index :

-- Queue prioritaire pour les workers Linux
CREATE INDEX idx_agent_tasks_worker_pending
  ON agent_tasks (priority, next_run_at)
  WHERE task_role='worker' AND status='pending';

-- Queue dédiée à l'executor Windows
CREATE INDEX idx_agent_tasks_executor_pending
  ON agent_tasks (executor, status, priority, created_at)
  WHERE status='pending';

Ces index partiels sont ce qui rend le polling efficace même avec des milliers de tâches. La requête de prise de tâche ne scanne que les lignes pending—jamais les done ou failed.


3. Surveillance automatique et scoring

La vraie valeur ajoutée, c'est ce que j'appelle le pipeline de surveillance.

Chaque tâche worker produit un résultat. Mais est-ce que ce résultat est bon ? Un agent humain sait évaluer ça intuitivement—un système automatisé a besoin de critères explicites.

Voici comment ça fonctionne : quand une tâche est créée, un trigger PostgreSQL injecte automatiquement les acceptance_criteria en JSON. Ces critères décrivent ce qu'un bon résultat doit contenir.

Ensuite, un agent de surveillance (mgrr-agent-surveillance) évalue chaque résultat :

-- Un résultat de surveillance ressemble à ça
UPDATE agent_tasks
SET surveillance_score = 8,
    result_json = result_json || '{"validation": {
      "criteria_1": {"pass": true, "comment": "Analyse complète"},
      "criteria_2": {"pass": true, "comment": "Format respecté"},
      "criteria_3": {"pass": false, "correction": "Manque la section edge-cases"}
    }}'
WHERE id = 437;

En production, sur 169 tâches exécutées, voici ce que j'observe :

Score Nombre Interprétation
1 (défaut) 49 Surveillance non exécutée — résultat non validé
2-6 3 Validation partielle
7-10 25 Revue active, bonne qualité

Ce tableau révèle quelque chose d'important : 49 tâches ont un score de 1 (valeur par défaut), ce qui signifie que la surveillance n'a pas été exécutée sur elles. Le pipeline de validation existe, mais les gates de surveillance ne sont pas encore consommées systématiquement—c'est le prochain chantier.

Ce que ça change concrètement : quand un agent produit un résultat médiocre, le correction_count monte. Après deux échecs, je reçois une alerte et je peux inspecter le result_json pour comprendre ce qui s'est passé. Plus de "ça marchait la semaine dernière"—j'ai maintenant un historique complet.


4. Le bonus inattendu : WSL2 et Windows qui parlent la même langue

Voilà le problème que je n'avais pas prévu au départ.

Mon setup de développement est hybride : j'utilise Linux WSL2 pour la rapidité des outils (rg, fd, jq, TypeScript), mais j'ai besoin de Windows pour certaines opérations : Playwright, Chrome, l'interface graphique de Claude Desktop, l'ouverture de fichiers HTML pour vérification visuelle.

Le problème classique dans ce setup, c'est la communication entre les deux environnements. Les solutions habituelles—volumes partagés Docker, sockets Unix, fichiers temporaires—sont soit lentes, soit complexes à maintenir, soit fragiles quand les chemins de fichiers divergent.

Ma solution : agent_tasks comme bus de messages cross-plateforme.

Comment ça fonctionne

Chaque tâche a une colonne executor qui peut valoir linux, windows, ou any.

Côté Linux (mon orchestrateur Claude Code) :

-- Linux crée une tâche pour Windows
INSERT INTO agent_tasks (
  title, description, executor, task_type,
  metadata, status
) VALUES (
  'Ouvrir rapport HTML',
  'Ouvrir le rapport d''audit dans Chrome pour vérification',
  'windows',
  'executor',
  '{"executor_type": "launch", "executor_command": "C:/Users/mathi/.claude/reports/audit.html"}',
  'pending'
);

Côté Windows — un daemon Node.js tourne en permanence et poll la table :

// windows-executor-daemon.ts (simplifié)
async function pollWindowsTasks() {
  const tasks = await db.query(`
    SELECT id, metadata
    FROM agent_tasks
    WHERE executor = 'windows'
      AND status = 'pending'
    ORDER BY priority ASC, created_at ASC
    LIMIT 5
  `);

  for (const task of tasks) {
    await db.query(`UPDATE agent_tasks SET status='in_progress' WHERE id=$1`, [task.id]);

    try {
      const { executor_type, executor_command } = task.metadata;

      if (executor_type === 'launch') {
        // Ouvrir un fichier ou URL directement — 100% fiable
        await shell.openExternal(executor_command);
      }

      await db.query(`UPDATE agent_tasks SET status='done' WHERE id=$1`, [task.id]);
    } catch (err) {
      await db.query(
        `UPDATE agent_tasks SET status='failed', result_json=$2 WHERE id=$1`,
        [task.id, { error: err.message }]
      );
    }
  }
}

// Poll toutes les 2 secondes
setInterval(pollWindowsTasks, 2000);

Côté Linux, pour attendre le résultat :

-- Attendre la complétion (polling simple)
SELECT status, result_json
FROM agent_tasks
WHERE id = 388
  AND status IN ('done', 'failed');

Ce que j'ai appris sur les erreurs Windows

L'audit de mes 169 tâches révèle que 12 sur 18 tâches Windows ont échoué. Taux d'échec : 66,7 %. Toutes dues aux mêmes causes :

Type d'erreur Cause Solution
ETIMEDOUT sur spawnSync cmd cmd.exe en mode headless attend une interaction utilisateur Utiliser executor_type: launch
Corruption de chemins Les backslashes sont stripés en JSON round-trip Utiliser des forward-slashes C:/Users/...
PowerShell one-liners Interpolation de string fragile Écrire un .ps1 dans un fichier temp, puis exécuter

La règle d'or après 12 échecs : executor_type: launch est le seul pattern fiable pour ouvrir des fichiers ou URLs depuis un contexte headless Windows.


5. Résultats après audit complet

Trois semaines après la mise en production de ce système, j'ai lancé un audit automatisé de l'infrastructure entière. Six agents spécialisés ont analysé la base de données, le système de fichiers, les skills RAG, la configuration multi-environnement.

Voici les chiffres clés :

Indicateur Valeur
Tâches exécutées (total historique) 169
Taux de complétion global 59,2 % (100 done)
Taux d'échec global 7,1 % (12 failed)
Agents disponibles 60 agents
Conformité format agents 100 %
Scripts infrastructure 153 fichiers
Hooks actifs 50

Ce qui m'a le plus surpris : 100 % de conformité sur les 60 agents. Chaque fichier agent respecte le format attendu, les modèles sont correctement déclarés, les frontmatter YAML sont valides. Sans ce système de monitoring, j'aurais passé des heures à déboguer des agents cassés silencieusement.

Les 5 anomalies critiques identifiées :

  1. Taux d'échec Windows de 66,7 % — 12 tâches sur 18 dues aux mauvais types d'executor
  2. Backlog surveillance non consommé — 14 tâches de validation en attente
  3. Tâche 388 bloquéein_progress depuis plus de 3 jours (> probablement un crash silencieux)
  4. 5 agents à max_turns atteint — statut DB non mis à jour après crash
  5. DSN hardcodés dans 3 hooks — migration impossible sans modification du code

Chacune de ces anomalies était invisible avant. Maintenant, elles sont détectables en une requête SQL.


Ce que j'aurais fait différemment

Avec le recul, voici les trois choses que j'aurais changées dès le départ :

1. Valeur de priorité par défaut à 3, pas 1. 53 % de mes tâches sont en priorité 1 (maximale) parce que c'est la valeur par défaut. Ça neutralise complètement la queue prioritaire.

2. Surveillance obligatoire, pas optionnelle. Le pipeline de surveillance existe mais n'est pas encore systématiquement consommé. Un trigger qui bloque le passage à done sans score de surveillance aurait évité les 49 tâches non validées.

3. Alerte sur les tâches in_progress depuis plus de 30 minutes. Une tâche bloquée 3 jours, c'est un agent crashé en silence. Une règle simple de timeout aurait suffi à détecter ça automatiquement.


Conclusion

L'observabilité des agents IA n'est pas un luxe—c'est une nécessité dès que vous dépassez 5 ou 6 agents en production.

PostgreSQL comme bus de messages central m'a permis de résoudre deux problèmes avec une seule solution : l'observabilité de mes agents, et la communication cross-plateforme WSL2/Windows. La table agent_tasks est maintenant le cœur de mon infrastructure—tout passe par elle, tout est traçable, tout est corrigible.

Est-ce que ça règle le problème de la dérive des modèles ? Pas complètement. Claude Haiku aujourd'hui n'est pas Claude Haiku dans trois mois. Mais au moins, maintenant, je peux mesurer la dérive. Et ce qu'on peut mesurer, on peut corriger.

Le prochain chantier : intégrer un score de qualité automatique basé sur des embeddings pour comparer les sorties d'un même agent dans le temps—et détecter les régressions avant qu'elles deviennent des problèmes.

Vous avez des questions sur l'architecture ou le schéma complet de agent_tasks ? Répondez à cette newsletter ou laissez un commentaire—je suis curieux de savoir si vous gérez la même problématique.


Articles connexes


Qui suis je ?

Je suis Mathieu GRENIER, CTO d'Easystrat une startup de Montpellier, en France. Je manage une équipe d'une dizaine d'ingénieurs (Graphistes, IA, frontend, backend, devOps, AWS) en remote depuis le Japon.

J'ai aussi mon activité de freelance, où je conseille des entrepreneurs dans leurs projets d'application.

Avec mon expérience personnelle de plus de 15 ans en ESN, j'ai pu travailler pour un large panel d'entreprises de différentes tailles. Ma compréhension des problèmes métiers est une de mes grandes forces et permet à mes clients de pouvoir se projeter plus facilement.

L'essentiel de mon travail consiste à canaliser l'énergie des entrepreneurs sur l'essence même de leur projet.

La technologie, les méthodes, le management sont le cœur de mes compétences.

Vous pouvez me faire confiance sur ces points là.

Si vous voulez me parler d'un de vos projets, n'hésitez pas à m'envoyer un email avec vos disponibilités à : contact@mathieugrenier.fr


Tous les articles de ce blog sont écrits par moi, même si je peux m'aider de l'IA pour illustrer mes propos. Mais jamais je ne fournis d'articles 100% IA.


Mathieu Grenier 3 mars 2026
Partager cet articlE