Changer le thème

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

Easton editorial illustration: agent rollout and rollback rail

"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 :

DimensionCritèreExemples d’actions
Impact externeTouche un système ou un utilisateur externeEnvoyer 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éesNiveau de données manipuléesLire des données publiques, modifier des records internes, exporter des données privées, lire une config de production
Seuil financier/droitsImplique argent ou changement de droitsPaiement, virement, remboursement, changement de droits, opération en masse, suppression de données utilisateur
Niveau d’autonomieNiveau 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": "new_customer@example.com"}})
    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 :

ChampDescriptionExemple
tool_nameNom du tool + type d’opérationsend_email / delete_record
tool_argumentsJSON complet des arguments{“to”: “customer@example.com”, “subject”: “Avis de remboursement”}
invoker_idIdentité appelanteuser@example.com / agent_run_abc123
request_timeHeure de la demande2026-06-23T09:26:10Z
approver_idIdentité de l’approbateuron-call-engineer@example.com
decision_timeHeure de décision2026-06-23T09:35:12Z
decisionRésultatapproved / rejected / timeout_auto_reject
evidencePreuve : capture ou résumé”Destinataire correct, contenu sans donnée sensible”
audit_trail_idLien vers les logs du runrun_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

CoucheResponsabilitéExemple
PolicyRègles statiques qui limitent le périmètre des tools”Interdire la suppression de la base production”, “outil paiement seulement en sandbox”
GuardrailContrôles automatiques sur entrées/sortiesValidation d’entrée, nettoyage de sortie, filtre données sensibles, seuil de montant
ApprovalDécision humaine pour action risquéeMontrer destinataire et contenu avant e-mail, confirmer suppression, approuver paiement
AuditTraçabilité après coupLogs 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 à customer@example.com 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 :

RisqueDescriptionRéponse par approval
LLM01 Prompt InjectionUne entrée externe pousse à des appels non autorisés, fuite de données ou commandes externesExiger approval pour les actions à haut risque ; ne pas s’appuyer uniquement sur le prompt ; montrer arguments et impact
LLM06 Excessive AgencyFonctionnalités, permissions et autonomie excessives créent un risque de tool systemLimiter 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 :

PhaseResponsabilité d’approbationExemple
GovernDéfinir rôles et règlesRôles owner/on-call engineer, niveaux L0-L3, stratégies reject/timeout
MapIdentifier les scénarios risquésUtiliser la matrice pour suppression, paiement, changement de droits et prompt injection
MeasureMesurer couverture et taux de rejetSuivre couverture approval, rejection rate, timeout rate et ajuster la policy
ManageRéponse à incident et repriseUtiliser 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. 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. 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. 3

    Step 3: Définir les niveaux d'approbation

    Associez chaque type d'action à auto, draft, approval, strong approval ou deny.
  4. 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. 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. 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. 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 ?
Les messages sortants, suppressions ou écrasements de données, paiements, changements de droits, accès aux données sensibles, écritures en masse, soumissions irréversibles et partages de contexte avec des tools distants devraient généralement exiger approval ou strong approval.
Faut-il encore une approbation si des guardrails existent ?
Oui. Un guardrail est un contrôle automatique ; l'approbation humaine est un point de décision avant une action métier risquée. Les deux ne couvrent pas le même risque.
Pourquoi une approbation reste nécessaire après OAuth scope ?
Un OAuth scope indique une permission technique d'appel. Il ne dit pas si l'action métier précise doit être exécutée dans ce contexte.
Un bouton peut-il signifier toujours autoriser pour cette session ?
Oui, mais avec une durée courte, un périmètre de tool limité, un périmètre d'objet et un audit log. Une approbation unique ne doit pas devenir un accès illimité à tous les tools.
Que faire après un rejet ?
Le run doit suivre une branche explicite : sauvegarder un brouillon, demander des informations, choisir un chemin moins risqué, escalader, compenser ou arrêter. Il ne doit pas retenter silencieusement la même action risquée.
Quels champs stocker dans un enregistrement d'approbation ?
Au minimum : taskId ou runId, tool, résumé des arguments, objet affecté, niveau de risque, approbateur, décision, raison, horodatage, traceId, action de reprise et code d'erreur.

15 min de lecture · Publié le: 11 sept. 2026 · Mis à jour le: 11 sept. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog