@ :~/lab/59-percento-tre-strati-agenti-agno$
← cd ~/lab

./run 59-percento-tre-strati-agenti-agno

Il 59%: perché ho smesso di lasciar fare tutto all'LLM

Diario di uno scaffold multi-agente su Agno: tre strati direttiva/orchestrazione/esecuzione, SOP in linguaggio naturale sopra, Python deterministico sotto. Perché e cosa ho imparato.

status
draft
project
agno-agents
updated
2026-07-07
tags
#agno#multi-agent#llm#orchestrazione#python#sop
generate cover # alt: Diagramma a tre strati di un'architettura multi-agente: direttive in Markdown sopra, orchestrazione LLM al centro, script Python deterministici sotto

Fai cinque passi con un LLM, ognuno affidabile al 90%. Quanto ci arrivi intero? 0.9^5 = 0.59. Cinquantanove per cento. In pratica: una pipeline agentica di cinque step “quasi sempre giusti” fallisce due volte su cinque. Questo numero — che non è mio, è banale aritmetica — è il motivo per cui ho passato un weekend a costruire uno scaffold che toglie lavoro all’LLM invece di dargliene.

L’idea

agno-agents è un’impalcatura personale per automazioni multi-agente costruita sul framework Agno (v2.5+). Ma il cuore non è Agno: è una regola architetturale che mi sono imposto, scritta nero su bianco nel CLAUDE.md del progetto. Tre strati, responsabilità separate:

  • Strato 1 — Direttiva (cosa fare). SOP in Markdown dentro directives/. Sono istruzioni in linguaggio naturale, scritte come le daresti a un dipendente di medio livello: obiettivi, input, tool da usare, output attesi, casi limite.
  • Strato 2 — Orchestrazione (decidere). Questo è l’LLM. Il suo unico mestiere è routing intelligente: legge la direttiva, chiama gli script giusti nell’ordine giusto, gestisce gli errori, chiede chiarimenti. Non fa il lavoro sporco.
  • Strato 3 — Esecuzione (fare). Script Python deterministici in execution/. Chiamate API, I/O, database. Testabili, veloci, ripetibili.

La frase che tengo appesa: non provi a scrapare tu il sito — leggi directives/scrape_website.md, prepari input e output, poi lanci execution/scrape_single_site.py. L’LLM non è il muratore, è il caposquadra.

Come funziona davvero

Sopra questi tre strati “filosofici” gira un secondo pattern, più concreto: un meta-team che costruisce altri team. C’è un Architect Team (Tier 1) di 4 agenti — Analyzer, Designer, Soul Writer, Builder — che prende un prompt tipo “mi serve un team che scriva post Instagram per hotel” e sputa fuori un progetto deployabile: file SOUL, un manifest YAML, e la registrazione in DB. Poi il Task Team (Tier 2) esegue davvero.

Il pezzo che mi piace di più è come sono cablati i team annidati nel team_factory.py. Un sotto-team di worker che collaborano, e un sotto-team di reviewer che invece non devono collaborare:

# Worker: condividono contesto, si costruiscono sopra a vicenda
work_team = Team(
    name=f"{manifest.project_id}-workers",
    mode="coordinate",
    members=worker_agents,
    share_member_interactions=True,   # <-- vedono il lavoro l'uno dell'altro
    ...
)

# Reviewer: broadcast, ognuno cieco rispetto agli altri
review_team = Team(
    name=f"{manifest.project_id}-reviewers",
    mode="broadcast",
    members=reviewer_agents,
    share_member_interactions=False,  # <-- niente groupthink
    ...
)

Quel share_member_interactions=False sui reviewer è una decisione precisa: se i revisori si vedono i punteggi a vicenda, convergono. Volevo pareri indipendenti, quindi ogni reviewer vede solo l’output originale, dà un voto 0-100, e il leader sintetizza. Se anche uno solo sta sotto la soglia, il lavoro torna indietro. La soglia non è un numero magico ma dipende dal dominio: 70 per il marketing, 75 per la ricerca, 80 per il codice, 85 per legale/compliance. Più alta la posta, più cattivo il gate.

Ogni agente è definito da un SOUL — un YAML validato da un modello Pydantic (SoulConfig) che diventa la personalità e i vincoli dell’agente:

name: quality-reviewer
display_name: The Hawk-Eye
role: Quality Reviewer
communication_style: blunt
is_reviewer: true
review_score_threshold: 80
review_criteria:
  - "Accuracy: i fatti sono corretti e citati?"
  - "Clarity: la scrittura è chiara e strutturata?"
