Cambia tema

Design di Agent human-in-the-loop: quali passaggi richiedono approvazione umana

Easton editorial illustration: agent rollout and rollback rail

"La documentazione human-in-the-loop di OpenAI Agents SDK descrive tool che richiedono approval, interruptions del run e ripresa con RunState dopo approve o reject."

La bozza di un messaggio Feishu è pronta: titolo, corpo e link dell’allegato sono compilati. Manca solo l’invio. Ma l’Agent si ferma prima di send_message e aspetta la tua conferma.

Un’e-mail può essere generata automaticamente, ma prima di arrivare a un cliente deve mostrare destinatario, oggetto, riepilogo del testo e allegati. Un form CMS può essere compilato da solo, ma submit_form viene bloccato da una policy e aspetta owner approve. Sembra un pulsante di conferma, ma il confine reale è lo stato di esecuzione. Il RunState dell’Agent viene salvato e l’esecuzione riprende solo dopo una decisione umana. Dove fermarsi, chi approva e cosa succede con reject o timeout non è un dettaglio frontend: è sicurezza del sistema di tool.

Questa guida offre una matrice di rischio, una checklist dei punti di approvazione, un meccanismo di pausa/ripresa e un modello di audit. L’obiettivo è passare da “aggiungere un pulsante” a uno stato serializzabile, riprendibile e auditabile.

Matrice di rischio: decidere quali azioni richiedono approvazione

Non tutto richiede approvazione. Le letture possono girare da sole; cancellazioni e pagamenti devono aspettare una persona. Parti da cinque dimensioni:

DimensioneCriterioEsempi di azioni
Impatto esternoTocca sistemi o utenti esterniInviare e-mail, inviare form, chiamare API esterne, scrivere in Feishu/Slack
ReversibilitàL’azione può essere annullata?Cancellare record (irreversibile), salvare bozza (reversibile), pagamento (compensazione parziale), inviare messaggio (irreversibile)
Sensibilità dei datiLivello dei dati coinvoltiConsultare dati pubblici, modificare record interni, esportare privacy utenti, leggere config di produzione
Soglia economica/permessiCoinvolge denaro o permessiPagamento, bonifico, rimborso, cambio permessi, operazione massiva, cancellare dati utenti
Livello di autonomiaAutomazione consentitaQuery read-only (automatica), bozza (automatica), messaggio in uscita (conferma), cancellare/pagare (approval)

Questa matrice segue la logica di OWASP LLM06: funzionalità eccessive, permessi eccessivi e autonomia eccessiva. Usala come base e adatta le soglie al business.

Impatto esterno: tutto ciò che raggiunge un’altra persona o sistema merita attenzione. Un’e-mail inviata non torna indietro. Un form può creare un ordine. Un’API esterna può cambiare dati altrui.

Reversibilità: un record cancellato è difficile da recuperare; una bozza si modifica; un pagamento può richiedere rimborso o compensazione.

Sensibilità dei dati: i dati pubblici si possono leggere più liberamente; i dati interni richiedono controllo in scrittura; privacy e configurazione di produzione chiedono approvazione.

Soglia economica/permessi: denaro e permessi devono fermarsi. Pagamenti, bonifici, rimborsi, modifiche ai permessi e operazioni massive sono punti ad alto rischio.

Livello di autonomia: le letture possono essere automatiche. Anche le bozze. Le uscite esterne richiedono conferma; cancellare e pagare richiedono approval.

La matrice evolve. Una notifica Feishu interna può restare a confirmation; un pagamento reale resta approval con revisione a due persone.

Tre scenari reali di classificazione

Caso 1: bozza di messaggio Feishu

Scrivere una bozza in Feishu è automatico (L0). Resta nelle bozze, non viene inviata, è reversibile e non ha impatto esterno. Chiamare send_message verso un cliente richiede approval (L2). Dopo l’invio il messaggio è irreversibile, esce all’esterno e può contenere informazioni sensibili. Confermare prima di MCP tools/call è questo caso.

Caso 2: invio di e-mail

Generare il contenuto dell’e-mail è automatico (L0). È ancora testo. Inviare tramite API richiede approval con destinatario, oggetto e allegati (L2). La UI deve mostrare prove e riepilogo, non solo “conferma invio”.

Caso 3: invio di form CMS

