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

"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:
| Dimensione | Criterio | Esempi di azioni |
|---|---|---|
| Impatto esterno | Tocca sistemi o utenti esterni | Inviare 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 dati | Livello dei dati coinvolti | Consultare dati pubblici, modificare record interni, esportare privacy utenti, leggere config di produzione |
| Soglia economica/permessi | Coinvolge denaro o permessi | Pagamento, bonifico, rimborso, cambio permessi, operazione massiva, cancellare dati utenti |
| Livello di autonomia | Automazione consentita | Query 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:
| Campo | Descrizione | Esempio |
|---|---|---|
| tool_name | Tool + tipo di operazione | send_email / delete_record |
| tool_arguments | JSON completo degli argomenti | {“to”: “[email protected]”, “subject”: “Avviso di rimborso”} |
| invoker_id | Identità del chiamante | [email protected] / agent_run_abc123 |
| request_time | Ora della richiesta | 2026-06-23T09:26:10Z |
| approver_id | Identità dell’approvatore | [email protected] |
| decision_time | Ora della decisione | 2026-06-23T09:35:12Z |
| decision | Risultato | approved / rejected / timeout_auto_reject |
| evidence | Prova: screenshot o riepilogo | ”Destinatario corretto, nessun dato sensibile” |
| audit_trail_id | Link ai log del run | run_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
| Livello | Responsabilità | Esempio |
|---|---|---|
| Policy | Regole statiche che limitano i tool | ”Non cancellare produzione”, “tool di pagamento solo in sandbox” |
| Guardrail | Controlli automatici su input/output | Validazione, sanitizzazione, filtro dati sensibili, soglia importo |
| Approval | Decisione umana per alto rischio | Mostrare destinatario e contenuto prima dell’e-mail, confermare cancellazione, approvare pagamento |
| Audit | Tracciabilità successiva | Log 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:
| Rischio | Descrizione | Risposta con approval |
|---|---|---|
| LLM01 Prompt Injection | Input esterno induce chiamate non autorizzate, perdita dati o comandi esterni | Richiedere approvazione per alto rischio; non affidarsi solo al prompt; mostrare argomenti e impatto |
| LLM06 Excessive Agency | Funzioni, permessi e autonomia eccessivi creano rischio | Limitare 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:
| Fase | Responsabilità di approvazione | Esempio |
|---|---|---|
| Govern | Definire ruoli e regole | Ruoli owner/on-call engineer, livelli L0-L3, strategie reject/timeout |
| Map | Identificare scenari di rischio | Cancellazione, pagamento, cambio permessi e prompt injection con la matrice |
| Measure | Misurare copertura e rifiuti | Tracciare coverage, rejection rate, timeout rate e regolare policy |
| Manage | Risposta e recupero | Usare 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
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
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
Step 3: Definisci i livelli di approvazione
Assegna ogni tipo di azione a auto, draft, approval, strong approval o deny. - 4
Step 4: Persistere lo stato di esecuzione
A runtime salva approval request, RunState o checkpoint e collegali a taskId, runId e traceId. - 5
Step 5: Mostra le prove di approvazione
Mostra tool, riepilogo degli argomenti, oggetto interessato, reversibilità, sensibilità e impatto previsto. - 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
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?
Serve ancora l'approvazione se ho già dei guardrail?
Perché serve approvazione dopo aver concesso OAuth scope?
Un pulsante può significare consenti sempre in questa sessione?
Cosa deve succedere dopo un rifiuto?
Quali campi deve salvare un record di approvazione?
13 min di lettura · Pubblicato il: 11 set 2026 · Aggiornato il: 11 set 2026
Guida all'ingegneria degli AI Agent
Se arrivi dalla ricerca, il modo più veloce per orientarti è passare all’articolo precedente o successivo della stessa serie.
Precedente
Context engineering per agent IA: come separare System Prompt, Memory, Tools e Files
Una guida pratica per dividere il contesto di un agent tra system prompt, regole developer, memory, files, retrieval, tool schema, runtime state e output contract, evitando che le regole si perdano nelle esecuzioni lunghe.
Parte 2 di 6
Successivo
Controllo dei costi degli agenti IA: routing dei modelli, budget degli strumenti, cache e retry
Guida pratica per controllare i costi degli agenti IA con oggetti di budget, routing dei modelli, limiti alle chiamate degli strumenti, Prompt Caching, Batch/Flex, circuit breaker, log di costo e alert.
Parte 4 di 6



Commenti
Accedi con GitHub per lasciare un commento