Chi progetta un agente singolo tratta l’errore come un’eccezione da catturare. In un sistema multi-agente l’errore è un input: qualcuno lo legge, e quello che legge cambia la traiettoria di tutto il resto. Il report di Anthropic sul sistema di ricerca multi-agente è netto: «Agents are stateful and errors compound», e «One step failing can cause agents to explore entirely different trajectories, leading to unpredictable outcomes». Da qui la scelta architetturale: «restarts are expensive and frustrating for users», per cui si costruiscono sistemi «that can resume from where the agent was when the errors occurred», combinando l’adattabilità del modello con «deterministic safeguards like retry logic and regular checkpoints».
Tre superfici, tre semantiche
Un errore che risale verso un agente passa da una di tre strade. Confonderle è il primo modo di sbagliare il design.
Errore di tool. Lo restituisci tu, dentro il tool_result, con is_error a true. Claude lo legge e adatta la risposta. La documentazione chiede messaggi istruttivi: al posto di un generico "failed" bisogna «include what went wrong and what Claude should try next (for example, "Rate limit exceeded. Retry after 60 seconds.")». Sugli input non validi il comportamento è già definito: «Claude will retry 2-3 times with corrections before apologizing to the user». Un errore di tool, quindi, non è un fallimento: è un canale di negoziazione con il modello.
Errore HTTP. Qui il modello non vede nulla: un 429 rate_limit_error, un 500 api_error, un 529 overloaded_error arrivano al tuo codice, non nella conversazione. Gli SDK ufficiali «automatically retry transient failures … with exponential backoff, twice by default, honoring the retry-after header when present». Due tentativi sono una difesa di default, non una politica: con decine di sottoagenti concorrenti il retry cieco moltiplica proprio il carico che ha prodotto il 429.
Fallimento di un sottoagente. È il caso che rompe i sistemi ingenui, perché il testo dell’errore rischia di essere letto dal padre come se fossero i risultati del figlio. Da Claude Code v2.1.199 non è più così. In foreground, se il sottoagente aveva già prodotto testo, «the Agent tool returns that partial output with a note that the subagent was cut off and didn’t finish its task»; se non aveva prodotto nulla, fallisce con Agent terminated early due to an API error. In background «the subagent is marked failed, and the message Claude receives when it ends names the API error and includes the subagent’s last output, so partial work isn’t lost».
La topologia decide chi cade
Il pattern di coordinamento non è neutro rispetto ai guasti. Con lo stato condiviso, «If any one agent stops, the others continue reading and writing»; con un orchestratore o un message bus, «a coordinator or router failure halts everything». Il caso peggiore però non è il crash: «If the router misclassifies or drops an event, the system fails silently, handling nothing but never crashing». Un guasto silenzioso non compare in nessun log di errore, e nessun blocco try/catch lo intercetta: va reso visibile confrontando eventi emessi ed eventi consegnati.
Vale anche per i cicli generatore-verificatore, che possono oscillare senza convergere: «A maximum iteration limit with a fallback strategy (escalate to a human, return the best attempt with caveats) prevents this from becoming an infinite loop». Il fallback è a sua volta propagazione: consegna il risultato migliore con le riserve attaccate, invece di consegnare silenzio.
Il runtime offre già limiti duri che nei fatti sono politica di propagazione:
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH default: 3 livelli di annidamento
CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS default: 20 -> "Concurrent subagent limit reached"
maxBudgetUsd / max_budget_usd nessun default -> subtype "error_max_budget_usd"
Il limite di concorrenza è istruttivo: «the error tells Claude not to retry». L’errore trasporta anche la reazione attesa.
Il contesto è la risorsa, non il token
Sull’esplorazione di codebase grandi il vincolo è dichiarato: le impostazioni tarate su progetti piccoli «can fill the context window with instructions and file reads unrelated to the task, costing tokens and degrading Claude’s performance». Non è solo una questione di spesa, perché «LLM performance degrades as context fills». E i sistemi multi-agente partono già in salita: «Multi-agent systems use about 15x more tokens than chats».
La prima famiglia di leve riduce quello che entra: CLAUDE.md per directory invece di un file radice che copre ogni sottosistema, claudeMdExcludes per i package in cui non lavori mai, regole Read in permissions.deny su build output e codice vendorizzato, un plugin di code intelligence per saltare a una definizione invece di scandire l’albero, worktree.sparsePaths per non scrivere su disco l’intero repository.
Isolare l’esplorazione, non solo comprimerla
La seconda famiglia isola chi esplora: un sottoagente gira in una finestra separata e «intermediate tool calls and results stay inside the subagent; only its final message returns to the parent». È l’antidoto al fallimento che le best practice chiamano «The infinite exploration». Il prezzo è che l’isolamento vale nei due sensi: «The only content you pass from parent to subagent is the Agent tool’s prompt string», quindi percorsi, messaggi di errore e decisioni già prese vanno scritti dentro quel prompt a mano.
Un dettaglio che salva le sessioni lunghe: il piano prodotto in plan mode viene reiniettato dopo ogni compattazione, «so the plan survives where conversation history may not». La conversazione è volatile, un file no. Ed è la stessa logica del checkpoint: quando l’errore arriva, quello che conta è cosa era stato scritto fuori dal contesto.