Compilare il form è automatico (L0). Nulla è stato inviato. Chiamare l’API CMS per inviare deve bloccare e aspettare owner approve (L2). Il blocco può venire da un guardrail, come “importo sopra soglia”, o da una policy fissa.

Caso 4: cancellare database di produzione

Consultare produzione è automatico (L0). È lettura. Cancellare in produzione richiede approvazione umana forte + audit del backup (L3). È irreversibile, sensibile e impatta gli utenti. Va limitato da Policy, non solo da Guardrail.

La lezione: nella stessa attività, ogni passaggio ha rischio diverso. La bozza è automatica, l’invio richiede approval, cancellare produzione richiede revisione doppia. Classifica azioni concrete.

Checklist dei punti di approvazione: scendere al tipo di azione

Con la matrice, definisci i livelli:

L0 automatico: query su database, ricerca vettoriale, lettura configurazione; salvare bozza o generare preview. Nessun impatto esterno, reversibile, niente dati sensibili.

L1 conferma: messaggi o dati in uscita come e-mail, form, API esterna; letture massive come export. C’è impatto esterno, ma controllabile.

L2 approval: cancellare, cambiare permessi, cancellare in massa; scrivere in Feishu, Slack o CRM. Sono azioni irreversibili o ad alto impatto.

L3 strong approval + revisione doppia: pagamenti, rimborsi, export di privacy, modifica config di produzione, cancellare database di produzione. Denaro o dati sensibili.

La lista è allineata con needs_approval in OpenAI Agents SDK e con la sicurezza MCP. MCP si aspetta tool call visibili, rifiutabili e confermati per azioni sensibili.

Puoi adattarla:

Se Feishu è solo notifica interna, L1 può bastare.
Se cancellare tocca dati utente, mantieni L2.
Se il pagamento è critico, passa a L3 con motivo obbligatorio.

La lista cambia con il business. Se la regola cambia, “invia messaggio” può uscire dall’approval.

Macchina a stati del flusso di approvazione: pausa, salvataggio, ripresa

L’approvazione non è un pop-up. È un run in pausa. Quando un tool call richiede approval, RunState viene salvato e l’esecuzione riprende dopo la decisione.

Diagramma di transizione di stato

Il flusso è:

request -> pending -> approved/rejected/timeout -> resume/abort/compensate

request: il tool call crea la richiesta; RunState contiene tool, argomenti e contesto.
pending: il run aspetta una decisione; lo stato è salvato in checkpoint e legato a thread_id.
approved: approvato; il run riprende dal checkpoint e chiama il tool.
rejected: rifiutato; va verso abort o convert to draft.
timeout: scade; escalates o auto-reject.
resume/abort/compensate: continua, ferma o compensa.

Modalità di ripresa

approve: continua, chiama il tool e prosegue.
reject: ferma o converte in bozza, senza chiamare il tool.
edit: modifica parametri, come destinatario o contenuto, e approva di nuovo.

Checkpoint e thread state sono la base tecnica. L’articolo già pubblicato su LangGraph checkpoint/thread state spiega come salvare il punto di pausa e tornarci.

Esempio di codice OpenAI Agents SDK HITL

Questo esempio mostra il flusso in OpenAI Agents SDK. L’API può cambiare; controlla la documentazione ufficiale prima della produzione:

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="Prepara la bozza dell'e-mail e aspetta approvazione prima dell'invio",
)

result = Runner.run_sync(agent, "Scrivi un avviso di rimborso per il cliente")

if result.interruptions:
    state = result.to_state()

    for interruption in result.interruptions:
        print(f"Tool in attesa di approvazione: {interruption.tool_name}")
        print(f"Argomenti: {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)

Punti chiave:

needs_approval=True marca il tool.
interruptions contiene tool call in attesa.
result.to_state() crea un RunState serializzabile.
state.approve() o state.reject() registra la decisione.
Runner.run_sync(agent, state) riprende dalla pausa.

I nomi possono cambiare dopo 2026-07; controlla la documentazione ufficiale.

Esempio di codice LangGraph interrupt/resume

Questo esempio usa interrupt e Command(resume=...):

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'invio dell'e-mail è stato rifiutato; il messaggio è stato salvato come bozza"]}

    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": ["Scrivi una notifica di rimborso per il cliente"]},
    config=config,
)

# Dopo la pausa, il payload di interrupt torna al chiamante.
# Mostra la UI di approvazione e attendi la decisione umana.
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)

Punti chiave:

