State machine per Agent IA: perché un workflow complesso non può dipendere solo dal prompt

"La documentazione LangGraph Persistence descrive i checkpoint come graph state snapshot con scope di thread e spiega che supportano conversation continuity, human-in-the-loop, time travel e fault tolerance."
Un Agent di reportistica è fallito poco prima di inviare l’e-mail al passo 5. Il team operations ha rilanciato il task. L’Agent è ripartito dal passo 1, ha generato un nuovo report e ha sovrascritto la versione già approvata. Lo stato di approvazione è andato perso. Il record firmato dall’approvatore è stato sostituito dal nuovo risultato, e nessun log poteva più dimostrare che la prima versione del report fosse stata approvata.
Non era un problema di rollback del database e nemmeno un semplice retry di coda. Nel prompt restava solo la frase “continua l’elaborazione”. Il modello ha dedotto di nuovo l’intero flusso, senza sapere che i passi 1-4 avevano già prodotto effetti esterni: chiamata all’API di approval, generazione del report, scrittura di un file temporaneo. Il punto di errore era il passo 5, ma gli effetti erano iniziati dal passo 2.
Il problema vero non era la capacità del modello. Il progresso del task era nascosto nel linguaggio naturale del prompt, senza uno state snapshot recuperabile. I messages portati dal prompt sono contesto del modello, non fatti di esecuzione.
Per correggere un incidente del genere non basta aggiungere al prompt una frase come “controlla il progresso prima di continuare”. La scelta più solida è scrivere nodo corrente, effetti già prodotti, action successiva e compensation dell’errore in una tabella di stato recuperabile.
Punti chiave dell’incidente
Flusso di esecuzione dell’Agent di reportistica:
| Passo | Operazione | Effetto esterno | Idempotenza |
|---|---|---|---|
| Passo 1 | Query dati | Chiama il database e legge dati utente | Idempotente (operazione di lettura) |
| Passo 2 | Generazione report | Chiama lo strumento di reportistica e genera un PDF | Non idempotente (sovrascrive un file) |
| Passo 3 | Attesa approval | Invia una richiesta di approvazione e attende un umano | Idempotente (API supportata) |
| Passo 4 | Approval accettato | Riceve l’event approve | Idempotente (query di stato) |
| Passo 5 | Invio e-mail | Chiama l’API e-mail e invia il report | Fallito (timeout) |
Causa dell’errore: l’invio e-mail al passo 5 è andato in timeout per rate limiting dell’API esterna, e il task è stato marcato come FAILED.
Logica di rerun: leggere il “progresso attuale” dal prompt. Il prompt diceva solo “approvato, continua”. Esecuzione reale: ripartire dal passo 1 -> rigenerare il report al passo 2 (sovrascrivendo la versione approvata) -> richiedere di nuovo approval al passo 3 -> inviare con successo al passo 5.
Impatto business: il report approvato è stato sostituito, i record di approval non corrispondevano più al report consegnato, l’utente ha segnalato che il report approvato non era quello ricevuto, e il flusso di approvazione è stato sprecato: due versioni approvate, una sola inviata.
Tabella per riconoscere gli anti-pattern
Controlla se il tuo Agent rientra in questi anti-pattern:
| Anti-pattern | Come si presenta | Rischio nascosto | Correzione |
|---|---|---|---|
| Progresso scritto nel Prompt | Riassunto in linguaggio naturale, tipo “siamo al passo 3” | Perso dopo restart, non recuperabile | Registrare il nodo corrente in un campo State |
| Trace trattato come State | Una trace completa viene scambiata per stato | Trace non decide il prossimo passo | State registra cosa deve succedere dopo |
| Retry senza controllo di idempotenza | In caso di errore si riparte dall’inizio | Gli effetti esterni si ripetono | Idempotency key + controllo già eseguito |
| Resume dopo approval senza verifica | Si continua direttamente | Non si torna al punto corretto | checkpoint + thread_id |
1. Fondamenti di state machine: State, Event, Transition, Guard, Action
Una state machine non è necessaria per ogni Agent. Un semplice Q&A di supporto può funzionare con un array di messages. Ma un task complesso, con più passi, approval, chiamate a sistemi esterni e recovery dopo errore, deve rendere esplicito il progresso.
1.1 Tabella dei termini centrali
La terminologia di base viene dalla documentazione Stately:
| Termine | Definizione | Esempio Agent | Fonte |
|---|---|---|---|
| State | Modalità in cui si trova la macchina, con un’intenzione semantica unica | INIT, PLAN_READY, TOOL_RUNNING, APPROVAL_PENDING, FAILED, COMPLETED | Stately state machines |
| Event | Segnale esterno che innesca un cambio di stato | timeout, approve, reject, retry, resume, task_received | Stately state machines |
| Transition | Percorso consentito tra stati, come mapping deterministico | INIT -> PLAN_READY (event: task_received) | Stately state machines |
| Guard/Condition | Precondizione per entrare in uno stato | Entrare in TOOL_RUNNING solo se il budget è sufficiente | Stately state machines |
| Action | Operazione eseguita durante una transition | Chiamare uno strumento entrando in TOOL_RUNNING | Stately state machines |
| Checkpoint | State snapshot usato per il recovery | LangGraph checkpointer salva graph state | LangGraph Persistence |
Principio di determinismo: la stessa combinazione State + Event dovrebbe puntare a un solo next state, evitando ambiguità. Insieme finito di stati: una state machine non è un flowchart infinito, ma un insieme finito di stati raggiungibili più regole di transition esplicite.
1.2 Confronto Trace vs State vs Audit
Trace, Audit Log e State Snapshot risolvono tre problemi diversi:
| Concetto | Problema risolto | È stato di business? | Decide il prossimo passo? | Esempio Agent |
|---|---|---|---|---|
| Trace | Osservabilità e diagnosi | No | No | OpenAI Agents SDK trace (workflow_name, trace_id) |
| Audit Log | Registro di compliance e audit trail | No | No | Campi audit del modello permessi (actor, traceId, action, result) |
| State Snapshot | Stato corrente che decide il prossimo passo | Sì | Sì | LangGraph checkpoint (nodo corrente, passi completati, prossima action) |
La distinzione è cruciale: una trace aiuta a osservare cosa è successo, ma non è lo stato di business. Un audit log registra la storia per responsabilità e compliance. Uno state snapshot decide cosa deve succedere dopo, ed è il cuore del recovery. Non sono intercambiabili: avere trace non significa avere state; avere audit non significa poter recuperare.
2. Come LangGraph gestisce la persistenza dello stato
Un checkpoint non è un riassunto in linguaggio naturale nel prompt. È uno state snapshot recuperabile, ispezionabile e replayable. La documentazione LangGraph persistence definisce il checkpoint come graph state snapshot con stato completo e prossimi nodi da eseguire.
2.1 Checkpointer e Thread State
Meccanismi centrali (documentazione LangGraph Persistence):
- Checkpointer: salva snapshot di stato con scope di thread (graph state snapshot)
- Store: salva dati a lungo termine tra thread (application-defined store)
- Thread_id: punto di ingresso unico per recuperare lo stato di uno specifico thread
- Quattro usi: conversation continuity, human-in-the-loop, time travel, fault tolerance
LangGraph persistence mette lo stato breve con scope di thread nei checkpointers e i dati a lungo termine tra thread negli stores. Un checkpoint include state snapshot e application-defined store. Thread_id è l’ingresso del recovery: con la stessa thread_id puoi continuare dal punto di pausa.
Un checkpoint LangGraph contiene graph state, lista dei prossimi nodi da eseguire, checkpoint_id, timestamp e versione. I dati sensibili non devono entrare nel checkpoint senza controllo: alcuni campi del graph state possono contenere informazioni sensibili e vanno esclusi dalla persistenza con configurazione esplicita.
2.2 Interrupts e meccanismo di recovery
Meccanismi centrali (documentazione LangGraph Interrupts):
- interrupt(): mette in pausa dinamicamente l’esecuzione dentro un nodo del grafo, salva graph state e attende input esterno
- Metodo di recovery: usare la stessa thread_id e Command(resume=…)
- Pattern comuni: approval, review/edit, tool call review, human input validation
- Avviso sugli effetti idempotenti: gli effetti prima di interrupt devono essere idempotenti, perché al resume il nodo riparte dall’inizio del nodo che ha chiamato interrupt
Una pausa di approval deve essere uno stato di pausa nella state machine, non una speranza che il modello “si ricordi di aspettare l’approvazione”. Il recovery richiede lo stesso thread cursor.
Il recovery usa la stessa thread_id e Command(resume=…). Gli effetti idempotenti sono una precondizione. Se prima dell’approvazione esiste un effetto esterno, come una chiamata API, deve essere idempotente; altrimenti il nodo ripreso chiamerà di nuovo l’API.
3. Analogia ingegneristica: Temporal Durable Execution
L’affidabilità dei task lunghi non è un problema nuovo. Temporal durable execution offre un riferimento maturo.
3.1 Definizione di Durable Execution
Concetti centrali (documentazione Temporal Durable Execution):
- Durable Execution: workflow execution conserva state/progress durante errori, crash o interruzioni di servizio
- Event History: registra lo stato di ogni passo, così dopo un errore si può recuperare dall’ultimo evento registrato
- Tre proprietà: Resumable, Recoverable, Reactive
L’affidabilità dei task lunghi nasce da event history ed esecuzione recuperabile, non dalla memoria di un singolo processo o dal contesto del prompt. Una state machine per Agent richiede un meccanismo simile: checkpoint/event log + stato di business, non solo nuova inferenza del modello.
Event History di Temporal e checkpoint di LangGraph sono concettualmente simili: registrano la storia di esecuzione e supportano il recovery dal punto di errore. La differenza è che Temporal è un workflow engine completo, mentre LangGraph è un framework di gestione dello stato per Agent. La lezione è chiara: durable execution richiede storia strutturata dello stato, non memoria di processo o contesto del modello.
4. Template di tabella di stato: una Agent State Table riutilizzabile
I concetti di state machine sono astratti. Per renderli utili serve un modello di stato concreto. Qui trovi tre template: tabella di stato, tabella degli eventi ed esempio guidato dall’incidente.
4.1 Template di tabella di stato (blocco eseguibile)
Struttura del template:
| State | Event | Guard | Action obbligatoria | Next |
|---|---|---|---|---|
| INIT | task_received | Nessuno | Inizializzare il contesto e registrare l’ora di inizio | PLAN_READY |
| PLAN_READY | plan_generated | plan_valid | Generare il piano di esecuzione e registrare la sequenza strumenti | TOOL_RUNNING |
| TOOL_RUNNING | tool_completed | budget_sufficient | Chiamare lo strumento, registrare il risultato e aggiornare il budget | APPROVAL_PENDING o COMPLETED |
| APPROVAL_PENDING | approve | approval_required | Inviare richiesta di approval e registrare l’approvatore | COMPLETED |
| APPROVAL_PENDING | reject | Nessuno | Registrare motivo del rifiuto e notificare l’utente | FAILED |
| FAILED | retry | retry_count < max | Verificare idempotenza e tornare al checkpoint precedente | TOOL_RUNNING o APPROVAL_PENDING |
| COMPLETED | Nessuno | Nessuno | Registrare ora di completamento e ripulire risorse | Terminal |
Note: la colonna State definisce tutti gli stati raggiungibili (INIT, PLAN_READY, TOOL_RUNNING, APPROVAL_PENDING, FAILED, COMPLETED). Event definisce gli eventi che innescano transition (task_received, approve, reject, retry). Guard definisce le precondizioni (budget_sufficient, retry_count < max). Action definisce l’operazione obbligatoria durante la transition. Next definisce il prossimo stato in modo deterministico.
4.2 Template di tabella eventi (complemento della tabella di stato)
Struttura del template:
| Event | Condizione di trigger | Stato precedente richiesto | Stato successivo | Produce effetti esterni? |
|---|---|---|---|---|
| task_received | L’utente invia un task | INIT | PLAN_READY | No |
| plan_generated | L’LLM genera un piano di esecuzione | PLAN_READY | TOOL_RUNNING | No |
| tool_completed | Lo strumento termina l’esecuzione | TOOL_RUNNING | APPROVAL_PENDING o COMPLETED | Sì (chiamata API esterna) |
| approve | L’approvatore accetta | APPROVAL_PENDING | COMPLETED | Sì (invia e-mail, scala budget) |
| reject | L’approvatore rifiuta | APPROVAL_PENDING | FAILED | No |
| retry | Richiesta di retry dopo errore | FAILED | TOOL_RUNNING o APPROVAL_PENDING | Richiede controllo di idempotenza |
| timeout | Timeout di esecuzione | TOOL_RUNNING | FAILED | No |
Note: lo stato precedente richiesto rende esplicito in quali stati un event può essere ricevuto. La colonna sugli effetti esterni segnala quali event richiedono idempotenza o compensation.
4.3 Esempio di tabella di stato guidata dall’incidente di report sovrascritto
Esempio completo: state table dell’Agent di reportistica derivata dall’incidente iniziale
| State | Event | Guard | Action | Next | Controllo idempotenza/compensation |
|---|---|---|---|---|---|
| INIT | task_received | Nessuno | Inizializzare thread_id e registrare ora di inizio | QUERY_RUNNING | Non serve |
| QUERY_RUNNING | query_completed | Nessuno | Interrogare dati e salvare risultato in state | REPORT_GENERATING | Non serve |
| REPORT_GENERATING | report_generated | Nessuno | Generare report e salvare report ID in state | APPROVAL_PENDING | Controllo idempotenza: se il report esiste già, saltare generazione |
| APPROVAL_PENDING | approve | Nessuno | Registrare approvatore e ora approval | EMAIL_SENDING | Non serve |
| APPROVAL_PENDING | reject | Nessuno | Registrare motivo del rifiuto | FAILED | Non serve |
| EMAIL_SENDING | email_sent | Nessuno | Inviare e-mail e registrare email ID | COMPLETED | Controllo idempotenza: se l’e-mail è già stata inviata, saltare |
| EMAIL_SENDING | timeout | retry_count < 3 | Registrare errore e verificare idempotenza | EMAIL_SENDING (retry) o FAILED | Idempotency key: email_id + thread_id |
| FAILED | retry | retry_count < max | Verificare idempotenza e recuperare dal checkpoint precedente | QUERY_RUNNING o REPORT_GENERATING o EMAIL_SENDING | Decidere il punto di recovery in base al checkpoint |
| COMPLETED | Nessuno | Nessuno | Registrare ora di completamento e ripulire risorse | Terminal | Non serve |
Correzione dell’incidente: se il passo 5 fallisce (EMAIL_SENDING -> timeout), il recovery deve riprendere da EMAIL_SENDING, non da QUERY_RUNNING. Il checkpoint deve registrare il nodo corrente (EMAIL_SENDING), i passi completati (QUERY, REPORT_GENERATED, APPROVAL_APPROVED) e cosa fare dopo (EMAIL_SENDING). Generazione report e invio e-mail richiedono idempotency key per evitare duplicati.
5. Idempotenza e compensation: recovery non significa solo checkpoint
Avere un checkpoint non significa poter recuperare in sicurezza tutti gli effetti esterni. Il recovery richiede anche idempotenza, transazioni, compensation e controlli sullo stato del sistema esterno.
5.1 Concetti di idempotenza e compensation
Definizioni:
- Idempotente: più esecuzioni producono lo stesso risultato e non creano effetti esterni duplicati
- Compensation: annullare un effetto esterno già avvenuto e ripristinare consistenza
- Rollback transazionale: operazione atomica che si annulla automaticamente in caso di errore
- Controllo dello stato esterno: verificare il sistema esterno prima del recovery per evitare operazioni duplicate
Tre pilastri della consistenza dello stato: identità di idempotenza (action_id + schema_hash), catena di state snapshot (snapshot + prev_hash + delta) e compensation action registrata (undo_op).
5.2 Checklist idempotenza e compensation
Come decidere quali operazioni richiedono idempotenza e quali richiedono compensation:
| Tipo di operazione | Richiede idempotenza? | Richiede compensation? | Design idempotency key | Piano di compensation |
|---|---|---|---|---|
| Query dati (senza effetti esterni) | No | No | - | - |
| Generazione report (sovrascrive file) | Sì | Sì | report_id + thread_id | Eliminare il nuovo report e ripristinare la versione approvata |
| Invio e-mail (API esterna) | Sì | Difficile | email_id + thread_id | Inviare e-mail di correzione o annullamento in alcuni scenari |
| Decurtazione inventario (database) | Sì | Sì | inventory_id + order_id | Aggiungere di nuovo inventario |
| Creazione ticket (sistema esterno) | Sì | Sì | ticket_id + thread_id | Chiudere il ticket |
| Decurtazione budget (stato interno) | Sì | Sì | budget_id + thread_id | Aggiungere di nuovo budget |
| Invio richiesta approval (senza effetto duraturo) | No | No | - | - |
Logica di decisione: il fatto che un’operazione produca effetti esterni determina il bisogno di idempotenza. Le operazioni reversibili richiedono compensation. Nelle chiamate tra sistemi, l’idempotency key dovrebbe includere un identificatore del sistema esterno. Le operazioni atomiche possono usare rollback transazionale.
Recovery non è solo checkpoint. Richiede idempotenza, transazioni, compensation e controlli sullo stato esterno. Dire che un checkpoint recupera in sicurezza tutti gli effetti esterni non è corretto.
6. Checklist degli stati del task Agent: recuperabile vs non recuperabile
Non tutti i checkpoint consentono recovery. Terminal state è lo stato finale di una workflow execution: completato, fallito, timeout o cancelled. Un terminal state non può essere ripreso; può solo essere rieseguito o compensato.
6.1 Tabella di classificazione degli stati
| Tipo di stato | Recuperabile? | Condizione di recovery | Metodo di recovery | Esempio |
|---|---|---|---|---|
| Failed | Sì | retry_count < max | Recuperare dal checkpoint precedente | Timeout di tool call |
| Retry | Sì | Controllo idempotenza superato | Rieseguire dal nodo fallito | Invio e-mail fallito |
| Compensation | Parzialmente | Esiste un piano di compensation | Eseguire undo_op | Decurtazione inventario fallita |
| Approval Pause | Sì | event approve/reject | Command(resume=…) | Attesa approval |
| Terminal | No | Nessuna | Nessun percorso di recovery | COMPLETED, FAILED (retry_count = max) |
Note: uno stato Failed può recuperare con retry se retry_count < max. Uno stato Retry richiede controllo di idempotenza e riesegue dal nodo fallito. Uno stato Compensation è parzialmente recuperabile se esiste un piano. Approval Pause recupera con event approve/reject. Terminal State non è recuperabile, per esempio COMPLETED o FAILED dopo il massimo numero di retry.
7. Letture successive
Il design della state machine è solo il punto di partenza. Il modello di stato deve aderire allo scenario di business; task diversi richiedono granularità dello stato e strategie di recovery diverse.
Navigazione della serie
| Articolo | Relazione | Link |
|---|---|---|
| Human-in-the-loop Agent: quali passi richiedono approvazione umana | Dettagli della pausa di approval | /blog/it/posts/ai/20260707-human-in-the-loop-agent-approval-design/ |
| Controllo costi Agent: model routing, budget strumenti e retry degli errori | Strategia budget e retry | /blog/it/posts/ai/20260707-agent-cost-control-model-routing-tool-budget-cache-retry/ |
| Gestione dello stato LangGraph in pratica: best practice di architettura Agent 2026 | Gestione stato LangGraph | /blog/it/posts/ai/20260424-langgraph-agent-architecture/ |
| Monitoraggio, alert e recovery degli errori per AI Agent: dai log alle state machine | Monitoraggio e recovery | /blog/it/posts/ai/20260527-ai-agent-monitoring-recovery/ |
| LangGraph vs AutoGen State Tracking | Confronto framework | /blog/it/posts/ai/20260526-langgraph-autogen-state-tracking/ |
| Dataset di valutazione Agent e test di regressione: evitare che una modifica rompa tutto | Valutazione e test di regressione | Anteprima, prossimo articolo della serie |
Riferimenti esterni
Fonti ad alta affidabilità:
| Fonte | Affidabilità | Tema | Link |
|---|---|---|---|
| Documentazione LangGraph Persistence | high | Checkpointer, Store, Thread State, Checkpoint | https://docs.langchain.com/oss/python/langgraph/persistence |
| Documentazione LangGraph Interrupts | high | interrupt(), Command(resume=…), thread_id | https://docs.langchain.com/oss/python/langgraph/interrupts |
| Documentazione Temporal Durable Execution | high | Event History, Durable Execution, Resumable/Recoverable | https://docs.temporal.io/temporal |
| Documentazione OpenAI Agents SDK Tracing | high | Trace, Span, workflow_name, trace_id | https://openai.github.io/openai-agents-python/tracing/ |
| Documentazione AWS Step Functions State Machines | high | State Machine, Flow State, Task State, StartAt, Next | https://docs.aws.amazon.com/step-functions/latest/dg/concepts-statemachines.html |
| Stately: State machines and statecharts | medium | State, Event, Transition, Guard, Action, Hierarchy | https://stately.ai/docs/state-machines-and-statecharts |
Una state machine non serve a ogni Agent, ma i task complessi devono rendere esplicito il progresso. Il passo successivo non è introdurre altri framework. È progettare State, Event, Transition, Guard e Action adatti al tuo scenario di business, spostando il progresso del task dal linguaggio naturale del prompt a uno stato strutturato.
Progettare la state machine di un Agent IA complesso
Scomponi un task complesso di Agent IA in state, event, guard, action, checkpoint, retry, compensation e terminal state, evitando che il progresso resti nascosto solo nel prompt.
⏱️ Estimated time: 45 min
- 1
Step 1: Elenca i punti di rischio
Elenca gli effetti esterni del task, i punti di pausa umana, i punti di errore e le condizioni terminali. - 2
Step 2: Definisci il set minimo di stati
Definisci il set minimo utile di state, come pending, running, waiting_approval, retrying, compensating, succeeded, failed e cancelled. - 3
Step 3: Collega event e next state
Per ogni state, scrivi quali event può ricevere e a quale next state porta ogni event. - 4
Step 4: Aggiungi le condizioni di guard
Aggiungi guard alle transition pericolose: permessi, budget, approval, idempotency key e stato delle risorse esterne. - 5
Step 5: Isola le action degli strumenti
Metti le chiamate agli strumenti nel livello action e registra riepilogo input, riepilogo output, traceId e risultato dell'effetto esterno. - 6
Step 6: Definisci le policy di errore
Definisci retry policy, terminal state e compensation policy per ogni percorso di errore. - 7
Step 7: Persisti la base di recovery
Definisci un checkpoint o event log per il recovery e tratta il prompt come contesto temporaneo, non come unica fonte di verità.
FAQ
Se l'Agent fallisce al passo 5, devo ripartire dal passo 1 o continuare da un checkpoint?
Lo stato del task deve stare nel prompt, in un database, in un checkpoint LangGraph o in un job di coda?
Qual è la differenza tra una state machine e un diagramma di workflow?
Come garantire che l'Agent torni allo stesso punto di esecuzione dopo approval?
Retry e compensation vanno nel prompt o nelle regole di transition dello stato?
Un semplice Agent di customer service ha bisogno di una state machine?
14 min di lettura · Pubblicato il: 17 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
Modello di autorizzazione per Agent IA: identità utente, permessi degli strumenti, audit log e isolamento dei secret
Prima di collegare un Agent IA a strumenti reali, progetta il modello di autorizzazione: identity mapping, service account, per-tool permission, scope, secret vault, rotazione delle chiavi, approval policy, data boundary e audit log.
Parte 5 di 6
Successivo
Questo è l’articolo più recente della serie per ora.



Commenti
Accedi con GitHub per lasciare un commento