Concevoir un agent Human-in-the-loop : quelles étapes exigent une approbation humaine ?

"La documentation Human-in-the-loop de l'OpenAI Agents SDK décrit les tools nécessitant approval, les interruptions de run et la reprise via RunState après approve ou reject."
Un brouillon de message Feishu est prêt : titre, corps et lien de pièce jointe sont remplis. Il ne reste qu’une étape : l’envoyer. Mais l’agent s’arrête avant send_message et attend votre confirmation.
Un e-mail peut être généré automatiquement, mais avant l’envoi au client il faut montrer le destinataire, le sujet, le résumé du corps et les pièces jointes. Un formulaire CMS peut être rempli, mais submit_form est bloqué par une policy et attend l’approval du owner. Cela ressemble à un bouton de confirmation, pourtant la vraie frontière est l’état d’exécution. Le RunState de l’agent est sauvegardé, puis l’exécution reprend seulement après une décision humaine. Où pauser, qui approuve, quoi faire en cas de rejet ou de timeout : ce n’est pas du frontend, c’est la frontière de sécurité du système de tools.
Ce guide propose une matrice de risque, une checklist de points d’approbation, un mécanisme pause/resume et un modèle de champs d’audit. L’objectif est de passer de « ajouter un bouton confirmer » à un état sérialisable, reprenable et auditable.
Matrice de risque : décider quelles actions exigent une approbation
Toutes les actions ne nécessitent pas d’approbation. Les lectures peuvent s’exécuter seules ; les suppressions et paiements doivent attendre un humain. On peut décider avec cinq dimensions :
| Dimension | Critère | Exemples d’actions |
|---|---|---|
| Impact externe | Touche un système ou un utilisateur externe | Envoyer un e-mail, soumettre un formulaire, appeler une API externe, écrire dans Feishu/Slack |
| Réversibilité | L’action peut-elle être annulée ? | Supprimer un record (irréversible), enregistrer un brouillon (réversible), paiement (partiellement compensable), envoyer un message (irréversible) |
| Sensibilité des données | Niveau de données manipulées | Lire des données publiques, modifier des records internes, exporter des données privées, lire une config de production |
| Seuil financier/droits | Implique argent ou changement de droits | Paiement, virement, remboursement, changement de droits, opération en masse, suppression de données utilisateur |
| Niveau d’autonomie | Niveau d’automatisation autorisé | Lecture seule (auto), brouillon (auto), message sortant (confirmation), suppression/paiement (approval) |
Cette matrice reprend l’esprit d’OWASP LLM06 : fonctionnalités excessives, permissions excessives et autonomie excessive. Vous pouvez l’utiliser telle quelle ou ajuster les seuils au métier.
Impact externe : tout ce qui atteint une personne ou un système externe mérite prudence. Un e-mail ne se désenvoie pas. Un formulaire peut créer une commande. Une API externe peut modifier les données d’un autre système.
Réversibilité : un record supprimé est perdu ; un brouillon se corrige ; un paiement peut nécessiter remboursement ou compensation.
Sensibilité des données : lire du public est généralement acceptable ; écrire dans l’interne doit être contrôlé ; les données personnelles et la configuration production exigent approbation.
Seuil financier/droits : argent et droits doivent faire pause. Paiements, virements, remboursements, changements de droits et opérations en masse sont des points à haut risque.
Niveau d’autonomie : les lectures peuvent être automatiques. Les brouillons aussi. Les sorties externes exigent confirmation ; suppression et paiement exigent approval.
La matrice évolue. Une notification Feishu interne peut descendre en confirmation ; un paiement réel reste approval + revue à deux personnes.
Trois scénarios concrets de classification
Cas 1 : brouillon de message Feishu
Écrire un brouillon Feishu est automatique (L0). Il reste dans les brouillons, n’est pas envoyé, reste réversible et n’a pas d’impact externe. Appeler send_message vers un client exige approval (L2). Le message est irréversible, sort vers un utilisateur externe et peut contenir des données sensibles. La confirmation avant MCP tools/call correspond à ce cas.
Cas 2 : envoi d’e-mail
Générer le contenu de l’e-mail est automatique (L0). Ce n’est encore que du texte. L’appel à l’API d’envoi exige approval avec destinataire, sujet et pièces jointes (L2). L’UI doit montrer un résumé et des preuves, pas seulement « confirmer l’envoi ».
Cas 3 : soumission de formulaire CMS
Remplir le formulaire est automatique (L0). Il n’est pas soumis. L’appel à l’API CMS pour soumettre doit être bloqué et attendre owner approve (L2). Le blocage peut venir d’un guardrail, par exemple « montant au-dessus du seuil », ou d’une policy statique.
Cas 4 : suppression en base de production
Interroger la base de production est automatique (L0). C’est une lecture. Supprimer via API en production exige approbation forte + audit de backup (L3). C’est irréversible, sensible et visible par les utilisateurs. Il faut une règle Policy, pas seulement un Guardrail.
Le point clé : dans une même tâche, chaque étape porte un risque différent. Le brouillon est automatique, l’envoi exige approval, la suppression production exige revue à deux personnes. Classez les actions concrètes.
Checklist des points d’approbation : descendre au type d’action
Une fois la matrice prête, définissez les niveaux :
L0 automatique : requêtes de base, recherche vectorielle, lecture de configuration ; enregistrement de brouillon ou génération de prévisualisation. Pas d’impact externe, réversible, pas de données sensibles.
L1 confirmation : messages ou données sortants, comme e-mail, formulaire, API externe ; lectures en masse comme export ou requêtes batch. Impact externe, mais risque contrôlable.
L2 approval : suppression, changement de droits, suppression en masse ; écriture dans Feishu, Slack ou CRM. Ces actions sont irréversibles ou visibles à l’extérieur.
L3 strong approval + revue à deux personnes : paiement, transfert, remboursement ; export de données personnelles, modification de config production, suppression de base production. Argent ou données sensibles.
Cette liste s’aligne avec needs_approval dans l’OpenAI Agents SDK et avec les recommandations de sécurité MCP. La spec MCP demande des tool calls visibles, refusables et confirmés pour les opérations sensibles.
Adaptez la liste :
Si Feishu sert seulement à une notification interne, L1 suffit.
Si une suppression touche des données utilisateur, gardez L2.
Si le paiement est critique, passez à L3 avec raison obligatoire et revue à deux.
La liste n’est pas figée. Si la règle métier change, « envoyer un message » peut sortir du périmètre approval.
Machine d’état du flux d’approbation : pauser, sauvegarder, reprendre
L’approbation n’est pas une fenêtre UI. C’est un run pausé. Quand un tool call exige approval, le RunState est sauvegardé et l’exécution reprend après décision.
Diagramme de transition d’état
Le flux est :
request -> pending -> approved/rejected/timeout -> resume/abort/compensate
request : le tool call crée une demande. RunState contient tool, arguments et contexte.
pending : le run attend une décision. L’état est sauvegardé en checkpoint et relié à un thread_id.
approved : la décision passe, on reprend au checkpoint et on appelle le tool.
rejected : le run part vers abort ou convert to draft.
timeout : la demande expire, puis escalate ou auto-reject.
resume/abort/compensate : reprendre, arrêter ou compenser.
Modes de reprise
approve : continuer, appeler le tool, puis passer à la suite.
reject : arrêter ou transformer en brouillon, sans appeler le tool.
edit : modifier les paramètres, par exemple destinataire ou contenu, puis redemander validation.
Checkpoint et thread state sont la base technique. L’article LangGraph checkpoint/thread state déjà publié explique comment sauvegarder le point d’arrêt et y revenir.
Exemple de code OpenAI Agents SDK HITL
Ce code illustre le flux d’approbation OpenAI Agents SDK. Les API peuvent changer ; vérifiez la documentation officielle avant production :
from agents import Agent, Runner, function_tool
@function_tool(needs_approval=True)
def send_email(to: str, subject: str, body: str) -> str:
return send_email_handler(to=to, subject=subject, body=body)
agent = Agent(
name="EmailAgent",
tools=[send_email],
instructions="Rédiger l'e-mail puis attendre l'approbation avant l'envoi",
)
result = Runner.run_sync(agent, "Rédigez un e-mail de remboursement pour le client")
if result.interruptions:
state = result.to_state()
for interruption in result.interruptions:
print(f"Tool en attente d'approbation : {interruption.tool_name}")
print(f"Arguments : {interruption.arguments}")
decision = show_approval_ui(interruption)
if decision == "approve":
state.approve(interruption)
elif decision == "reject":
state.reject(interruption)
result = Runner.run_sync(agent, state)
Points clés :
needs_approval=True marque le tool comme soumis à approval.
interruptions contient les tool calls en attente.
result.to_state() convertit le résultat pausé en RunState sérialisable.
state.approve() ou state.reject() enregistre la décision.
Runner.run_sync(agent, state) reprend depuis le point d’arrêt.
Les champs peuvent changer après 2026-07 ; vérifiez la documentation officielle.
Exemple de code LangGraph interrupt/resume
Ce code montre interrupt et Command(resume=...) dans LangGraph :
from langgraph.graph import StateGraph, MessagesState
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command, interrupt
def send_email_node(state: MessagesState):
approved = interrupt({
"action": "send_email",
"summary": state["email_summary"],
})
if approved != "approved":
return {"messages": ["L'envoi de l'e-mail a été refusé ; le message est enregistré comme brouillon"]}
email_result = send_email(state["email_params"])
return {"messages": [email_result]}
graph = StateGraph(MessagesState)
graph.add_node("send_email", send_email_node)
graph.add_edge("draft_email", "send_email")
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
thread_id = "thread_123"
config = {"configurable": {"thread_id": thread_id}}
result = app.invoke(
{"messages": ["Rédigez une notification de remboursement pour le client"]},
config=config,
)
# Après la pause, le payload interrupt revient à l'appelant.
# Affichez l'UI d'approbation et attendez une décision humaine.
decision = show_approval_ui(result["__interrupt__"])
if decision == "approve":
app.invoke(Command(resume="approved"), config=config)
elif decision == "reject":
app.invoke(Command(resume="rejected"), config=config)
elif decision == "edit":
app.update_state(config, {"email_params": {"to": "[email protected]"}})
app.invoke(Command(resume="approved"), config=config)
Points clés :
interrupt() pause le graph.
Command(resume=...) reprend l’exécution.
checkpoint + thread_id gardent l’état cohérent.
approve/reject/edit sont trois chemins de reprise.
Vérifiez aussi l’API LangGraph avant production.
Champs de preuve d’approbation : quoi stocker et comment tracer
L’approbation est une décision, mais aussi une trace. Le minimum d’un audit log :
| Champ | Description | Exemple |
|---|---|---|
| tool_name | Nom du tool + type d’opération | send_email / delete_record |
| tool_arguments | JSON complet des arguments | {“to”: “[email protected]”, “subject”: “Avis de remboursement”} |
| invoker_id | Identité appelante | [email protected] / agent_run_abc123 |
| request_time | Heure de la demande | 2026-06-23T09:26:10Z |
| approver_id | Identité de l’approbateur | [email protected] |
| decision_time | Heure de décision | 2026-06-23T09:35:12Z |
| decision | Résultat | approved / rejected / timeout_auto_reject |
| evidence | Preuve : capture ou résumé | ”Destinataire correct, contenu sans donnée sensible” |
| audit_trail_id | Lien vers les logs du run | run_abc123_step_5_tool_3 |
Ces champs viennent des recommandations d’audit MCP Tools et des items OpenAI API comme mcp_approval_request. Les logs d’approbation font partie de l’observabilité ; l’article Agent monitoring/recovery déjà publié couvre le logging plus large.
Comment sérialiser RunState ? Avec l’OpenAI Agents SDK, result.to_state() convertit un résultat pausé en RunState. LangGraph utilise checkpoint + thread_id. Stockez l’état sérialisé dans une base ou un système de logs et reliez-le à audit_trail_id.
Les audit logs servent à trois choses :
Traçage d’incident : si une fuite est découverte, on sait qui a approuvé quoi et quand.
Preuve de conformité : en entreprise, il faut démontrer qu’une action risquée a eu une approbation humaine.
Amélioration de policy : suivez les actions souvent approuvées, rejetées ou expirées pour ajuster les règles.
Le schéma peut s’étendre : durée, canal Slack/Feishu/e-mail, revue à deux personnes. Mais le minimum doit rester.
Rejet et timeout : que faire quand l’approbation échoue
L’approbation ne passe pas toujours. Reject et timeout doivent avoir des chemins explicites pour éviter un run bloqué.
Trois chemins après rejet
Chemin 1 : continue with fallback. Utiliser une action moins risquée. Si l’envoi d’e-mail est refusé, sauvegarder un brouillon et continuer.
Chemin 2 : convert to draft. Transformer l’action en brouillon. Si la soumission CMS est refusée, enregistrer un brouillon pour correction humaine.
Chemin 3 : abort task. Arrêter toute la tâche. Si la suppression de base production est refusée, le run doit s’arrêter.
Choix selon le type :
Action réversible : fallback ou convert to draft.
Action irréversible à haut risque : abort task.
Besoin d’intervention humaine : escalate.
Deux chemins après timeout
Chemin 1 : escalate to backup approver. Si l’approbateur principal ne répond pas après 30 minutes, envoyer à l’on-call engineer.
Chemin 2 : auto-reject. Après une heure, rejeter automatiquement et arrêter la tâche. Utile pour un risque plus bas mais sensible au temps.
Choix selon le contexte :
Haut risque : escalader, ne pas exécuter automatiquement.
Temps critique : auto-reject pour ne pas bloquer indéfiniment.
Cas général : escalader et laisser plus de temps.
Revenir sur les étapes déjà exécutées
Après un rejet, certaines étapes peuvent déjà être faites. Si l’agent a créé une commande avant le refus du paiement, il faut l’annuler.
Stratégies :
Checkpoint rollback : revenir au checkpoint avant approval et jeter les étapes suivantes.
Transaction compensatoire : appeler une API de compensation, par exemple annuler une commande.
Intervention humaine : notifier quelqu’un quand l’automatisation ne suffit pas.
Le rollback ne marche pas toujours. Un e-mail envoyé ne peut pas être retiré. Dans ce cas, on garde l’audit log et on traite l’incident ensuite.
Quatre frontières de sécurité : combiner Policy, Guardrail, Approval et Audit
L’approbation ne suffit pas seule. Policy, Guardrail, Approval et Audit se complètent sans se remplacer.
Tableau des responsabilités en quatre couches
| Couche | Responsabilité | Exemple |
|---|---|---|
| Policy | Règles statiques qui limitent le périmètre des tools | ”Interdire la suppression de la base production”, “outil paiement seulement en sandbox” |
| Guardrail | Contrôles automatiques sur entrées/sorties | Validation d’entrée, nettoyage de sortie, filtre données sensibles, seuil de montant |
| Approval | Décision humaine pour action risquée | Montrer destinataire et contenu avant e-mail, confirmer suppression, approuver paiement |
| Audit | Traçabilité après coup | Logs d’approbation, logs de tool call, logs de changement d’état |
Aucune couche ne remplace l’autre.
Policy ne remplace pas Guardrail : elle est statique.
Guardrail ne remplace pas Approval : il ne juge pas le métier.
Approval ne remplace pas Audit : décision et trace sont deux choses.
Audit ne remplace pas les trois premières : il arrive après le risque.
Exemples :
Paiement : policy limite le montant, guardrail valide les arguments, approval impose une revue à deux, audit enregistre.
E-mail : policy limite les domaines, guardrail vérifie le contenu sensible, approval montre un résumé, audit logue l’envoi.
Frontière de sécurité des tools MCP
MCP (Model Context Protocol) a sa propre frontière de sécurité. Pour la spec 2025-06-18, vérifiez la version avant publication :
tools/list affiche les tools disponibles et rend le risque visible.
tools/call avant une opération sensible correspond à Approval.
inputSchema correspond à Guardrail.
timeout évite les tool calls bloqués.
audit logging correspond à Audit.
Rappel clé : MCP approval ne remplace ni OAuth scope ni server-side authorization. MCP approval confirme un tool call ; OAuth scope donne une permission API ; server-side authorization vérifie les droits métier. Les trois sont nécessaires.
Un serveur Feishu MCP peut avoir OAuth et le scope send_message. Cela ne rend pas chaque message sûr. MCP approval vérifie le contenu avant envoi, et server-side authorization vérifie le destinataire autorisé.
Les cas de messages sortants, écritures collaboratives et modifications de tables en masse seront traités dans l’article Feishu MCP prévu.
Points de conception de l’UI d’approbation
L’UI d’approbation ne se résume pas à approve/reject. Elle doit donner assez d’information pour décider.
Principes :
Afficher le tool et les arguments.
Afficher l’impact attendu, par exemple « envoyer un e-mail à [email protected] avec le sujet Avis de remboursement ».
Afficher la réversibilité : « irréversible après envoi » ou « restaurable après suppression ».
Afficher la sensibilité des données.
Distinguer cancel et reject. Cancel abandonne l’interaction ; reject refuse le tool call et l’audite.
Éléments clés :
Nom du tool + type d’opération
Arguments complets, repliables si besoin
Résumé de l’impact attendu
Avertissement de réversibilité
Label de sensibilité
Champ de raison d’approbation
Boutons approve / reject / cancel
Important : l’UI ne remplace pas server-side authorization. Même après confirmation, le backend doit vérifier l’appelant, l’objet cible et le droit.
Si l’UI dit « supprimer record ID=123 », le backend vérifie encore que le record appartient au bon utilisateur et que la suppression est autorisée.
HITL n’est pas une fenêtre isolée. Il appartient au tool gateway, aux logs et au système de droits. L’article d’architecture MCP prévu approfondira ce point.
Correspondance avec les risques OWASP LLM01/LLM06
OWASP LLM Top 10 définit les risques des systèmes LLM et agents. Les numéros peuvent évoluer ; vérifiez avant publication. Deux risques touchent directement l’approbation :
| Risque | Description | Réponse par approval |
|---|---|---|
| LLM01 Prompt Injection | Une entrée externe pousse à des appels non autorisés, fuite de données ou commandes externes | Exiger approval pour les actions à haut risque ; ne pas s’appuyer uniquement sur le prompt ; montrer arguments et impact |
| LLM06 Excessive Agency | Fonctionnalités, permissions et autonomie excessives créent un risque de tool system | Limiter le périmètre avec Policy, limiter l’autonomie avec Approval, cadrer tout “always allow” |
LLM01 rappelle qu’une prompt injection peut pousser le modèle à appeler un tool non autorisé. L’approbation pause avant l’action risquée et montre arguments + impact à un humain.
LLM06 rappelle que trop d’autonomie est dangereux. Approval n’est pas magique ; il doit fonctionner avec Policy et Guardrail. Le bouton « toujours autoriser cette session » doit être fortement limité.
Correspondance avec NIST AI RMF Core
NIST AI RMF Core organise la gestion des risques IA en quatre phases. Une petite équipe peut en garder une version légère :
| Phase | Responsabilité d’approbation | Exemple |
|---|---|---|
| Govern | Définir rôles et règles | Rôles owner/on-call engineer, niveaux L0-L3, stratégies reject/timeout |
| Map | Identifier les scénarios risqués | Utiliser la matrice pour suppression, paiement, changement de droits et prompt injection |
| Measure | Mesurer couverture et taux de rejet | Suivre couverture approval, rejection rate, timeout rate et ajuster la policy |
| Manage | Réponse à incident et reprise | Utiliser les approval logs, rollback, compensation |
Govern définit les règles. Map trouve les risques. Measure vérifie si le contrôle marche. Manage traite les incidents et la reprise.
Pour une petite équipe, la version légère suffit : niveaux d’approbation, actions à haut risque, taux de rejet et audit logs.
Conclusion
La classification du risque est le premier pas. Tout ne doit pas être approuvé : lectures et brouillons peuvent être automatiques ; suppressions et paiements doivent attendre un humain. Les cinq axes sont impact externe, réversibilité, sensibilité des données, seuil financier/droits et autonomie.
L’approbation n’est pas une fenêtre. C’est un état système sérialisable, reprenable et auditable. RunState est sauvegardé dans un checkpoint, le run reprend au point d’arrêt et l’audit log garde décision et chaîne d’exécution.
Les quatre frontières ont des rôles différents. Policy limite les tools, Guardrail vérifie automatiquement, Approval donne un point de décision humain, Audit rend traçable. Il faut les combiner.
OWASP LLM01 et LLM06 placent prompt injection et excessive agency au centre des risques de tool systems. Approval doit fonctionner avec Policy et Guardrail, pas seul.
NIST AI RMF Core donne le cadre. Pour une petite équipe : définir les niveaux, identifier les actions risquées, mesurer le taux de rejet et garder les audit logs.
À lire ensuite :
Article LangGraph checkpoint/thread state déjà publié : base technique pour sauvegarder l’état d’approbation.
Article Agent monitoring/recovery déjà publié : approval logs dans l’observabilité.
Article architecture MCP en production, prévu : pourquoi HITL doit vivre dans le gateway et les droits.
Article Feishu MCP, prévu : scénarios d’approbation pour messages sortants et écritures collaboratives.
Concevoir un flux d'approbation humaine pour un agent
Utilisez la classification des risques, la pause d'exécution, les preuves d'approbation et les journaux d'audit pour concevoir un flux reprenable.
- 1
Step 1: Lister les tools et les actions
Listez les tools, systèmes externes et actions concrètes que l'agent peut appeler. Ne classez pas seulement par nom de tool. - 2
Step 2: Marquer les dimensions de risque
Pour chaque action, notez l'impact externe, la réversibilité, la sensibilité des données, le seuil financier ou de droits et le niveau d'autonomie. - 3
Step 3: Définir les niveaux d'approbation
Associez chaque type d'action à auto, draft, approval, strong approval ou deny. - 4
Step 4: Persister l'état d'exécution
À l'exécution, enregistrez l'approval request, le RunState ou le checkpoint, puis reliez-les au taskId, runId et traceId. - 5
Step 5: Afficher les preuves d'approbation
Montrez le nom du tool, le résumé des arguments, l'objet touché, la réversibilité, la sensibilité et l'effet attendu. - 6
Step 6: Traiter approve, reject et timeout
Selon la décision, reprenez l'exécution, rétrogradez en brouillon, compensez les étapes déjà faites, escaladez ou arrêtez la tâche. - 7
Step 7: Ajouter audit et tests de régression
Enregistrez les journaux d'approbation et les résultats de reprise, puis testez les chemins reject, timeout et compensation.
FAQ
Quelles actions d'un agent IA nécessitent une approbation humaine ?
Faut-il encore une approbation si des guardrails existent ?
Pourquoi une approbation reste nécessaire après OAuth scope ?
Un bouton peut-il signifier toujours autoriser pour cette session ?
Que faire après un rejet ?
Quels champs stocker dans un enregistrement d'approbation ?
15 min de lecture · Publié le: 11 sept. 2026 · Mis à jour le: 11 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
Context Engineering pour agents IA : séparer System Prompt, Memory, Tools et Files
Un cadre pratique pour répartir le contexte d’un agent entre system prompt, règles développeur, memory, files, retrieval, tool schema, runtime state et output contract afin d’éviter le gonflement du contexte et l’oubli des règles.
Partie 18 sur 22
Suivant
Maîtriser le coût d'un agent IA : routage de modèles, budgets d'outils, cache et limites de retry
Un guide pratique pour contrôler le coût des agents IA avec objets de budget, routage de modèles, limites d'appels d'outils, Prompt Caching, Batch/Flex, circuit breakers, journaux de coût et alertes.
Partie 20 sur 22



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire