Dopo mesi passati a costruire, rompere e ricostruire sistemi agentici “seri”, ho messo per iscritto un metodo per decidere quando un agente AI serve davvero e quanti. Non è un manifesto contro gli agenti: il punto è che “voglio usare gli agenti” non è un requisito, è una soluzione possibile, e va scelta dopo aver capito il problema, non prima.
Per renderlo concreto invece che teorico, ho preso un caso che chiunque riconosce: una cartella Download piena all’orlo di fatture in PDF, screenshot con nomi tipo Schermata 2024-03-11 alle 18.42.png, installer mai aperti, zip doppi, IMG_4471.jpg, referti mescolati a documenti di lavoro. L’obiettivo: un sistema che legga i file, capisca cosa sono, li smisti in cartelle sensate, elimini i doppioni e si segni ogni decisione.
Un problema come questo non richiede un planner, un supervisor, un reviewer e un evaluator: serve un agente solo, non una colonia.
Il giudizio umano-simile serve solo per una cosa: capire se un file sconosciuto, tipo scan0007.pdf, è una bolletta o un referto medico. Quello è lavoro da modello linguistico. Tutto il resto — spostare, contare, riconoscere i doppioni, assegnare i nomi — è codice deterministico, senza bisogno di alcuna intelligenza artificiale.
Anche la scala del problema, una volta misurata invece che immaginata, si è ridimensionata. La cartella conteneva novemila file in totale, ma la gran parte era già dentro vecchie sottocartelle ordinate. Quelli davvero da sistemare erano 364, un decimo di quanto la percezione iniziale suggeriva.
Il ciclo esegui-verifica-riprova, sottovalutato
Una parte consistente dei problemi che sembrano complessi si risolve con un pattern semplice: esegui il task, verifica il risultato, se il risultato non va bene riprova, poi continua. Molti sistemi che dall’esterno sembrano sofisticati, sotto sotto applicano esattamente questo loop. La differenza sta nel passaggio di verifica, che è quello più spesso saltato.
Nel mio sistema, l’agente che classifica un file restituisce anche un punteggio di confidenza. Se è alto, la decisione è definitiva. Se è basso, il loop non si blocca e non improvvisa: raccoglie più contesto (più testo del file) e riprova con l’istruzione esplicita di essere decisivo. Se anche al secondo tentativo la confidenza resta bassa, il file finisce in quarantena invece che smistato a caso.
# primo tentativo: poca anteprima di contenuto, costa poco
res = classify(file, content_preview(path, 2000))
if res["confidence"] < CONFIDENCE_THRESHOLD: # in dubbio?
res = classify(file, content_preview(path, 8000),
decisive=True) # più contesto, e sii deciso
# più avanti, al momento di spostare:
if res["confidence"] < CONFIDENCE_THRESHOLD:
folder = QUARANTINE_DIR # ancora incerto: in quarantena
else:
folder = CATEGORIES[res["category"]] # sicuro: al suo posto
Il caso limite: un file senza nome, senza estensione, cinque byte casuali. Confidenza 0,30 dopo due tentativi, spedito in quarantena. Un agente che ammette di non saperlo vale più di uno che restituisce una risposta sbagliata con sicurezza.
Responsabilità piccole, non un prompt che fa tutto
La tentazione successiva è un unico prompt gigante che legge, classifica, propone la cartella, inventa il nome, controlla i doppioni e spiega il ragionamento. Sembra efficiente, mentre in pratica è il modo più rapido per ottenere risultati mediocri su tutto: i modelli linguistici diventano più affidabili quando fanno una cosa sola alla volta.
Per questo il sistema è diviso in tre fasi nette. La prima è deterministica: calcola le impronte hash dei file e raggruppa i doppioni, zero chiamate al modello. La seconda è l’unica fase agentica: una domanda sola, “che cos’è questo file?”. La terza è di nuovo deterministica: sposta i file, gestisce le collisioni di nome, scrive il log. Mettere la deduplica prima della classificazione ha un vantaggio diretto: i doppioni non costano nemmeno una chiamata al modello.
# FASE 1 deterministica, zero token: hash e raggruppa i doppioni
for p in files:
by_hash.setdefault(sha256_of(p), []).append(p)
for p in files:
if file_hash[p] in seen: # già archiviato in una run prima
dups.append(p)
elif by_hash[file_hash[p]][0] == p: # prima volta che lo vedo
keepers.append(p)
else: # doppione interno a questa run
dups.append(p)
# FASE 2 l'unico agente, in parallelo: "che cos'è questo file?"
with ThreadPoolExecutor(max_workers=8) as ex:
for p, res, err in ex.map(work, keepers):
results[p] = (res, err)
# FASE 3 deterministica: sposta, niente sovrascritture, logga
for p in keepers:
dest = unique_destination(cartella(results[p]), nome_pulito(p))
os.rename(p, dest)
log(p, dest, results[p])
Tenere separate queste fasi non è pignoleria formale: ogni fase si rompe in modo diverso e si aggiusta in modo diverso. Se i nomi generati sono sbagliati, si corregge solo quel pezzo senza toccare la classificazione. Responsabilità piccole significano guasti piccoli e circoscritti — vale anche per i prompt: più cose ci si infila dentro, più aumenta il rischio che si perda proprio l’informazione che serviva.
Il vero banchmark è il costo, non il funzionamento
Costruire una demo che funziona è relativamente facile. Farla costare un decimo è molto più difficile, ed è una delle competenze più sottovalutate nello sviluppo di sistemi con LLM: ottenere il risultato voluto con il modello più economico disponibile.
Il mio sistema usa un modello DeepSeek economico (deepseek-chat, versione flash) con una sola chiamata per file, circa 600 token a chiamata. Tutto il resto — hash, doppioni, conteggi, cartelle, nomi — è codice che non consuma token. Questi sono i numeri della run reale sulla cartella Download: 364 file processati, 265 file organizzati, 95 doppioni isolati, 0 errori sulla run reale.
Il totale: circa 162.000 token, per una spesa di pochi centesimi. Il confronto è con l’architettura “barocca”: se per ogni file si fanno parlare planner, supervisor, classificatore, reviewer ed evaluator, servono cinque o sei chiamate a un modello grande solo per spostare un file. Moltiplicato per decine di migliaia di file, e passando da dieci richieste al giorno a centomila, la differenza di costo diventa immediatamente centrale. È un problema che si progetta fin dall’inizio, non che si sistema dopo su un’architettura già costosa.
Nessun framework, un file Python
Decisi gli agenti necessari, la domanda successiva è tipicamente: con quale framework? LangGraph, CrewAI, AutoGen, o quello uscito ieri. La mia risposta è che per un problema come questo il framework spesso non serve affatto. Il sistema è un unico file Python. La chiamata al modello è una richiesta HTTP fatta con la libreria standard, senza dipendenze installate. La coda dei file incerti è una cartella. La memoria di cosa è già stato visto è un file di testo — componenti comprensibili, debuggabili, e destinati a restare leggibili nel tempo.
Il costo nascosto di un framework: regala un grafo di agenti funzionante in due righe di codice, ottimo per una demo. Ma quando serve capire perché un PDF è finito nella cartella sbagliata, si finisce a fare reverse engineering del suo loop interno, dei retry impliciti, del modo proprietario di gestire lo stato. Una dipendenza pesante per risparmiare codice che sarebbe stato comunque comprensibile.
Il contesto giusto batte il modello grosso
C’è un’idea diffusa secondo cui la qualità di un sistema dipenda soprattutto dalla potenza del modello. Nella pratica, retrieval, contesto e qualità dei dati contano quanto o più del modello: un modello mediocre con le informazioni giuste batte spesso un modello enorme senza contesto.
Nel sistema, le categorie di destinazione sono fisse — sempre le stesse — invece di essere reinventate a ogni file, altrimenti due esecuzioni diverse produrrebbero due tassonomie diverse. E soprattutto il sistema ricorda gli hash dei file già archiviati: rimettendo nella cartella una copia identica di un PDF già sistemato il giorno prima, il sistema l’ha riconosciuta come doppione di una run precedente senza interpellare il modello.
La cartella stessa, con lo stato delle sue sottocartelle e il registro delle decisioni prese, è la memoria più preziosa del sistema. Un modello mediocre con quella memoria produce risultati coerenti nel tempo; un modello eccezionale senza memoria reinventa l’ordine ogni volta.
Gli LLM non sanno contare
È un punto ripetuto spesso perché è all’origine di molti errori: i modelli linguistici non sono database, non sono motori SQL, non sono calcolatrici. Ragionano sul linguaggio, e in quello sono efficaci. Per contare record, verificare somme, confrontare numeri o fare aggregazioni serve un tool deterministico, non il modello.
I doppioni sono l’esempio più chiaro: stabilire se due file sono identici non è una questione di giudizio, è una questione di impronta digitale. Si calcola l’hash e si confronta — o sono uguali, o non lo sono, senza margine di ambiguità.
def sha256_of(path, buf=1024 * 1024):
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(buf), b""):
h.update(chunk)
return h.hexdigest() # o due file sono identici, o non lo sono
Nella cartella reale, l’hash ha individuato file identici nel contenuto ma con nomi completamente diversi: un README.pdf byte per byte uguale a un altro documento che con “readme” non c’entrava nulla, e un’estensione del browser scaricata due volte sotto due nomi diversi. Per un occhio umano, e per un LLM che ragiona sui nomi, sarebbero apparsi come file diversi. Per l’hash, doppioni esatti — in tutto 95 copie, materiale riscaricato nel tempo. Al modello il ragionamento, al codice i conti: così quando il sistema dichiara “95 doppioni” è un fatto verificabile, non una stima approssimativa.
Il log come rete di sicurezza
Una sezione facile da sottovalutare finché tutto funziona. Quando un sistema sposta e rinomina file, ogni riga di log conta: prompt, esito, confidenza, azione, errori, token, tempi. Il rischio concreto: il sistema classifica una categoria di file come “roba temporanea” e ne sposta tremila, e dentro a quel lotto finiscono venti contratti importanti. Senza log, la cartella risulta diversa da un giorno all’altro senza modo di ricostruire perché. Con i log, si apre il registro, si vede la decisione presa e con quale confidenza, e si corregge in dieci minuti.
I log non servono solo a capire cosa è successo, servono anche a tornare indietro: ogni spostamento registrato ha reso possibile costruire un comando di annullamento, che rigioca il log al contrario, rimette ogni file al suo posto originale e cancella le cartelle vuote create nel frattempo. Verificato su 150 file copiati appositamente per il test: organizzati, annullati, tutti tornati al punto di partenza. Con questa rete di sicurezza, lanciare il sistema sulla cartella reale comporta un rischio molto più contenuto.
Git per il registro, non per i file
Una domanda naturale a questo punto: perché non usare git per tracciare i cambiamenti nella cartella? L’idea di partenza è sensata, ma mettere git dentro una cartella Download da 26 gigabyte di foto, installer e archivi compressi è una scelta sbagliata: git è pensato per versionare testo, e su una massa di file binari diventa lento, perché per calcolare le differenze deve mantenere copie multiple di tutto. Il risultato sarebbe raddoppiare lo spazio occupato per un beneficio minore.
Git ha però un uso corretto in questo sistema: non versiona i file spostati, versiona il registro delle operazioni — lo stato dell’organizzatore e i log delle run, che sono testo puro. In questo modo la cronologia delle modifiche, e quando sono avvenute, si consulta con un semplice git log, una riga per passata.
| Elemento | Dimensione |
|---|---|
| Registro testuale versionato | 236 KB |
| Cartella reale, non versionata | 26 GB |
Lo stesso strumento, usato su due bersagli diversi, produce risultati opposti: leggero e utile sul testo, pesante e controproducente sui binari. La scelta di dove applicare uno strumento conta quanto la scelta dello strumento stesso.
Il demone orario, e l’errore quasi ripetuto
Dopo la prima pulizia, il passo successivo è naturale: un processo che ogni ora controlli se sono arrivati file nuovi e li sistemi. Ed è qui che lo stesso riflesso descritto in apertura dell’articolo si è quasi ripetuto: il primo istinto è stato “ogni ora controllo con git quali file sono cambiati, e organizzo quelli”. Un meccanismo più complicato del necessario, scelto per riflesso invece che per bisogno reale.
La verifica sul foglio ha chiarito il punto: i file nuovi atterrano sempre nella radice della cartella, mai nelle sottocartelle già organizzate, che l’organizzatore ignora. Quindi “i file cambiati” coincidono semplicemente con “i file in radice”: non serve alcun meccanismo di rilevamento differenze, perché la radice è già, di per sé, la coda di lavoro. Rilanciare il sistema organizza solo gli arrivi nuovi, e la memoria degli hash intercetta le copie di materiale già archiviato.
Il demone risultante è quindi semplice: un job pianificato che ogni ora avvia il sistema sulla cartella. Se in radice non c’è nulla di nuovo, il giro si chiude subito senza consumare un solo token. Se sono arrivati file, li classifica, li sposta e aggiorna il registro. Nella verifica pratica: a vuoto il sistema segnala “niente da fare”; con due file nuovi, uno è finito tra le fatture e l’altro, copia identica di un file già archiviato, è stato riconosciuto come doppione — 277 token per un giro che a cartella invariata costa zero.
Un agente. Uno. Si divide solo quando serve davvero
Il punto centrale, una volta stabilito con la prova del foglio che un agente serve davvero, è partire da uno solo: un loop con un modello che interpreta e codice deterministico intorno. Farlo girare sul caso reale, osservarlo lavorare, e solo dopo — se necessario — dividerlo.
Quando ha senso dividere l’agente in più componenti:
- Rottura reale: l’agente unico si rompe perché ha troppe responsabilità insieme.
- Collo di bottiglia: il singolo agente diventa il limite misurabile delle prestazioni del sistema.
- Competenze specialistiche: emergono compiti davvero distinti, non solo varianti dello stesso compito.
- Mai per estetica: non si divide un agente il primo giorno solo perché “sembra più architetturale”.
Nel sistema descritto, l’organizzatore è rimasto un agente solo: un modello per capire cosa sono i file, tre fasi di codice deterministico intorno, nessun motivo concreto per separarlo in più componenti. Se in futuro aggiungerò il riconoscimento OCR per i PDF scansionati o un riconoscitore di volti per le foto, quelle saranno competenze realmente distinte — e solo allora avrà senso introdurre altri agenti.
Il codice
Il sistema completo è pubblicato su GitHub, nella cartella scripts/diecimila-file-un-agente-solo/. Gira con la sola libreria standard di Python; la chiamata al modello è una richiesta HTTP diretta, senza dipendenze da installare.
| File | Cosa fa |
|---|---|
organize.py |
Il sistema completo: tre fasi (hash e dedup, l’unico agente che classifica con DeepSeek, spostamento), retry e quarantena, --undo dal log, --journal su git |
make_sandbox.py |
Genera una cartella Download finta ma realistica (doppioni esatti compresi) per testare il sistema senza rischi |
com.signalpirate.download-organizer.plist |
Il demone orario: un LaunchAgent macOS che sistema solo i nuovi arrivi in radice |
README.md |
Comandi, modalità dry-run e apply, note sui limiti |
Cosa resta
Il messaggio di fondo non è che gli agenti AI siano una moda da evitare: vanno trattati come una soluzione da scegliere consapevolmente, non come punto di partenza automatico. Partire dal problema. Usare un loop che verifica e riprova. Dividere le responsabilità in pezzi piccoli. Tenere i conti fuori dal modello, affidati al codice. Misurare il costo, non solo il funzionamento. Diffidare del framework di turno. Dare al sistema la memoria del contesto che è già disponibile sul disco. Loggare tutto. Usare git per il testo, non per i file binari pesanti. E partire da un agente solo, dividendolo esclusivamente quando il problema lo richiede in modo concreto.
Cosa questa analisi non fa: non inventa una nuova tassonomia a ogni esecuzione (le categorie sono fisse per garantire coerenza tra run diverse); non decide mai “a naso” sui doppioni (la deduplica è sempre basata su hash crittografico, non su somiglianza di nome o giudizio del modello); non forza una classificazione a bassa confidenza (sotto soglia, il file va sempre in quarantena, mai smistato per approssimazione); non versiona i file binari con git (solo il registro testuale delle operazioni).
La cartella Download è un esempio volutamente minimo, utile proprio perché chiunque lo riconosce e la sproporzione fra soluzione barocca e soluzione sobria si vede a colpo d’occhio. Lo schema, però, si applica a problemi ben più complessi: cambia la posta in gioco, non la logica. “Voglio usare gli agenti” non è un requisito. È una soluzione possibile. Prima il problema, poi la decisione.
Nota metodologica: il sistema descritto è un singolo file Python che usa la libreria standard e una chiamata HTTP a un modello DeepSeek economico per la sola fase di classificazione; deduplica, conteggi, policy delle cartelle, gestione delle collisioni e logging sono codice deterministico. I numeri riportati vengono dalla run reale sulla mia cartella Download (364 file, 0 errori) e dai test di annullamento su 150 file copiati separatamente per la prova. I blocchi di codice riprodotti in questo articolo sono estratti, leggermente condensati per leggibilità rispetto alla versione integrale disponibile nel repository.
Andrea Amani — Security Engineer · Signal Pirate
Security engineer. Sul suo laboratorio Signal Pirate smonta algoritmi, protocolli e sistemi e ne pubblica l’analisi tecnica con dati reali e codice riproducibile. Studia come funzionano le cose, dalla sicurezza offensiva agli agenti AI, e scrive quello che trova.
#Adessonews seleziona nella rete articoli di particolare interesse.
Se vuoi leggere l’articolo completo clicca sul seguente link
Andrea Amani
Source link