interrupt() mette in pausa il graph.
Command(resume=...) riprende.
checkpoint + thread_id mantengono coerenza.
approve/reject/edit sono percorsi di ripresa.

Controlla anche l’API di LangGraph.

Campi di prova dell’approvazione: cosa salvare e come tracciare

L’approvazione è anche un record. L’audit log minimo:

CampoDescrizioneEsempio
tool_nameTool + tipo di operazionesend_email / delete_record
tool_argumentsJSON completo degli argomenti{“to”: “[email protected]”, “subject”: “Avviso di rimborso”}
invoker_idIdentità del chiamante[email protected] / agent_run_abc123
request_timeOra della richiesta2026-06-23T09:26:10Z
approver_idIdentità dell’approvatore[email protected]
decision_timeOra della decisione2026-06-23T09:35:12Z
decisionRisultatoapproved / rejected / timeout_auto_reject
evidenceProva: screenshot o riepilogo”Destinatario corretto, nessun dato sensibile”
audit_trail_idLink ai log del runrun_abc123_step_5_tool_3

Questi campi derivano dalle raccomandazioni MCP Tools e da item OpenAI API come mcp_approval_request. Gli approval log sono observability; l’articolo Agent monitoring/recovery copre il logging più ampio.

Come serializzare RunState? In OpenAI Agents SDK, result.to_state() converte il risultato in pausa. LangGraph usa checkpoint + thread_id. Salva lo stato in database o log e collegalo ad audit_trail_id.

Usi principali:

Tracciamento degli incidenti: sapere chi ha approvato cosa e quando.
Evidenza di compliance: dimostrare approvazione umana per azioni critiche.
Miglioramento della policy: misurare azioni approvate, rifiutate o scadute.

Puoi aggiungere durata, canale Slack/Feishu/e-mail o revisione doppia. Ma il minimo deve restare.

Rifiuto e timeout: cosa succede quando l’approvazione fallisce

L’approvazione non passa sempre. Reject e timeout richiedono percorsi chiari, altrimenti il run resta bloccato.

Tre percorsi dopo il rifiuto

Percorso 1: continue with fallback. Usa un’azione meno rischiosa. Se l’invio e-mail viene rifiutato, salva come bozza e continua.

Percorso 2: convert to draft. Trasforma l’azione in bozza. Se l’invio CMS viene rifiutato, resta una bozza da correggere.

Percorso 3: abort task. Ferma tutto il task. Se la cancellazione di produzione viene rifiutata, il run deve fermarsi.

Scelta per tipo:

Reversibile: fallback o convert to draft.
Irreversibile e ad alto rischio: abort task.
Richiede intervento umano: escalate.

Due percorsi dopo timeout

Percorso 1: escalate to backup approver. Se l’approvatore principale non risponde in 30 minuti, passa all’on-call engineer.

Percorso 2: auto-reject. Dopo un’ora rifiuta automaticamente e ferma. Utile per basso rischio con limite di tempo.

Scelta per contesto:

Alto rischio: escalation, non esecuzione automatica.
Tempo critico: auto-reject per non bloccare.
Caso generale: escalation.

Come annullare passaggi già eseguiti

Dopo un rifiuto, qualcosa può essere già successo. Se l’Agent ha creato un ordine prima del rifiuto del pagamento, va annullato.

Strategie:

Checkpoint rollback: tornare al checkpoint prima di approval.
Transazione compensativa: chiamare un’API, per esempio cancellare l’ordine.
Intervento umano: avvisare una persona quando non è automatizzabile.

Non sempre si può annullare. Un’e-mail inviata non si ritira. In quel caso restano audit log e gestione successiva.

Quattro confini di sicurezza: combinare Policy, Guardrail, Approval e Audit

Approval non è una protezione isolata. Policy, Guardrail, Approval e Audit lavorano insieme e non si sostituiscono.

Tabella delle responsabilità in quattro livelli

LivelloResponsabilitàEsempio
PolicyRegole statiche che limitano i tool”Non cancellare produzione”, “tool di pagamento solo in sandbox”
GuardrailControlli automatici su input/outputValidazione, sanitizzazione, filtro dati sensibili, soglia importo
ApprovalDecisione umana per alto rischioMostrare destinatario e contenuto prima dell’e-mail, confermare cancellazione, approvare pagamento
AuditTracciabilità successivaLog di approvazione, tool call, cambi di stato

Policy non sostituisce Guardrail: è statica.
Guardrail non sostituisce Approval: non prende decisioni di business.
Approval non sostituisce Audit: decisione e tracciabilità sono diverse.
Audit non sostituisce i primi tre: arriva dopo il rischio.

Esempi:

Pagamento: Policy limita importo, Guardrail valida argomenti, Approval richiede revisione doppia, Audit registra.
E-mail: Policy limita domini, Guardrail controlla contenuti sensibili, Approval mostra riepilogo, Audit registra invio.

Confine di sicurezza dei tool MCP

MCP (Model Context Protocol) ha un proprio confine. Per la spec 2025-06-18, verifica la versione prima della pubblicazione:

tools/list mostra i tool disponibili.
tools/call prima di azioni sensibili corrisponde ad Approval.
inputSchema corrisponde a Guardrail.
timeout evita tool call bloccati.
audit logging corrisponde ad Audit.

Promemoria: MCP approval non sostituisce OAuth scope né server-side authorization. MCP approval conferma un tool call; OAuth scope dà permesso API; server-side authorization verifica permessi di business. Servono tutti e tre.

Un server Feishu MCP può avere OAuth e scope send_message. Questo non rende sicuro ogni messaggio. MCP approval controlla il contenuto; server-side authorization controlla il destinatario consentito.

I casi di messaggi in uscita, scritture collaborative e modifiche massive di tabelle saranno nell’articolo pianificato su Feishu MCP.

Design della UI di approvazione

La UI non è solo approve/reject. Deve mostrare informazioni sufficienti.

Principi:

Mostra tool e argomenti.
Mostra l’impatto previsto, per esempio “invia e-mail a [email protected] con oggetto Avviso di rimborso”.
Mostra la reversibilità: “irreversibile dopo invio” o “ripristinabile dopo cancellazione”.
Mostra la sensibilità dei dati.
Separa cancel e reject. Cancel abbandona l’interazione; reject nega il tool call e lo audita.

Elementi:

Tool + tipo di operazione
Argomenti completi, richiudibili
Riepilogo dell’impatto
Avviso di reversibilità
Etichetta di sensibilità
Campo motivo
Pulsanti approve / reject / cancel

Importante: la UI non sostituisce server-side authorization. Anche dopo la conferma, il backend verifica chiamante, oggetto e permesso.

Se la UI dice “cancella record ID=123”, il backend verifica ancora proprietario e permesso.

HITL non è un pop-up isolato. Sta nel tool gateway, nei log e nei permessi. L’articolo sull’architettura MCP lo approfondirà.

Mappatura dei rischi OWASP LLM01/LLM06

OWASP LLM Top 10 definisce rischi per LLM e Agent. Numeri e versioni possono cambiare; verifica prima di pubblicare. Due rischi contano per approval:

RischioDescrizioneRisposta con approval
LLM01 Prompt InjectionInput esterno induce chiamate non autorizzate, perdita dati o comandi esterniRichiedere approvazione per alto rischio; non affidarsi solo al prompt; mostrare argomenti e impatto
LLM06 Excessive AgencyFunzioni, permessi e autonomia eccessivi creano rischioLimitare tool con Policy, autonomia con Approval e restringere ogni “always allow”

LLM01 mostra che prompt injection può spingere il modello a chiamare tool non autorizzati. Approval pausa prima dell’alto rischio e mostra argomenti + impatto.

LLM06 mostra che troppa autonomia è pericolosa. Approval deve lavorare con Policy e Guardrail. “Consenti sempre in questa sessione” va ristretto.

Mappatura con NIST AI RMF Core

NIST AI RMF Core organizza il rischio AI in quattro fasi. Un piccolo team può usarne una versione leggera:

FaseResponsabilità di approvazioneEsempio
GovernDefinire ruoli e regoleRuoli owner/on-call engineer, livelli L0-L3, strategie reject/timeout
MapIdentificare scenari di rischioCancellazione, pagamento, cambio permessi e prompt injection con la matrice
MeasureMisurare copertura e rifiutiTracciare coverage, rejection rate, timeout rate e regolare policy
ManageRisposta e recuperoUsare log, rollback e compensazione

Govern definisce regole. Map trova rischi. Measure misura se il controllo funziona. Manage gestisce incidenti.

Per team piccoli basta: livelli di approvazione, azioni ad alto rischio, tasso di rifiuto e audit log.

Conclusione

Classificare il rischio è il primo passo. Non tutto richiede approvazione: letture e bozze possono essere automatiche; cancellazioni e pagamenti devono aspettare. Usa impatto esterno, reversibilità, sensibilità, denaro/permessi e autonomia.

Approval non è un pop-up. È stato serializzabile, riprendibile e auditabile. RunState viene salvato in checkpoint, il run torna al punto di pausa e l’audit log conserva decisione e catena di esecuzione.

I quattro confini hanno ruoli diversi. Policy limita i tool, Guardrail controlla automaticamente, Approval aggiunge decisione umana, Audit dà tracciabilità. Vanno usati insieme.

OWASP LLM01 e LLM06 mettono prompt injection ed excessive agency tra i rischi centrali. Approval deve lavorare con Policy e Guardrail.

NIST AI RMF Core dà il quadro. Per un piccolo team: livelli di approval, azioni ad alto rischio, tasso di rifiuto e audit log.

Prossime letture:

Articolo pubblicato su LangGraph checkpoint/thread state: base tecnica per salvare lo stato di approvazione.
Articolo pubblicato su Agent monitoring/recovery: approval log come observability.
Articolo pianificato sull’architettura MCP in produzione: perché HITL vive in gateway e permessi.
Articolo pianificato su Feishu MCP: scenari per messaggi in uscita e scritture collaborative.

Progettare un flusso di approvazione umana per un Agent

Usa classificazione del rischio, pausa dell'esecuzione, prove di approvazione e audit log per progettare un flusso riprendibile.

  1. 1

    Step 1: Elenca tool e azioni

    Elenca i tool, i sistemi esterni e le azioni concrete che l'Agent può invocare. Non classificare solo per nome del tool.
  2. 2

    Step 2: Segna le dimensioni di rischio

    Per ogni azione segna impatto esterno, reversibilità, sensibilità dei dati, soglia economica o di permessi e livello di autonomia.
  3. 3

    Step 3: Definisci i livelli di approvazione

    Assegna ogni tipo di azione a auto, draft, approval, strong approval o deny.
  4. 4

    Step 4: Persistere lo stato di esecuzione

    A runtime salva approval request, RunState o checkpoint e collegali a taskId, runId e traceId.
  5. 5

    Step 5: Mostra le prove di approvazione

    Mostra tool, riepilogo degli argomenti, oggetto interessato, reversibilità, sensibilità e impatto previsto.
  6. 6

    Step 6: Gestisci approve, reject e timeout

    In base alla decisione riprendi, declassa a bozza, compensa passaggi già eseguiti, fai escalation o ferma il task.
  7. 7

    Step 7: Registra audit e test

    Salva log di approvazione e risultati di ripresa; aggiungi ai test i percorsi reject, timeout e compensazione.

FAQ

Quali azioni di un Agent IA richiedono approvazione umana?
Messaggi in uscita, cancellazione o sovrascrittura di dati, pagamenti, modifiche ai permessi, lettura o scrittura di dati sensibili, scritture massive, invii irreversibili e condivisione del contesto con tool remoti di solito richiedono approval o strong approval.
Serve ancora l'approvazione se ho già dei guardrail?
Sì. Un guardrail è un controllo automatico; l'approvazione umana è un punto decisionale prima di un'azione di business rischiosa. Coprono rischi diversi.
Perché serve approvazione dopo aver concesso OAuth scope?
OAuth scope indica il permesso tecnico di chiamare un'API. Non decide se quella specifica azione di business debba essere eseguita nel contesto attuale.
Un pulsante può significare consenti sempre in questa sessione?
Sì, ma con durata breve, ambito del tool limitato, ambito dell'oggetto e audit log. Una singola approvazione non deve diventare accesso illimitato.
Cosa deve succedere dopo un rifiuto?
Il run deve prendere un ramo esplicito: salvare una bozza, chiedere più informazioni, scegliere un percorso meno rischioso, fare escalation, compensare o fermarsi. Non deve ritentare in silenzio la stessa azione.
Quali campi deve salvare un record di approvazione?
Almeno taskId o runId, tool, riepilogo degli argomenti, oggetto interessato, livello di rischio, approvatore, decisione, motivo, ora, traceId, azione di ripresa e codice errore.

13 min di lettura · Pubblicato il: 11 set 2026 · Aggiornato il: 11 set 2026

Commenti

Accedi con GitHub per lasciare un commento

Easton BlogEaston Blog