Perché molti sistemi agentici falliscono in produzione e come progettare agenti affidabili con macchine a stati, contratti espliciti per gli strumenti, retry e osservabilità profonda.

I sistemi agentici sono applicazioni dove un LLM non si limita a rispondere a un prompt, ma decide cosa fare dopo: quali strumenti chiamare, quali dati recuperare, quali passi eseguire e quando ha finito. Riuniscono un modello, un insieme di strumenti (API, database, servizi), un ciclo di pianificazione/esecuzione e l'infrastruttura che collega il tutto.
In un demo questo sembra magia: un agente elabora un piano, chiama alcuni strumenti e restituisce un risultato perfetto. Il percorso felice è breve, la latenza è bassa e niente fallisce contemporaneamente.
Sotto carichi reali, lo stesso agente viene stressato in modi che il demo non ha mai visto:
Il risultato: comportamenti fluttuanti difficili da riprodurre, corruzione silenziosa dei dati e flussi utente che occasionalmente si bloccano o girano all'infinito.
Gli agenti instabili non solo riducono la “soddisfazione”. Essi:
Questo articolo parla di pattern di ingegneria, non di “prompt migliori”. Vedremo macchine a stati, contratti espliciti per gli strumenti, strategie di retry e gestione degli errori, controllo di memoria e concorrenza, e pattern di osservabilità che rendono i sistemi agentici prevedibili sotto carico — non solo impressionanti sul palco.
La maggior parte dei sistemi agentici sembra funzionare bene in un demo con percorso felice. Falliscono quando traffico, strumenti e casi limite arrivano insieme.
L'orchestrazione ingenua presume che il modello farà “la cosa giusta” in una o due chiamate. Nell'uso reale emergono pattern ricorrenti:
Senza stati espliciti e condizioni di fine, questi comportamenti sono inevitabili.
Il campionamento degli LLM, la variabilità di latenza e i tempi degli strumenti creano un non-determinismo nascosto. Lo stesso input può seguire rami diversi, invocare strumenti differenti o interpretare in modo diverso i risultati degli strumenti.
A scala, i problemi degli strumenti predominano:
Ognuno di questi si trasforma in loop spurii, retry o risposte finali incorrette.
Ciò che si rompe raramente a 10 RPS si romperà costantemente a 1.000 RPS. La concorrenza rivela:
I team di prodotto spesso si aspettano workflow deterministici, SLA chiari e auditabilità. Gli agenti, lasciati senza vincoli, offrono comportamento probabilistico e best‑effort con garanzie deboli.
Quando le architetture ignorano questa discrepanza — trattando gli agenti come servizi tradizionali invece che come pianificatori stocastici — i sistemi diventano imprevedibili proprio quando l'affidabilità conta di più.
Agenti pronti per la produzione sono meno questione di “prompt intelligenti” e più di progettazione disciplinata dei sistemi. Un modo utile di pensarli è come macchine piccole e prevedibili che occasionalmente chiamano un LLM, non come blob misteriosi che occasionalmente toccano i tuoi sistemi.
Quattro proprietà contano più di tutte:
Non ottieni queste proprietà solo dai prompt. Le ottieni dalla struttura.
Il pattern predefinito con cui molti team iniziano è: “while not done, call the model, let it think, maybe call a tool, repeat”. È facile da prototipare e difficile da gestire.
Un pattern più sicuro è rappresentare l'agente come un workflow esplicito:
COLLECTING_INPUT, PLANNING, EXECUTING_STEP, WAITING_ON_HUMAN, DONE).Questo trasforma l'agente in una macchina a stati dove ogni passo è ispezionabile, testabile e riproducibile. I loop libero‑formali sembrano flessibili, ma sono i workflow espliciti a rendere gli incidenti debuggabili e il comportamento verificabile.
Agenti monolitici che “fanno tutto” sono attraenti, ma creano accoppiamenti stretti fra responsabilità non correlate: pianificazione, retrieval, logica di business, orchestrazione UI e altro.
Invece, componi piccoli agenti o skill ben delimitati:
Ogni skill può avere la propria macchina a stati, strumenti e regole di sicurezza. La logica di composizione diventa allora un workflow di livello superiore, non un prompt in continua espansione dentro un unico agente.
Questa modularità mantiene ogni agente sufficientemente semplice da ragionare e ti permette di evolvere una capacità senza destabilizzare il resto.
Un modello mentale utile è dividere un agente in tre livelli:
Decision policy (prompt LLM + modello)
Incapsula come l'agente sceglie le azioni successive, interpretato sotto vincoli stringenti. Dovresti poter sostituire il modello, regolare la temperatura o affinare i prompt senza toccare il wiring del sistema.
Macchina a stati / motore di workflow
Gestisce dove sei nel processo, quali transizioni sono possibili e come persistere il progresso. La policy suggerisce una mossa; la macchina a stati la valida e la applica.
Layer degli strumenti
Implementa cosa può effettivamente accadere nel mondo: API, database, code, servizi esterni. Gli strumenti espongono contratti stretti e ben tipizzati e applicano autorizzazioni, rate limit e validazione degli input.
Forzando questa separazione eviti la trappola di nascondere la logica di business nei prompt o nelle descrizioni degli strumenti. L'LLM diventa un componente decisionale dentro un involucro deterministico chiaro, non l'involucro stesso.
Gli agenti agentici più affidabili non sono i demo più impressionanti — sono quelli il cui comportamento puoi spiegare su una lavagna.
Concretamente:
Questa preferenza per agenti piccoli, componibili e ben strutturati permette ai sistemi di crescere senza collassare sotto la loro complessità.
La maggior parte delle implementazioni di agenti inizia come un loop di "think, act, observe" attorno a una chiamata LLM. Va bene per i demo, ma diventa rapidamente opaco e fragile. Un approccio migliore è trattare l'agente come una macchina a stati esplicita: un insieme finito di stati, con transizioni ben definite scatenate da eventi.
Invece di lasciare che il modello decida implicitamente cosa fare dopo, definisci un piccolo diagramma di stati:
Le transizioni tra questi stati sono scatenate da eventi tipizzati come UserRequestReceived, ToolCallSucceeded, ToolValidationFailed, TimeoutExceeded o HumanOverride. Ogni evento, insieme allo stato corrente, determina lo stato successivo e le azioni.
Questo rende retry e timeout semplici: alleghi policy a stati individuali (es.: CALL_TOOL può ritentare 3 volte con backoff esponenziale, PLAN potrebbe non ritentare affatto) invece di spargere logica di retry nel codice.
Persisti lo stato corrente e il contesto minimo in uno store esterno (database, coda o motore di workflow). L'agente diventa allora una funzione pura:
next_state, actions = transition(current_state, event, context)
Questo abilita:
Con una macchina a stati, ogni passo del comportamento dell'agente è esplicito: quale stato aveva, quale evento si è verificato, quale transizione è scattata e quali effetti collaterali sono stati prodotti. Questa chiarezza accelera il debug, semplifica le indagini sugli incidenti e crea una traccia d'audit naturale per revisioni di compliance. Puoi dimostrare, dai log e dalla cronologia di stato, che azioni rischiose sono prese solo da stati specifici e sotto condizioni definite.
Gli agenti si comportano in modo molto più prevedibile quando gli strumenti sembrano meno "API nascoste in prosa" e più interfacce ben progettate con garanzie esplicite.
Ogni strumento dovrebbe avere un contratto che copra:
InvalidInput, NotFound, RateLimited, TransientFailure) con semantiche chiare.Esponi questo contratto al modello come documentazione strutturata, non come un muro di testo. Il planner dell'agente dovrebbe sapere quali errori sono ritentabili, quali richiedono intervento umano e quali devono fermare il workflow.
Tratta I/O degli strumenti come qualsiasi altra API di produzione:
Questo ti permette di semplificare i prompt: invece di istruzioni verbose, affidati a indicazioni guidate da schema. Vincoli chiari riducono argomenti allucinati e sequenze di chiamate insensate.
Gli strumenti evolvono; gli agenti non dovrebbero rompersi ogni volta che succede.
v1, v1.1, v2) e lega gli agenti a una versione.La logica di pianificazione può così mescolare agenti e strumenti a diversi livelli di maturità in sicurezza.
Progetta i contratti pensando al fallimento parziale:
L'agente può così adattarsi: continuare il workflow con funzionalità ridotte, chiedere conferma all'utente o passare a uno strumento di fallback.
I contratti degli strumenti sono un luogo naturale per codificare limiti di sicurezza:
confirm: true).Combina questo con controlli server‑side; non fare affidamento solo sul modello perché si "comporti".
Quando gli strumenti hanno contratti chiari, validati e versionati, i prompt possono essere più corti, l'orchestrazione diventa più semplice e il debug molto più agevole. Sposti complessità da istruzioni in linguaggio naturale fragili a schemi e policy deterministici, riducendo chiamate a strumenti allucinatorie e effetti collaterali inaspettati.
I sistemi agentici affidabili assumono che tutto fallirà prima o poi: modelli, strumenti, reti, persino il tuo livello di coordinamento. L'obiettivo non è evitare il fallimento, ma renderlo economico e sicuro.
Idempotenza significa: ripetere la stessa richiesta ha lo stesso effetto visibile esternamente che farla una sola volta. Questo è critico per gli agenti LLM, che spesso rilanciano chiamate a strumenti dopo fallimenti parziali o risposte ambigue.
Rendi gli strumenti idempotenti per progettazione:
request_id stabile. Lo strumento memorizza questo ID e restituisce lo stesso risultato se lo vede di nuovo.Usa retry strutturati per fallimenti transitori (timeout, rate limit, 5xx): backoff esponenziale, jitter per evitare thundering herd e un massimo tentativi rigoroso. Logga ogni tentativo con correlation ID così puoi tracciare il comportamento dell'agente.
Per fallimenti permanenti (4xx, errori di validazione, violazioni di regole di business), non ritentare. Esporre un errore strutturato all'agent policy così può rivedere il piano, chiedere all'utente o scegliere uno strumento alternativo.
Implementa circuit breaker sia a livello agente sia a livello strumento: dopo ripetuti fallimenti, blocca temporaneamente le chiamate a quello strumento e fallisci velocemente. Abbina questo a fallback definiti: modalità degradate, dati cache o strumenti alternativi.
Evita retry ciechi dal loop agente. Senza strumenti idempotenti e classi di errore chiare, moltiplicheresti solo effetti collaterali, latenza e costi.
Agenti affidabili nascono da una chiara separazione tra cosa è statoato e dove vive.
Tratta un agente come un servizio che gestisce una richiesta:
Mescolare i due porta a confusione e bug. Per esempio, mettere risultati effimeri in “memoria” fa sì che gli agenti riutilizzino contesto obsoleto in conversazioni future.
Hai tre opzioni principali:
Una buona regola: l'LLM è una funzione stateless su un oggetto stato esplicito. Persisti quell'oggetto fuori dal modello e rigenera prompt da esso.
Un fallimento comune è usare log di conversazione, trace o prompt come memoria di fatto.
Problemi:
Definisci invece schemi di memoria strutturati: user_profile, project, task_history, ecc. Deriva i log dallo stato, non il contrario.
Quando più strumenti o agenti aggiornano le stesse entità (es.: record CRM o stato di un ticket), servono controlli di consistenza base:
Per operazioni ad alto valore, registra un decision log separato dal log conversazionale: cosa è cambiato, perché e basandosi su quali input.
Per sopravvivere a crash, deploy e limiti di rate, i workflow dovrebbero essere riprendibili:
Questo abilita anche il time travel debugging: puoi ispezionare e riprodurre esattamente lo stato che ha portato a una cattiva decisione.
La memoria è tanto un rischio quanto un asset. Per agenti in produzione:
Tratta la memoria come un prodotto: progettata, versionata e governata — non come un dump testuale che cresce senza controllo.
Gli agenti appaiono sequenziali su una lavagna ma si comportano come sistemi distribuiti sotto carico reale. Appena hai molti utenti concorrenti, strumenti e job in background, affronti condizioni di gara, lavoro duplicato e problemi di ordinamento.
Modalità di fallimento comuni:
Mitighi tutto questo con contratti idempotenti, stato di workflow esplicito e locking ottimistico/pessimistico a livello dati.
I flussi sincroni request–response sono semplici ma fragili: ogni dipendenza deve essere su, entro i limiti e veloce. Quando gli agenti fan out verso molti strumenti o sottotask paralleli, sposta passi di lunga durata o con effetti secondari dietro una coda.
L'orchestrazione basata su code ti permette di:
Gli agenti tipicamente incontrano tre classi di limiti:
Ti serve uno strato esplicito di rate limit con soglie per utente, tenant e globali. Usa token bucket o leaky bucket per applicare le policy e restituisci errori chiari (es.: RATE_LIMIT_SOFT, RATE_LIMIT_HARD) così gli agenti possono fare backoff con grazia.
Il backpressure è come il sistema si protegge sotto stress. Strategie utili:
Monitora segnali di saturazione: profondità delle code, utilizzo dei worker, tassi di errore e percentili di latenza. Code in crescita insieme a latenza e 429/503 sono l'avviso precoce che gli agenti stanno sovraccaricando l'ambiente.
Non puoi rendere un agente affidabile se non riesci a rispondere a due domande velocemente: cosa ha fatto? e perché l'ha fatto? L'osservabilità per i sistemi agentici serve a rendere quelle risposte economiche e precise.
Progetta l'osservabilità in modo che un singolo task abbia una traccia che collega:
All'interno di quella traccia allega log strutturati per decisioni chiave (scelta di routing, revisione del piano, trigger di guardrail) e metriche per volume e salute.
Una traccia utile in genere include:
Logga prompt, input e output degli strumenti in forma strutturata, ma falli passare prima attraverso uno strato di redaction:
Mantieni il contenuto grezzo dietro feature flag negli ambienti non produttivi; la produzione dovrebbe defaultare a viste redatte.
Al minimo monitora:
Quando succedono incidenti, buone tracce e metriche ti permettono di passare da “l'agente è instabile” a una frase precisa come: “P95 dei task fallisce in ToolSelection dopo 2 retry a causa di un nuovo schema in billing_service,” riducendo la diagnosi da ore a minuti e fornendo leve concrete per la correzione.
Testare agenti significa testare sia gli strumenti che chiamano sia i flussi che collegano il tutto. Trattalo come testing di sistemi distribuiti, non solo come tweaking dei prompt.
Inizia con unit test al confine dello strumento:
Questi test non dipendono mai dall'LLM. Chiami lo strumento direttamente con input sintetici e asserti l'output o il contratto d'errore esatto.
I test di integrazione esercitano il workflow agente end‑to‑end: LLM + strumenti + orchestrazione.
Modellali come test basati su scenari:
Questi test verificano transizioni di stato e chiamate a strumenti, non ogni token dell'LLM. Controlla: quali strumenti sono stati chiamati, con quali argomenti, in quale ordine e quale stato/risultato finale ha raggiunto l'agente.
Per mantenere i test ripetibili, fissa sia le risposte LLM sia gli output degli strumenti.
Un pattern tipico:
with mocked_llm(fixtures_dir="fixtures/llm"), mocked_tools():
result = run_agent_scenario(input_case)
assert result.state == "COMPLETED"
Ogni cambiamento di prompt o schema dovrebbe scatenare una run di regressione non negoziabile:
L'evoluzione degli schemi (aggiunta di campi, restrizioni di tipo) ha le sue casse di regressione per intercettare agenti o strumenti che presumono il vecchio contratto.
Non pubblicare mai un nuovo modello, policy o strategia di routing direttamente nel traffico di produzione.
Invece:
Solo dopo aver superato i gate offline una variante nuova dovrebbe arrivare in produzione, idealmente dietro feature flag e rollout graduale.
I log degli agenti spesso contengono dati utente sensibili. I test devono rispettare questo.
Codifica queste regole nella CI così nessun artefatto di test può essere generato o salvato senza controlli di anonimizzazione.
Operare agenti in produzione assomiglia più a gestire un sistema distribuito che a spedire un modello statico. Ti servono controlli per il rollout, obiettivi di affidabilità chiari e gestione disciplinata delle modifiche.
Introdurre nuovi agenti o comportamenti gradualmente:
Supporta tutto questo con feature flag e policy configurabili: regole di routing, strumenti abilitati, temperatura, impostazioni di sicurezza. I cambiamenti dovrebbero essere deployabili via config, non codice, e immediatamente reversibili.
Definisci SLO che riflettano sia la salute del sistema sia il valore utente:
Collega questi ad alert e gestisci gli incidenti come per qualsiasi servizio: ownership chiara, runbook per il triage e passi di mitigazione standard (rollback flag, drain del traffico, modalità safe).
Usa log, trace e trascrizioni delle conversazioni per affinare prompt, strumenti e policy. Tratta ogni cambiamento come un artefatto versionato con revisione, approvazione e possibilità di rollback.
Evita modifiche silenziose di prompt o strumenti. Senza controllo delle modifiche non puoi correlare regressioni a edit specifici e la risposta agli incidenti diventa congettura invece che ingegneria.
Un sistema agentico pronto per la produzione beneficia di una chiara separazione delle responsabilità. L'obiettivo è mantenere l'agente intelligente nelle decisioni, ma semplice nell'infrastruttura.
1. Gateway / API edge
Punto d'ingresso unico per client (app, servizi, UI). Gestisce:
2. Orchestrator
L'orchestrator è il “tronco”, non il cervello. Coordina:
Gli LLM vivono dietro l'orchestrator, usati dal planner e da strumenti specifici che richiedono comprensione del linguaggio.
3. Layer di strumenti e storage
La logica di business rimane nei microservizi esistenti, code e sistemi dati. Gli strumenti sono wrapper sottili attorno a:
L'orchestrator invoca gli strumenti tramite contratti stretti, mentre i sistemi di storage rimangono fonte di verità.
Applica auth e quote al gateway; applica sicurezza, accesso ai dati e policy nell'orchestrator. Tutte le chiamate (LLM e strumenti) emettono telemetria strutturata verso una pipeline che alimenta:
Un'architettura più semplice (gateway → singolo orchestrator → strumenti) è più facile da operare; aggiungere planner separati, policy engine e model gateway aumenta flessibilità, a scapito di più coordinazione, latenza e complessità operativa.
Hai ora gli ingredienti principali per agenti che si comportano prevedibilmente sotto carico reale: macchine a stati esplicite, contratti chiari per gli strumenti, retry disciplinati e osservabilità profonda. Il passo finale è trasformare queste idee in pratiche ripetibili per il tuo team.
Pensa a ogni agente come a un workflow stateful:
Quando questi pezzi sono allineati, ottieni sistemi che degradano con grazia invece di collassare di fronte ai casi limite.
Prima di esporre un agente prototipo agli utenti reali, conferma:
Se manca qualcosa, sei ancora in modalità prototipo.
Una configurazione sostenibile solitamente separa:
Questo permette ai team prodotto di muoversi velocemente mentre i team platform impongono affidabilità, sicurezza e controllo dei costi.
Una volta stabilite basi solide, puoi esplorare:
Il progresso qui dovrebbe essere incrementale: introduce componenti di apprendimento dietro feature flag, con valutazione offline e guardrail forti.
Il tema ricorrente è sempre lo stesso: progetta per il fallimento, preferisci la chiarezza all'astuzia e iterare dove puoi osservare e ripristinare in sicurezza. Con questi vincoli, i sistemi agentici smettono di essere prototipi spaventosi e diventano infrastruttura su cui la tua organizzazione può fare affidamento.
Un sistema agentico è un'applicazione in cui un LLM non si limita a rispondere a un singolo prompt ma decide cosa fare dopo: quali strumenti chiamare, quali dati recuperare, quale passo di un workflow eseguire e quando fermarsi.
A differenza di una semplice chat completion, un sistema agentico combina:
In produzione, l'LLM diventa un componente decisionale all'interno di un contenitore deterministico più ampio — non l'intero sistema.
I demo di solito mostrano un percorso ideale: un singolo utente, strumenti che funzionano perfettamente, nessun timeout, nessuna deriva degli schemi e conversazioni brevi. In produzione gli agenti affrontano:
Senza workflow espliciti, contratti e gestione degli errori, questi fattori generano loop, blocchi, lavori parziali ed errori silenziosi che non compaiono in ambienti di demo.
Fai operare l'LLM dentro una struttura chiara invece che in un loop libero:
Significa modellare l'agente come un workflow con stati nominati ed eventi tipizzati invece di while not done: call LLM.
Stati tipici includono:
Progetta gli strumenti come vere API di produzione, non come descrizioni in prosa. Ogni tool dovrebbe avere:
Dai per scontato che ogni chiamata esterna fallirà talvolta e progetta di conseguenza.
Pattern chiave:
Separa lo stato a breve termine dalla memoria a lungo termine e mantieni l'LLM stateless.
Considera il sistema agente come un sistema distribuito sotto carico, anche se ogni flusso appare sequenziale.
Per restare affidabili:
Devi poter rispondere a “cosa ha fatto?” e “perché l'ha fatto?” per qualsiasi task.
Requisiti pratici:
Tratta gli agenti come servizi in evoluzione: gestiscili con la stessa disciplina delle altre componenti di produzione.
Pratiche raccomandate:
Così puoi spiegare, testare e fare il debug passo passo invece di inseguire opachi "pensieri" dell'agente.
PLAN – interpreta la richiesta e produce un piano passo‑passoCALL_TOOL – invoca uno strumento specifico o un batch di strumentiVERIFY – controlla gli output rispetto a regole semplici o controlli secondariRECOVER – gestisce errori con retry, fallback o escalationDONE / FAILED – esiti terminaliEventi (es.: ToolCallSucceeded, TimeoutExceeded) insieme allo stato corrente determinano il prossimo stato. Questo rende retry, timeout e gestione degli errori espliciti invece che nascosti nei prompt o nel glue code.
InvalidInput, NotFound, RateLimited, TransientFailureValida gli input prima della chiamata (per catturare errori del modello) e gli output dopo (per intercettare regressioni nello strumento). Versiona i contratti e fissa gli agenti su versioni specifiche in modo che i cambiamenti di schema non rompano i flussi silenziosamente.
request_id stabile o una chiave di business e restituiscono lo stesso risultato se vengono richiamati con lo stesso ID.Questo mantiene alta l'affidabilità senza creare loop incontrollati, effetti collaterali duplicati o costi fuori controllo.
Evita di usare log grezzi o cronologie conversazionali come “memoria”; deriva invece record compatti e strutturati con regole chiare di retention e privacy.
Monitora profondità delle code, percentili di latenza e tassi 429/503 per intercettare il sovraccarico prima che diventi un outage.
Con questo, il triage degli incidenti passa da “l'agente sembra instabile” a identificare esattamente stato, strumento e modifica che hanno causato la regressione.
Così puoi migliorare continuamente mantenendo i fallimenti contenuti, diagnostici e reversibili.