Concevoir une state machine pour agent IA : pourquoi un workflow complexe ne peut pas dépendre du prompt

"La documentation LangGraph Persistence décrit les checkpoints comme des graph state snapshots scoped par thread et explique qu'ils soutiennent conversation continuity, human-in-the-loop, time travel et fault tolerance."
Un agent de reporting a échoué juste avant l’envoi de l’e-mail à l’étape 5. L’équipe d’exploitation a relancé la tâche. L’agent est reparti de l’étape 1, a généré un nouveau rapport et a écrasé la version déjà approuvée. L’état d’approbation a disparu. La trace signée par l’approbateur a été remplacée par le nouveau résultat, et aucun log ne permettait de prouver que la première version avait bien été validée.
Ce n’était ni un rollback de base de données, ni un retry de file de messages. Dans le prompt, il ne restait qu’une phrase : “continuer le traitement”. Le modèle a ré-inféré tout le workflow sans savoir que les étapes 1 à 4 avaient déjà produit des effets externes : appel de l’API d’approbation, génération du rapport, écriture d’un fichier temporaire. Le point d’échec était l’étape 5, mais les effets externes avaient commencé à l’étape 2.
Le vrai problème n’était pas la capacité du modèle. La progression de la tâche était cachée en langage naturel dans le prompt, sans snapshot d’état récupérable. Les messages portés par un prompt sont du contexte de modèle, pas des faits d’exécution.
Pour corriger ce type d’incident, ajouter une phrase au prompt du genre “vérifier la progression avant de continuer” ne suffit pas. La solution plus robuste consiste à écrire le nœud courant, les effets déjà produits, l’action suivante et la compensation d’échec dans une table d’état récupérable.
Points clés de l’incident
Flux d’exécution de l’agent de reporting :
| Étape | Opération | Effet externe | Idempotence |
|---|---|---|---|
| Étape 1 | Requête de données | Appel de la base de données et lecture des données utilisateur | Idempotent (lecture) |
| Étape 2 | Génération du rapport | Appel de l’outil de reporting et génération du PDF | Non idempotent (écrase un fichier) |
| Étape 3 | Attente d’approbation | Envoi d’une demande d’approbation et attente d’un humain | Idempotent (API compatible) |
| Étape 4 | Approbation reçue | Réception de l’event approve | Idempotent (lecture de statut) |
| Étape 5 | Envoi d’e-mail | Appel de l’API e-mail et envoi du rapport | Échec (timeout) |
Cause de l’échec : l’envoi d’e-mail à l’étape 5 a expiré à cause d’une limite de l’API externe, et la tâche a été marquée FAILED.
Logique de relance : lire la “progression actuelle” dans le prompt. Le prompt ne disait que “approuvé, continuer le traitement”. Exécution réelle : repartir de l’étape 1 -> régénérer le rapport à l’étape 2 (écrasement de la version approuvée) -> redemander l’approbation à l’étape 3 -> envoyer avec succès à l’étape 5.
Impact métier : le rapport approuvé a été remplacé, les enregistrements d’approbation ne correspondaient plus au rapport livré, l’utilisateur a signalé que le rapport approuvé et le rapport reçu étaient différents, et le processus d’approbation a été gaspillé : deux versions approuvées, une seule envoyée.
Tableau de détection des anti-patterns
Vérifiez si votre agent tombe dans ces anti-patterns :
| Anti-pattern | Symptôme | Risque caché | Correction |
|---|---|---|---|
| Progression écrite dans le Prompt | Résumé en langage naturel, par exemple “actuellement à l’étape 3” | Perdu après redémarrage, non récupérable | Enregistrer le nœud courant dans un champ State |
| Trace pris pour State | Une trace complète donne l’impression d’avoir un état | La trace ne décide pas de l’étape suivante | Le State enregistre ce qui doit arriver ensuite |
| Retry sans contrôle d’idempotence | En cas d’échec, tout recommencer | Les effets externes se répètent | Clé d’idempotence + vérification déjà exécutée |
| Resume après approval sans vérification | Continuer directement | Retour au mauvais point d’exécution | checkpoint + thread_id |
1. Bases de la State Machine : State, Event, Transition, Guard, Action
Une state machine n’est pas nécessaire pour tous les agents. Un simple support Q&A peut fonctionner avec un tableau de messages. Mais une tâche complexe, avec plusieurs étapes, une approbation, des appels à des systèmes externes et une reprise après échec, doit rendre la progression explicite.
1.1 Tableau des termes clés
Les termes de base viennent de la documentation Stately :
| Terme | Définition | Exemple côté Agent | Source |
|---|---|---|---|
| State | Mode dans lequel se trouve la machine, avec une intention sémantique unique | INIT, PLAN_READY, TOOL_RUNNING, APPROVAL_PENDING, FAILED, COMPLETED | Stately state machines |
| Event | Signal externe qui déclenche un changement d’état | timeout, approve, reject, retry, resume, task_received | Stately state machines |
| Transition | Chemin autorisé entre deux états, sous forme de mapping déterministe | INIT -> PLAN_READY (event: task_received) | Stately state machines |
| Guard/Condition | Précondition pour entrer dans un état | Entrer dans TOOL_RUNNING uniquement si le budget est suffisant | Stately state machines |
| Action | Opération exécutée pendant une transition | Appeler un outil en entrant dans TOOL_RUNNING | Stately state machines |
| Checkpoint | Snapshot d’état utilisé pour la récupération | Un checkpointer LangGraph sauvegarde le graph state | LangGraph Persistence |
Principe de déterminisme : une même combinaison State + Event doit pointer vers un seul next state afin d’éviter l’ambiguïté. Ensemble fini d’états : une state machine n’est pas un flowchart infini, mais un ensemble fini d’états atteignables plus des règles de transition explicites.
1.2 Comparaison Trace vs State vs Audit
Trace, Audit Log et State Snapshot répondent à trois problèmes différents :
| Concept | Problème résolu | Est-ce un état métier ? | Décide-t-il de l’étape suivante ? | Exemple côté Agent |
|---|---|---|---|---|
| Trace | Ossature d’observabilité et de diagnostic | Non | Non | Trace OpenAI Agents SDK (workflow_name, trace_id) |
| Audit Log | Historique de conformité et traçabilité d’audit | Non | Non | Champs d’audit du modèle de permissions (actor, traceId, action, result) |
| State Snapshot | État courant qui décide de l’étape suivante | Oui | Oui | Checkpoint LangGraph (nœud courant, étapes exécutées, prochaine action) |
La différence est centrale : une trace aide à observer ce qui s’est passé, mais ce n’est pas l’état métier. Un audit log garde l’historique de conformité. Un state snapshot décide ce qui doit se passer ensuite, et c’est le cœur de la récupération. Ces objets ne se remplacent pas : avoir une trace ne signifie pas avoir un state ; avoir un audit log ne signifie pas pouvoir récupérer.
2. Comment LangGraph gère la persistance d’état
Un checkpoint n’est pas un résumé en langage naturel dans le prompt. C’est un snapshot d’état récupérable, inspectable et rejouable. La documentation LangGraph persistence définit un checkpoint comme un graph state snapshot qui contient l’état complet et les nœuds à exécuter ensuite.
2.1 Checkpointer et Thread State
Mécanismes clés (documentation LangGraph Persistence) :
- Checkpointer : sauvegarde les snapshots d’état scoped par thread (graph state snapshots)
- Store : sauvegarde les données long terme entre threads (application-defined store)
- Thread_id : point d’entrée unique pour récupérer l’état d’un thread précis
- Quatre usages : conversation continuity, human-in-the-loop, time travel, fault tolerance
LangGraph persistence place l’état court terme scoped par thread dans les checkpointers, et les données long terme entre threads dans les stores. Un checkpoint inclut le state snapshot et l’application-defined store. Thread_id est l’entrée de récupération : avec la même thread_id, on peut continuer depuis le point de pause.
Un checkpoint LangGraph contient graph state, la liste des nœuds à exécuter ensuite, checkpoint_id, timestamp et version. Les données sensibles ne doivent pas entrer aveuglément dans le checkpoint : certains champs de graph state peuvent contenir des informations sensibles et doivent être explicitement exclus de la persistance.
2.2 Interrupts et mécanisme de récupération
Mécanismes clés (documentation LangGraph Interrupts) :
- interrupt() : met dynamiquement en pause l’exécution dans un nœud du graphe, sauvegarde le graph state et attend une entrée externe
- Méthode de reprise : utiliser la même thread_id et Command(resume=…)
- Patterns courants : approval, review/edit, tool call review, human input validation
- Avertissement sur les effets idempotents : les effets avant interrupt doivent être idempotents, car à la reprise le nœud redémarre au début du nœud qui a appelé interrupt
Une pause d’approbation doit être un état de pause dans la state machine, pas une consigne laissée au modèle pour “se souvenir d’attendre l’approbation”. La reprise exige le même thread cursor.
La reprise utilise la même thread_id et Command(resume=…). L’idempotence des effets externes est une condition préalable. Si un effet externe précède l’approbation, par exemple un appel d’API externe, il doit être idempotent ; sinon, le nœud repris appellera de nouveau l’API.
3. Analogie d’ingénierie : Temporal Durable Execution
La fiabilité des tâches longues n’est pas un problème nouveau. Temporal durable execution offre une analogie solide.
3.1 Définition de Durable Execution
Concepts clés (documentation Temporal Durable Execution) :
- Durable Execution : une workflow execution conserve state/progress malgré les échecs, crashs ou interruptions de service
- Event History : enregistre l’état de chaque étape afin de reprendre depuis le dernier event enregistré après un échec
- Trois propriétés : Resumable, Recoverable, Reactive
La fiabilité d’une tâche longue vient de l’event history et d’une exécution récupérable, pas de la mémoire d’un processus unique ni du contexte du prompt. Une state machine d’agent a besoin d’un mécanisme comparable : checkpoint/event log + état métier, pas seulement une nouvelle inférence du modèle.
L’Event History de Temporal et le checkpoint de LangGraph sont proches conceptuellement : ils enregistrent l’historique d’exécution et permettent de reprendre depuis le point d’échec. La différence est que Temporal est un moteur complet de workflow, tandis que LangGraph est un framework de gestion d’état pour agents. La leçon à retenir : durable execution exige un historique d’état structuré, pas la mémoire du process ni le contexte du modèle.
4. Modèle de table d’état : une Agent State Table réutilisable
Les concepts de state machine sont abstraits. Pour les appliquer, il faut un modèle d’état concret. Voici trois modèles : table d’état, table d’événements et exemple tiré de l’incident.
4.1 Modèle de table d’état (bloc d’étapes exécutable)
Structure du modèle :
| State | Event | Guard | Action obligatoire | Next |
|---|---|---|---|---|
| INIT | task_received | Aucun | Initialiser le contexte et enregistrer l’heure de début | PLAN_READY |
| PLAN_READY | plan_generated | plan_valid | Générer le plan d’exécution et enregistrer la séquence d’outils | TOOL_RUNNING |
| TOOL_RUNNING | tool_completed | budget_sufficient | Appeler l’outil, enregistrer le résultat et mettre à jour le budget | APPROVAL_PENDING ou COMPLETED |
| APPROVAL_PENDING | approve | approval_required | Envoyer la demande d’approbation et enregistrer l’approbateur | COMPLETED |
| APPROVAL_PENDING | reject | Aucun | Enregistrer le motif de rejet et notifier l’utilisateur | FAILED |
| FAILED | retry | retry_count < max | Vérifier l’idempotence et revenir au checkpoint précédent | TOOL_RUNNING ou APPROVAL_PENDING |
| COMPLETED | Aucun | Aucun | Enregistrer l’heure de fin et nettoyer les ressources | Terminal |
Explication : la colonne State définit les états atteignables (INIT, PLAN_READY, TOOL_RUNNING, APPROVAL_PENDING, FAILED, COMPLETED). La colonne Event définit les événements qui déclenchent les transitions (task_received, approve, reject, retry). La colonne Guard définit les préconditions (budget_sufficient, retry_count < max). La colonne Action définit les opérations obligatoires pendant la transition. La colonne Next définit la transition déterministe.
4.2 Modèle de table d’événements (complément de la table d’état)
Structure du modèle :
| Event | Condition de déclenchement | État préalable requis | État après | Produit un effet externe ? |
|---|---|---|---|---|
| task_received | L’utilisateur soumet une tâche | INIT | PLAN_READY | Non |
| plan_generated | Le LLM génère un plan d’exécution | PLAN_READY | TOOL_RUNNING | Non |
| tool_completed | L’outil termine son exécution | TOOL_RUNNING | APPROVAL_PENDING ou COMPLETED | Oui (appel d’API externe) |
| approve | L’approbateur valide | APPROVAL_PENDING | COMPLETED | Oui (envoi d’e-mail, débit de budget) |
| reject | L’approbateur refuse | APPROVAL_PENDING | FAILED | Non |
| retry | Demande de retry après échec | FAILED | TOOL_RUNNING ou APPROVAL_PENDING | Vérification d’idempotence requise |
| timeout | Timeout d’exécution | TOOL_RUNNING | FAILED | Non |
Explication : l’état préalable rend explicite dans quels états un event peut être reçu. La colonne des effets externes marque les events qui nécessitent idempotence ou compensation.
4.3 Exemple de table d’état tiré de l’incident de rapport écrasé
Exemple complet : state table de l’agent de reporting déduite de l’incident d’ouverture
| State | Event | Guard | Action | Next | Vérification d’idempotence/compensation |
|---|---|---|---|---|---|
| INIT | task_received | Aucun | Initialiser thread_id et enregistrer l’heure de début | QUERY_RUNNING | Inutile |
| QUERY_RUNNING | query_completed | Aucun | Interroger les données et sauvegarder le résultat dans state | REPORT_GENERATING | Inutile |
| REPORT_GENERATING | report_generated | Aucun | Générer le rapport et sauvegarder le report ID dans state | APPROVAL_PENDING | Vérification d’idempotence : si le rapport existe déjà, sauter la génération |
| APPROVAL_PENDING | approve | Aucun | Enregistrer l’approbateur et l’heure d’approbation | EMAIL_SENDING | Inutile |
| APPROVAL_PENDING | reject | Aucun | Enregistrer le motif de rejet | FAILED | Inutile |
| EMAIL_SENDING | email_sent | Aucun | Envoyer l’e-mail et enregistrer l’email ID | COMPLETED | Vérification d’idempotence : si l’e-mail est déjà envoyé, sauter |
| EMAIL_SENDING | timeout | retry_count < 3 | Enregistrer l’échec et vérifier l’idempotence | EMAIL_SENDING (retry) ou FAILED | Clé d’idempotence : email_id + thread_id |
| FAILED | retry | retry_count < max | Vérifier l’idempotence et récupérer depuis le checkpoint précédent | QUERY_RUNNING ou REPORT_GENERATING ou EMAIL_SENDING | Décider le point de reprise selon le checkpoint |
| COMPLETED | Aucun | Aucun | Enregistrer l’heure de fin et nettoyer les ressources | Terminal | Inutile |
Correction de l’incident : si l’étape 5 échoue (EMAIL_SENDING -> timeout), la reprise doit partir de EMAIL_SENDING, pas de QUERY_RUNNING. Le checkpoint doit enregistrer le nœud courant (EMAIL_SENDING), les étapes déjà exécutées (QUERY, REPORT_GENERATED, APPROVAL_APPROVED) et la prochaine action (EMAIL_SENDING). La génération du rapport et l’envoi d’e-mail exigent des clés d’idempotence pour éviter les doublons.
5. Idempotence et compensation : récupérer ne se limite pas au checkpoint
Avoir un checkpoint ne signifie pas que tous les effets externes sont récupérables en sécurité. La reprise exige aussi idempotence, transactions, compensation et vérification de l’état du système externe.
5.1 Concepts d’idempotence et de compensation
Définitions :
- Idempotent : plusieurs exécutions produisent le même résultat sans créer d’effet externe en double
- Compensation : annuler un effet externe déjà produit afin de restaurer la cohérence
- Rollback transactionnel : une opération atomique s’annule automatiquement en cas d’échec
- Vérification d’état externe : vérifier l’état du système externe avant la reprise afin d’éviter une opération en double
Les trois piliers de la cohérence d’état : identité d’idempotence (action_id + schema_hash), chaîne de state snapshots (snapshot + prev_hash + delta), action de compensation enregistrée (undo_op).
5.2 Checklist idempotence et compensation
Pour décider quelles opérations exigent idempotence ou compensation :
| Type d’opération | Idempotence nécessaire ? | Compensation nécessaire ? | Conception de clé d’idempotence | Plan de compensation |
|---|---|---|---|---|
| Requête de données (sans effet externe) | Non | Non | - | - |
| Génération de rapport (écrase un fichier) | Oui | Oui | report_id + thread_id | Supprimer le nouveau rapport et restaurer la version approuvée |
| Envoi d’e-mail (API externe) | Oui | Difficile | email_id + thread_id | Envoyer un e-mail de correction ou d’annulation dans certains cas |
| Déduction de stock (base de données) | Oui | Oui | inventory_id + order_id | Réajouter le stock |
| Création de ticket (système externe) | Oui | Oui | ticket_id + thread_id | Fermer le ticket |
| Déduction de budget (état interne) | Oui | Oui | budget_id + thread_id | Réajouter le budget |
| Envoi de demande d’approbation (sans effet durable) | Non | Non | - | - |
Logique de décision : la création d’un effet externe détermine le besoin d’idempotence. Les opérations réversibles ont besoin de compensation. Les appels inter-systèmes doivent inclure un identifiant du système externe dans la clé d’idempotence. Les opérations atomiques peuvent s’appuyer sur un rollback transactionnel.
La récupération n’est pas seulement un checkpoint. Elle exige idempotence, transactions, compensation et vérifications d’état externe. Dire qu’un checkpoint suffit à récupérer tous les effets externes en sécurité est inexact.
6. Checklist des états de tâche Agent : récupérable vs non récupérable
Tous les checkpoints ne permettent pas une reprise. Un terminal state est l’état final d’une workflow execution : terminé, échoué, expiré ou annulé. Un terminal state ne se reprend pas ; il se relance ou se compense.
6.1 Tableau de classification des états
| Type d’état | Récupérable ? | Condition de récupération | Méthode de récupération | Exemple |
|---|---|---|---|---|
| Failed | Oui | retry_count < max | Reprendre depuis le checkpoint précédent | Timeout d’appel d’outil |
| Retry | Oui | Vérification d’idempotence réussie | Réexécuter depuis le nœud en échec | Échec d’envoi d’e-mail |
| Compensation | Partiellement | Un plan de compensation existe | Exécuter undo_op | Échec de déduction de stock |
| Approval Pause | Oui | event approve/reject | Command(resume=…) | Attente d’approbation |
| Terminal | Non | Aucune | Aucun chemin de reprise | COMPLETED, FAILED (retry_count = max) |
Explication : un état Failed peut reprendre par retry si retry_count < max. Un état Retry exige un contrôle d’idempotence et réexécute depuis le nœud en échec. Un état Compensation est partiellement récupérable si un plan de compensation existe. Un état Approval Pause reprend via un event approve/reject. Un Terminal State n’est pas récupérable, par exemple COMPLETED ou FAILED après le nombre maximal de retries.
7. Pour aller plus loin
La state machine n’est qu’un point de départ. Le modèle d’état doit coller au scénario métier : chaque tâche a sa granularité d’état et sa stratégie de récupération.
Navigation dans la série
| Article | Relation | Lien |
|---|---|---|
| Human-in-the-loop Agent : quelles étapes doivent passer par une approbation humaine | Détails de la pause d’approbation | /blog/fr/posts/ai/20260707-human-in-the-loop-agent-approval-design/ |
| Contrôle des coûts Agent : model routing, budget d’outils et retry d’échec | Budget et stratégie de retry | /blog/fr/posts/ai/20260707-agent-cost-control-model-routing-tool-budget-cache-retry/ |
| Gestion d’état LangGraph en pratique : bonnes pratiques d’architecture Agent 2026 | Gestion d’état LangGraph | /blog/fr/posts/ai/20260424-langgraph-agent-architecture/ |
| Monitoring, alerting et récupération d’échec pour AI Agent : des logs à la state machine | Monitoring et récupération | /blog/fr/posts/ai/20260527-ai-agent-monitoring-recovery/ |
| LangGraph vs AutoGen : suivi d’état | Comparaison de frameworks | /blog/fr/posts/ai/20260526-langgraph-autogen-state-tracking/ |
| Jeux de données d’évaluation Agent et tests de régression : éviter qu’un changement casse tout | Évaluation et tests de régression | À venir, prochain article de la série |
Références externes
Sources à forte confiance :
| Source | Confiance | Sujet | Lien |
|---|---|---|---|
| Documentation LangGraph Persistence | high | Checkpointer, Store, Thread State, Checkpoint | https://docs.langchain.com/oss/python/langgraph/persistence |
| Documentation LangGraph Interrupts | high | interrupt(), Command(resume=…), thread_id | https://docs.langchain.com/oss/python/langgraph/interrupts |
| Documentation Temporal Durable Execution | high | Event History, Durable Execution, Resumable/Recoverable | https://docs.temporal.io/temporal |
| Documentation OpenAI Agents SDK Tracing | high | Trace, Span, workflow_name, trace_id | https://openai.github.io/openai-agents-python/tracing/ |
| Documentation AWS Step Functions State Machines | high | State Machine, Flow State, Task State, StartAt, Next | https://docs.aws.amazon.com/step-functions/latest/dg/concepts-statemachines.html |
| Stately: State machines and statecharts | medium | State, Event, Transition, Guard, Action, Hierarchy | https://stately.ai/docs/state-machines-and-statecharts |
Une state machine n’est pas nécessaire pour tous les agents, mais les tâches complexes doivent rendre leur progression explicite. La prochaine étape n’est pas d’ajouter plus de frameworks. Elle consiste à concevoir les bons State, Event, Transition, Guard et Action pour votre scénario métier, puis à sortir la progression de la tâche du langage naturel du prompt pour la placer dans un état structuré.
Concevoir la state machine d'un agent IA complexe
Découper une tâche d'agent IA complexe en state, event, guard, action, checkpoint, retry, compensation et terminal state afin que la progression ne soit pas cachée uniquement dans le prompt.
⏱️ Estimated time: 45 min
- 1
Step 1: Lister les points à risque
Listez les effets externes, les points de pause humaine, les points d'échec et les conditions terminales de la tâche. - 2
Step 2: Définir l'ensemble minimal d'états
Définissez l'ensemble minimal d'états utiles : pending, running, waiting_approval, retrying, compensating, succeeded, failed, cancelled. - 3
Step 3: Relier events et états suivants
Pour chaque state, écrivez les events acceptés et le next state produit par chaque event. - 4
Step 4: Ajouter les conditions de guard
Ajoutez des guards aux transitions dangereuses : permission, budget, approval, clé d'idempotence et état de ressource externe. - 5
Step 5: Isoler les actions d'outils
Placez les appels d'outils dans la couche action et enregistrez le résumé d'input, le résumé d'output, le traceId et le résultat de l'effet externe. - 6
Step 6: Définir les politiques d'échec
Définissez la retry policy, le terminal state et la compensation policy pour chaque chemin d'échec. - 7
Step 7: Persister la base de récupération
Définissez un checkpoint ou un event log pour la récupération, et traitez le prompt comme un contexte temporaire plutôt que comme l'unique source de vérité.
FAQ
Si l'agent échoue à l'étape 5, faut-il repartir de l'étape 1 ou continuer depuis un checkpoint ?
L'état de la tâche doit-il vivre dans le prompt, une base de données, un checkpoint LangGraph ou un job de queue ?
Quelle est la différence entre une state machine et un diagramme de workflow ?
Comment garantir qu'un agent revient au même point d'exécution après approval ?
Les règles de retry et de compensation doivent-elles être dans le prompt ou dans les règles de transition d'état ?
Un agent de support client simple a-t-il besoin d'une state machine ?
14 min de lecture · Publié le: 17 sept. 2026
Guide d'ingénierie AI Agent
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Modèle d'autorisation pour agent IA : identité utilisateur, droits d'outils, journaux d'audit et isolation des secrets
Avant de connecter un agent IA à de vrais outils, concevez son modèle d'autorisation : identité utilisateur, service account, per-tool permission, scopes, secret vault, rotation des clés, approval policy, data boundary et audit log.
Partie 21 sur 22
Suivant
C’est le dernier article publié dans cette série pour le moment.



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire