./run dipartimento-it-agenti-che-dibattono
Il dipartimento IT che litiga da solo: tre strati e un umano nel loop
Un dipartimento IT fatto di agenti AI — NOC, SOC, SRE, Architecture Board — che osservano un cluster k8s, si confutano a vicenda e preparano decisioni per un umano. Architettura a tre strati, gotcha onesti.
- status
- draft
- project
- IT-department
- updated
- 2026-07-07
- tags
C’è un momento preciso in cui un operatore singolo capisce di essere troppo piccolo per i tool enterprise e troppo grande per gestire tutto a mano: quando davanti a un alert “OSD lento” deve decidere da solo se comprare un disco, spostare un carico o non fare niente — e non ha nessuno con cui litigare prima di decidere. IT-Department nasce da lì: un dipartimento IT fatto di agenti AI che si confutano a vicenda, così l’umano riceve una decisione già dibattuta invece di un allarme grezzo.
L’idea: non uno script, un organigramma
Il sistema gestisce un cluster Kubernetes self-hosted su bare metal, con storage Ceph e servizi dentro e fuori dal cluster. Ma il punto non è cosa gestisce — è come pensa. Invece di un unico agente onnisciente, ho modellato sei team con mandati distinti:
- NOC — occhi e orecchie: metriche, health check, primo intervento.
- SOC/CERT — sicurezza: CVE, hardening, certificati, incident response.
- SRE — capacity planning, Ceph, DR, draft di ticket tecnici.
- Architecture Board — il tavolo delle decisioni: fa il devil’s advocate su ogni proposta.
- Procurement — scouting del mercato hardware.
- Project Office — migrazioni e change management.
La regola che tiene insieme tutto è una sola: i team si alimentano e si contraddicono. Se il NOC dice “serve più RAM”, l’SRE può ribattere “no, il collo di bottiglia sono i dischi, guarda le latency di Ceph”. Il Board pesa entrambe le posizioni. L’umano decide informato.
Architettura: tre strati, una separazione netta
La spina dorsale è un modello a tre strati, ripetuto ossessivamente in ogni file di istruzioni:
- Layer 1 — Directive (cosa fare): SOP in Markdown in
directives/. Sono istruzioni in linguaggio naturale, come le daresti a un dipendente di medio livello. Ogni team ha la sua. - Layer 2 — Orchestration (decidere): l’LLM. Legge la directive, chiama gli strumenti giusti nell’ordine giusto, gestisce gli errori. È la colla tra intento ed esecuzione.
- Layer 3 — Execution (fare): script Python deterministici in
execution/. API, parsing, kubectl, file. Affidabili, testabili, veloci.
Il razionale è scritto nero su bianco nel CLAUDE.md, ed è la frase che giustifica l’intera struttura:
Se fai tutto tu, gli errori si moltiplicano. 90% di accuratezza per step = 59% di successo su 5 step. La soluzione è spingere la complessità dentro codice deterministico.
Tradotto: l’LLM non deve fare il lavoro, deve decidere quale script deterministico invocare. Un collect_ceph_status.py che parsa ceph osd perf non sbaglia una volta su dieci come farebbe un modello che “guarda” l’output. L’intelligenza si aggiunge sopra i dati deterministici, non li sostituisce.
Come si sveglia un umano: due livelli e una macchina a stati
Qui sta il cuore. Non tutto merita di svegliare l’operatore. L’orchestrator distingue due classi di azione:
- L1 — autonomo: manutenzione ordinaria che il sistema fa da solo. Esempio reale dal codice: se in namespace Velero ci sono più di 10 pod
Succeeded, li ripulisce senza chiedere. - L2 — richiede autorizzazione: tutto ciò che è distruttivo o costoso. Qui il sistema non agisce: prepara e segnala.
E c’è una scelta che dice tutto sulla filosofia — i pod in CrashLoopBackOff vengono rilevati e loggati, mai riavviati in automatico:
if "CrashLoopBackOff" in line:
# log only, non restart automatico
crashloop_pods.append({"namespace": ..., "name": ..., "restarts": restarts})
Un pod che crasha 2400 volte non è un problema di riavvio: è un sintomo. Riavviarlo in automatico nasconderebbe la malattia. Meglio un alert L2.
Ma il pezzo che mi rende più orgoglioso è la macchina a stati delle decisioni (decision_manager.py). Ogni proposta del Board diventa un file JSON con un lifecycle esplicito:
NEW → NOTIFIED → SEEN → ACCEPTED/REJECTED → IMPLEMENTED
NOTIFIED → (5 giorni senza SEEN) → EXPIRED → re-analisi → NEW
Le transizioni sono validate a monte — non puoi saltare da NEW a IMPLEMENTED:
valid_transitions = {
"NEW": ["NOTIFIED"],
"NOTIFIED": ["SEEN", "EXPIRED"],
"SEEN": ["ACCEPTED", "REJECTED"],
"ACCEPTED": ["IMPLEMENTED"],
"REJECTED": [], "IMPLEMENTED": [],
"EXPIRED": ["NEW"],
}
Il dettaglio elegante è EXPIRED → NEW. Se una decisione resta 5 giorni senza che l’umano la guardi, non scade nel silenzio: viene ri-analizzata con i dati freschi e ri-proposta, con un refresh_count che cresce. Il sistema insiste, ma non decide mai al posto tuo. È l’opposto dell’alert-fatigue: invece di 200 notifiche identiche, una decisione che si ripresenta più informata.
Il gotcha onesto: il dibattito non è (ancora) codice
Ecco la parte che un README venderebbe diversamente. La documentazione dice: “Orchestrazione: AGNO framework per team multi-agent con ruoli e collaborazione”. Ho fatto grep -rn agno su tutto il repo. Zero import. Zero Agent(), zero Team().
Il framework multi-agente, oggi, è aspirazionale. Il dibattito tra NOC e SRE non avviene tra due processi Python che si scambiano messaggi: avviene dentro un LLM che, leggendo le directive dei vari team come persona, gioca entrambi i ruoli e produce un Decision Record strutturato. Il “confronto” è un pattern di prompt orchestration, non un’architettura di agenti concorrenti.
Funziona? Sì, sorprendentemente bene — perché la sostanza vera sta negli script deterministici (quelli esistono e girano) e nella macchina a stati (quella esiste e valida). Il layer AGNO è la ciliegina che formalizzerà i ruoli, ma la torta regge già senza. È esattamente il tipo di drift README/realtà che va detto ad alta voce: il valore non è nel framework citato, è nel fatto che i dati sono affidabili e l’umano resta nel loop.
Come va: operativo per davvero
Non è un POC da slide. Le fasi 0–4 sono completate e il dipartimento produce output reali. Un ciclo recente ha tirato fuori, tra le altre cose:
- NOC/SRE: 7 CRITICAL + 8 WARNING su 7 nodi; un nodo con storage Ceph ~19× più lento di un altro (root cause: HDD sotto stress I/O, non CPU/RAM); un control plane a 2 vCPU saturi al 100%; un worker con 283 pod, il 55% del cluster.
- Capacity: ~120 PVC, ~2.1TB richiesti su ~43TB disponibili (12% usato) — nessun panico storage, il collo di bottiglia è la latenza, non lo spazio.
- SOC: 6 CRITICAL e 124 WARNING (SSL flexible, niente fail2ban sui bare metal, decine di record DNS con IP origine esposto), a fronte di certificati TLS tutti validi.
Ogni finding è confluito in uno dei 4 Decision Record P0/P1 che aspettano — pazientemente, con il loro refresh_count — la mia decisione. L’automazione continua (agent di raccolta via cron ogni 5 minuti, alerting) è la fase incrementale successiva.
Cosa ho imparato
- La reliability non viene dall’LLM, viene da dove lo togli. Ogni volta che ho spostato logica dal ragionamento del modello a uno script deterministico, il sistema è diventato più prevedibile. Il modello deve scegliere, non calcolare.
- Il dibattito serve più della risposta. Costringere il sistema a produrre sempre un’opzione “non fare nulla”, un devil’s advocate e una dissenting opinion ha cambiato la qualità delle raccomandazioni più di qualsiasi prompt “sii intelligente”.
- Far scadere una decisione è un feature, non un bug.
EXPIRED → NEWè la differenza tra un sistema che ti tormenta e uno che ti ricorda le cose al momento giusto, con dati aggiornati. - Scrivi il drift a voce alta. Dire “AGNO è documentato ma non ancora implementato” costa un attimo di orgoglio e compra tutta la credibilità del resto.
Il sistema pensa, si confuta, e prepara. Ma l’ultima parola resta dov’è giusto che sia: sull’operatore.