constraints:
  - "Approvare lavoro senza feedback specifico e azionabile."

Sotto c’è il resto: 3 provider (openai gpt-4o/mini, anthropic sonnet-4/haiku-4.5, ollama locale), un TOOL_REGISTRY di 12 tool risolti dinamicamente per nome, SQLite per la memoria per-progetto, e un dashboard FastAPI per l’approvazione umana finale. Circa 3.300 righe di Python nel solo execution/.

La decisione dura: due volte umano nel loop

La tentazione, quando costruisci roba agentica, è l’auto-approvazione. “I reviewer hanno detto 88/100, spedisci”. Ho scelto il contrario: nessun output è completo finché un umano non lo firma. Due gate in sequenza — peer review automatica, poi human_approval_tool che serializza l’intero deliverable in JSON e lo mette in coda su dashboard. E se il lavoro fallisce 3+ giri di review, il leader smette di ostinarsi e chiama notify_human_tool con urgenza high. Niente loop infiniti che bruciano token in silenzio.

È lento? Sì. Ma il punto di tutto lo scaffold è che non mi fido dell’LLM per le cose deterministiche, e non mi fido nemmeno dei reviewer LLM per l’ok finale. La coerenza la garantisce Python; il giudizio finale lo garantisco io.

Gotcha onesti

Un progetto è credibile quando ammette dove il README e la realtà divergono. Qui divergono in tre punti:

  • data/projects/ è vuota. Lo scaffold sa come generare e far girare un team, ma non ho ancora committato un progetto end-to-end che sia nato dall’Architect e abbia prodotto un deliverable approvato. Il meta-team è codice + documentazione, non ancora una demo che gira.
  • Tre file “mirror”, ma uno non esiste. In cima a CLAUDE.md c’è scritto “mirrored across CLAUDE.md, AGENTS.md, and GEMINI.md”. CLAUDE.md e AGENTS.md sono byte-identici — GEMINI.md non c’è. Documentazione che descrive un’intenzione, non lo stato.
  • La chiave che non porta a nulla. Il .env espone GOOGLE_API_KEY, ma il registry provider in config.py conosce solo openai, anthropic, ollama. Google è cablato nell’ambiente ma non nel codice.
  • Bonus infrastruttura: ci sono manifest k8s completi (Postgres, ingress, networkpolicy) per un sistema che non ha ancora fatto girare un singolo progetto reale. Ho impalcato il deploy prima del prodotto — l’errore classico.

C’è anche una tensione concettuale che tengo a mente: il “tre strati” della filosofia (direttiva/orchestrazione/esecuzione) e il “due tier” del runtime (Architect → Task Team) sono due mappe diverse dello stesso territorio. Comodo per me, potenzialmente confuso per chiunque altro apra il repo.

Come va

POC, onestamente. Un singolo commit, “full implementation”, che è più un’impalcatura densa che un prodotto. I pezzi ci sono e stanno in piedi: config, sicurezza (validazione path, redazione secret con regex, guardie contro path traversal), factory dei team, loader dei SOUL, dashboard. Quello che manca è il chilometraggio: farlo girare abbastanza da scoprire dove si rompe davvero, e poi self-anneal — che è la parte del CLAUDE.md che mi ripromette di aggiornare le direttive ogni volta che imparo qualcosa. Per ora ho imparato solo scrivendolo.

Cosa ho imparato

  • Il 59% è la tesi, non un dettaglio. Ogni decisione dello scaffold — SOP in Markdown, script deterministici, gate multipli — esiste per spostare passi fuori dalla colonna “probabilistico” e dentro quella “deterministico”. Meno passi affida all’LLM, più alto quel numero risale verso il 100%.
  • La separazione dei concern è più facile da scrivere che da rispettare. È tentativamente facile far fare “solo un piccolo scraping” all’LLM in orchestrazione. La disciplina è dire di no e scrivere lo script.
  • L’indipendenza dei reviewer è un flag booleano. share_member_interactions=False è una riga; il groupthink evitato vale molto di più.
  • Impalcare il deploy prima di avere un output reale è vanità. I miei manifest k8s sono più maturi della mia cartella data/projects/ vuota. La prossima riga di codice utile non è in Kubernetes: è il primo progetto che nasce, gira e viene approvato per davvero.