Agentic loop (1): anatomia del ciclo

Un LLM è senza stato: un agente è ciò che ottieni quando lo metti in un loop con un obiettivo, strumenti, osservazioni e una condizione di stop. Parte 1 di 3: il ciclo minimo (con pseudocodice e diagramma), i componenti, il tool calling a schema e la terminazione.

AICybersecurityR&DAIAgenticAgenti AutonomiLLMTool CallingContext EngineeringArchitetturaCybersecurity
Diagramma dell'agentic loop: dall'obiettivo iniziale il modello decide, agisce con una tool call, l'harness esegue nell'ambiente, il risultato torna nel contesto, e il ciclo si ripete finché il task è risolto o il budget è esaurito; il ramo end_turn produce la risposta finale
Il ciclo minimo di un agente: da un obiettivo, decide, agisce, esegue, osserva, ripete. Lo stato vive nel contesto; la terminazione e la sicurezza vivono nell’harness.

Serie in 3 parti. Come funziona davvero un agente AI: (1) anatomia del ciclo · (2) contesto, pattern e multi-agente (dall’8 luglio) · (3) sicurezza e governance (dal 10 luglio). Questa è la parte 1.

Un LLM non è un agente

Un modello linguistico, di per sé, è una funzione senza stato: riceve una sequenza di token e ne produce un’altra. Non “fa” nulla, non ricorda nulla tra una chiamata e l’altra, non osserva il mondo. Un agente è ciò che si ottiene mettendo quel modello dentro un loop che, a partire da un obiettivo, gli permette di invocare strumenti, osservarne i risultati e decidere il passo successivo, finché il compito non è risolto. In una frase: il loop è l’agente. Il modello è solo l’organo decisionale al suo interno.

Questa distinzione sposta l’attenzione dal punto sbagliato (il prompt, il modello) a quello giusto: l’harness, cioè il codice che tiene il modello nel ciclo, esegue le sue richieste, gestisce contesto ed errori e applica le policy. È lì che un sistema agentico diventa affidabile, governabile e sicuro, o non lo diventa.

Il ciclo minimo

Il ciclo parte da un obiettivo (più il system prompt): è l’unico ingresso “umano”. Da lì in avanti l’agente procede da solo. Ridotto all’osso:

messages = [system_prompt, user_goal]     # l'obiettivo entra nel contesto

for step in range(MAX_STEPS):
    # 1. DECIDE: il modello sceglie la prossima azione
    reply = llm(messages, tools=TOOLS, temperature=0)
    messages.append(reply)

    # 2. TERMINA? il modello segnala di aver finito
    if reply.stop_reason == "end_turn":
        return reply.text                 # risposta finale
    if reply.stop_reason != "tool_use":
        raise UnexpectedStop(reply.stop_reason)   # es. max_tokens, refusal

    # 3. AGISCE + ESEGUE: l'harness esegue le tool call richieste
    results = []
    for call in reply.tool_calls:
        try:
            args = validate(call.arguments, SCHEMA[call.name])   # niente input non validato
            results.append(tool_result(call.id, execute(call.name, args)))  # side effect nell'ambiente
        except ValidationError as e:
            results.append(error_result(call, e))   # l'errore torna nel contesto, il modello si corregge

    # 4. OSSERVA: il risultato rientra nel contesto
    messages.append(tool_results(results))

raise BudgetExceeded("nessuna soluzione entro MAX_STEPS")

Ogni iterazione il modello emette o una risposta finale o una o più tool call. L’harness esegue le chiamate, ne rimette gli esiti nel contesto, e si ricomincia. Il punto cruciale: il modello è senza stato, tutto lo stato del run vive in messages. Non esiste “memoria” magica: c’è solo ciò che, ad ogni giro, viene serializzato nel contesto, a partire dall’obiettivo iniziale.

I componenti, uno per uno

  • Obiettivo: il compito da risolvere, l’unico input esterno che avvia il ciclo.
  • System prompt / policy: ruolo, vincoli, descrizione degli strumenti, criteri di stop. È il contratto comportamentale, non un suggerimento.
  • Tool schema: nome, descrizione e parametri (JSON Schema) di ogni strumento. È l’interfaccia che il modello vede; la qualità di questa descrizione conta quanto il modello.
  • Contesto (messages): l’unica memoria del modello dentro il run. Cresce a ogni passo.
  • Il modello: decide, non esegue. Produce testo e richieste di azione strutturate.
  • L’harness / executor: esegue le tool call, valida gli argomenti, gestisce errori e timeout, applica le policy, traccia tutto. Qui vive la sicurezza.
  • La condizione di terminazione: senza, il loop è potenzialmente infinito.

Tool calling, nel dettaglio

Il tool calling è il meccanismo con cui il modello passa dal “dire” al “fare”. Il modello non chiama nulla: emette una struttura (nome dello strumento più argomenti conformi a uno schema) che l’harness interpreta ed esegue. Tre punti tecnici che separano un giocattolo da un sistema serio:

  • Validazione degli argomenti. I modelli allucinano parametri: un intero fuori range, un campo mancante, un path arbitrario. Gli argomenti vanno validati contro lo schema prima dell’esecuzione. Se non passano, non si esegue: si rimanda l’errore nel contesto come osservazione, e il modello si auto-corregge al giro successivo. Il messaggio d’errore è parte del ciclo di controllo.
  • Chiamate parallele. In un singolo passo il modello può emettere più tool call indipendenti, eseguibili in parallelo (fan-out). Riduce il numero di iterazioni, quindi latenza e costo.
  • Strumenti deterministici e non. Una query SQL è deterministica; un altro LLM usato come strumento (un sub-agente, un classificatore) non lo è. Un sub-agente, in particolare, è di fatto un loop annidato dentro il loop.

Terminazione: sapere quando fermarsi

Un loop senza una buona condizione di stop è un modo elegante per bruciare budget. Le condizioni tipiche, in AND/OR tra loro:

  • end_turn: il modello segnala esplicitamente di aver concluso (risposta finale).
  • Budget: numero massimo di passi, di token o di tempo.
  • No-progress / anti-loop: rilevare che l’agente oscilla (ripete la stessa azione, alterna due stati) e interrompere. È una difesa essenziale: un loop reattivo ingenuo può girare all’infinito.
  • Handoff umano: l’agente cede il controllo quando incontra una decisione fuori dal suo mandato.

Un esempio concreto. In Claude Code, il comando /goal <condizione> fonde obiettivo e terminazione: si dà all’agente una condizione di completamento (ad esempio /goal tutti i test in test/auth passano e il lint è pulito) e l’agente prosegue turno dopo turno, in autonomia, finché un modello valutatore leggero (di default Haiku) non conferma che la condizione è soddisfatta; a quel punto si ferma. È esattamente il ciclo di questo articolo, con la condizione di stop resa esplicita e verificata da un secondo modello. Da non confondere con /loop, che ripete un comando a intervalli di tempo: scopo diverso (polling, controlli di stato, task ricorrenti), non un obiettivo da raggiungere.

Questo è lo scheletro. Ma un agente che deve durare più di pochi passi si scontra subito con il limite del contesto, e un agente che tocca dati o azioni reali si scontra con la sicurezza. Sono i temi delle prossime due parti.


La parte 2 (contesto, pattern e multi-agente) arriva l’8 luglio, la parte 3 (sicurezza e governance) il 10 luglio.

Vuoi supporto?Sei sotto attacco?Stato dei servizi
Vuoi supporto?Sei sotto attacco?Stato dei servizi