2026-06-22 | Pinperepette

Diecimila File, Un Agente Solo

Dopo mesi a costruire, rompere e ricostruire sistemi agentici, un po' di appunti su come si ragiona davvero. Non "evita gli agenti": li uso e funzionano. Ma parti dal problema, poi decidi se ti servono e quanti. Per renderlo concreto metto in ordine una cosa che hai sul disco anche tu: la cartella Download.

Agenti AI Architettura LLM Python

// Parti Dal Problema, Poi Decidi Degli Agenti

Sezione 00. "Voglio usare gli agenti" non e' un requisito

Mi fanno spesso domande sugli agenti. Sanno che sono di Piacenza, e forse pensano che da queste parti di agenti ce ne intendiamo. Del resto e' la citta' dove hanno arrestato un'intera caserma, quindi diciamo che qui il concetto di "agente" e' sempre stato interpretato in modo creativo. Ti fermavano e ti chiedevano se avevi della droga, non per sequestrarla, per capire se ne volevi comprare dell'altra. Comunque. Dopo parecchi mesi passati a costruire, rompere, debuggare e ricostruire sistemi agentici di quelli seri, mi sono segnato un po' di appunti. Non per dirti di non usare gli agenti: li uso, e funzionano. Per dirti come ci si ragiona prima di tirarne su mille.

E per renderlo concreto invece che teorico, prendo una cosa che capisce chiunque: una cartella Download piena fino all'orlo. Fatture in PDF, screenshot con nomi tipo Schermata 2024-03-11 alle 18.42.png, installer mai aperti, zip scaricati due volte, IMG_4471.jpg, documento (3).pdf, referti, roba di lavoro mischiata ai meme. Vuoi che qualcuno la sistemi: legga i file, capisca cosa sono, faccia le cartelle giuste, dia nomi sensati, tolga i doppioni, ci riprovi quando e' in dubbio e si segni tutto.

Adesso guarda cosa succede appena lo dici in una stanza piena di gente sveglia. Parte la liturgia: ci vuole un planner che decide la strategia, un supervisor che coordina, un reviewer che controlla, un evaluator che controlla il reviewer. In dieci minuti hai sei riquadri che si parlano addosso, per spostare dei file in delle cartelle. Il punto che saltano tutti: "voglio usare gli agenti" non e' un requisito, e' una soluzione possibile. Meta' delle architetture che vedo nasce al contrario, dalla soluzione verso il problema. Prima scriviti su un foglio cosa devi fare. Poi decidi se ti servono gli agenti, e quanti.

La risposta, per la cartella, non e' "zero agenti". Scrivendo il foglio ti accorgi che un pezzo richiede davvero del giudizio: capire se scan0007.pdf e' una bolletta o un referto guardandoci dentro. Quello e' lavoro da modello, ed e' giusto usare un agente per farlo. Ma e' un pezzo. Per spostare, contare, riconoscere i doppioni e dare i nomi non serve nessuna intelligenza, serve del codice. Quindi la risposta onesta e': si', un agente mi serve. Uno. Non una colonia.

E una sorpresa, scoperta solo mettendoci le mani. La cartella, contati i file col codice e non a occhio, ne aveva novemila: sembra un caso da grande orchestrazione. Ma il grosso era gia' dentro a vecchie sottocartelle, e quelli davvero da sistemare erano trecentosessantaquattro. Un decimo di quello che la paura raccontava. Succede quasi sempre: il caso enorme si sgonfia appena lo guardi davvero invece di immaginarlo.

// I Loop Sono Sottovalutati

Sezione 01. Esegui, verifica, se fa schifo riprova, continua

Una quantita' imbarazzante di problemi si risolve con quattro righe in croce:

esegui il task · verifica il risultato · se fa schifo riprova · continua

Tanti sistemi che da fuori sembrano astronavi, sotto sotto, fanno esattamente questo. I loop sono una delle invenzioni migliori dell'informatica e nel 2026 funzionano ancora benissimo. La parte che fa la differenza e' il "verifica", ed e' proprio quella che la gente salta. Sulla cartella Download il mio agente classifica un file e tira fuori una confidenza. Se e' alta, vado. Se e' bassa, il loop non si blocca e non spara a caso: raccoglie piu' contesto e riprova, e se proprio non ne viene a capo mette il file in quarantena invece di indovinare.

1# primo tentativo: poche righe di contenuto, costa poco
2res = classify(file, content_preview(path, 2000))
3if res["confidence"] < CONFIDENCE_THRESHOLD: # in dubbio?
4 res = classify(file, content_preview(path, 8000), # piu' contesto,
5 decisive=True) # e sii deciso
6
7# piu' avanti, al momento di spostare:
8if res["confidence"] < CONFIDENCE_THRESHOLD:
9 folder = QUARANTINE_DIR # ancora incerto: in quarantena
10else:
11 folder = CATEGORIES[res["category"]] # sicuro: al suo posto

"Riprovare" non vuol dire rilanciare identico sperando in un miracolo: vuol dire dare al secondo giro piu' testo e l'ordine di essere deciso. L'ho visto su un file senza nome, senza estensione, cinque byte a caso: confidenza 0,30 dopo due tentativi, in quarantena. Giusto cosi'. Un agente che ammette "questo non lo so" vale piu' di uno che ti sporge una bugia con la faccia sicura.

// Spezzetta Tutto

Sezione 02. Task piccoli, prompt piccoli, responsabilita' piccole

La tentazione e' un unico prompt gigante che fa tutto: leggi, classifica, proponi la cartella, inventa il nome, controlla i doppioni, e spiegami pure il ragionamento. Sembra efficiente, ed e' il modo piu' rapido per ottenere risposte mediocri su tutto. Gli LLM diventano molto piu' affidabili quando fanno una cosa sola: quando gli chiedi di fare insieme il planner, il programmatore, il revisore, il tester e il filosofo, cominciano a perdere pezzi, e proprio quelli importanti.

Per questo il sistema lavora in tre fasi nette, e a ognuna il suo mestiere. Prima fase deterministica: calcolo le impronte e raggruppo i doppioni, zero modello. Seconda fase, e qui c'e' l'unico agente: una domanda sola, "che cos'e' questo file?". Terza fase di nuovo deterministica: sposto, gestisco le collisioni di nomi, scrivo il log. Mettere la deduplica prima della classificazione ha un effetto bello: i doppioni non costano nemmeno una chiamata.

1# FASE 1 deterministica, zero token: hash e raggruppa i doppioni
2for p in files:
3 by_hash.setdefault(sha256_of(p), []).append(p)
4for p in files:
5 if file_hash[p] in seen: # gia' archiviato in una run prima
6 dups.append(p)
7 elif by_hash[file_hash[p]][0] == p: # prima volta che lo vedo
8 keepers.append(p)
9 else: # doppione interno a questa run
10 dups.append(p)
11
12# FASE 2 l'unico agente, in parallelo: "che cos'e' questo file?"
13with ThreadPoolExecutor(max_workers=8) as ex:
14 for p, res, err in ex.map(work, keepers):
15 results[p] = (res, err)
16
17# FASE 3 deterministica: sposta, niente sovrascritture, logga
18for p in keepers:
19 dest = unique_destination(cartella(results[p]), nome_pulito(p))
20 os.rename(p, dest)
21 log(p, dest, results[p])

Tenere separate queste cose non e' pignoleria: e' che ognuna si rompe in modo diverso e si aggiusta in modo diverso. Se i nomi vengono brutti, sistemi il pezzo dei nomi senza toccare la classificazione. Responsabilita' piccole vuol dire guasti piccoli e circoscritti.

Non ricordo chi fosse il poeta che scrisse "se ti cade per terra qualcosa, ti rimbalza nel culo". Vale pure per i prompt: piu' cose gli infili dentro, piu' quella che si perde e' quella che ti serviva.

// Il Benchmark Che Conta E' Il Costo

Sezione 03. "Funziona" e' facile, "funziona e costa niente" no

Far vedere una demo che funziona e' relativamente facile. Farla costare un decimo e' molto piu' difficile. Una delle skill piu' importanti oggi e' ottenere il risultato che vuoi col modello piu' economico possibile. Qui non ti devo convincere a parole, perche' l'ho misurato. Il modello e' un DeepSeek economico (deepseek-chat, gira sulla loro versione flash): una chiamata per file, circa seicento token a botta. Tutto il resto, hash, doppioni, conteggi, cartelle, nomi, e' codice che non paghi a token. Ecco la run vera:

364
file processati
265
organizzati
95
doppioni isolati
0
errori

Centosessantaduemila token in tutto, pochi centesimi. Adesso l'altro conto: la versione barocca, per ogni file, fa parlare planner, supervisor, classificatore, reviewer ed evaluator. Cinque o sei chiamate a un modello grosso per spostare un file. Moltiplica per le decine di migliaia, e quando passi da dieci richieste al giorno a centomila, di colpo diventate tutti interessatissimi all'ottimizzazione. Quella roba la progetti dall'inizio, non la appiccichi su una colonia di agenti gia' nata cara.

// Smettila Di Inseguire Il Framework Della Settimana

Sezione 04. Python, qualche API, una coda, un database

Deciso che ti servono gli agenti, parte la seconda domanda: con quale framework? LangGraph, CrewAI, AutoGen, quello uscito ieri, quello che esce domani. Prendine uno. Oppure nessuno. Perche' la verita' scomoda e' che, per mettere in ordine dei file, il framework spesso non ti serve proprio. Il sistema intero e' un file Python solo. Per chiamare il modello non ho installato niente: gli mando una richiesta HTTP con la libreria standard e basta. Zero dipendenze. La coda dei file incerti e' una cartella. La memoria di cosa ho gia' visto e' un file di testo. Questi pezzi li capisci tutti, li debugghi tutti, e tra cinque anni esistono ancora.

Quello che il framework ti nasconde. Ti regala un grafo di agenti in due righe, e per la demo e' fantastico. Poi vuoi sapere perche' un PDF e' finito nella cartella sbagliata e ti ritrovi a fare reverse engineering del suo loop interno, dei suoi retry impliciti, del suo modo tutto suo di tenere gli stati. Hai aggiunto una dipendenza enorme per risparmiare il codice che avresti capito al volo. Ho visto sistemi costruiti sopra tre framework diversi fare meno, e peggio, di uno script scritto bene.

// La Memoria Conta Quanto Il Modello

Sezione 05. Il contesto giusto batte il modello grosso

C'e' un'idea dura a morire: che la qualita' dipenda soprattutto da quanto e' potente il modello. Il retrieval conta piu' del modello. Il contesto conta piu' del modello. I dati contano piu' del modello. Sto semplificando, ma non di tanto. Un modello mediocre con le informazioni giuste batte spesso un modello enorme che naviga nel vuoto cosmico.

In concreto: il sistema sceglie tra categorie fisse, sempre le stesse, invece di reinventare la tassonomia a ogni file (altrimenti due lanci ti danno due ordini diversi). E soprattutto ricorda gli hash dei file gia' archiviati. L'ho provato: ho ributtato nella cartella una copia identica di un PDF sistemato il giorno prima, e il sistema l'ha riconosciuto come doppione di un file archiviato in una run precedente, senza nemmeno disturbare il modello.

La cartella e' la sua stessa memoria. Il pezzo piu' prezioso di contesto ce l'hai gia' sul disco: lo stato delle cartelle e il registro di cosa hai deciso. Un modello mediocre con quella memoria produce risultati coerenti. Un modello eccezionale senza, ti reinventa l'ordine ogni volta. La coerenza non viene dal modello, viene da quello che gli fai sapere.

// Gli LLM Non Sanno Contare

Sezione 06. I conti li fa il codice, il ragionamento il modello

Ripetiamolo, perche' e' la fonte di meta' dei disastri: gli LLM non sanno contare. Non sono database. Non sono motori SQL. Non sono calcolatrici. Sono macchine che ragionano sul linguaggio, e in quello sono bravissime. Se devi contare record, verificare somme, confrontare numeri o fare aggregazioni, usa un tool. I doppioni sono l'esempio perfetto: capire se due file sono identici non e' una questione di giudizio, e' una questione di impronta. Calcoli l'hash e confronti. O sono uguali o non lo sono, certo al cento per cento.

1def sha256_of(path, buf=1024 * 1024):
2 h = hashlib.sha256()
3 with open(path, "rb") as f:
4 for chunk in iter(lambda: f.read(buf), b""):
5 h.update(chunk)
6 return h.hexdigest() # o due file sono identici, o non lo sono

E qui c'e' la prova piu' bella, una che il modello non avrebbe mai trovato da solo. Sulla mia cartella l'hash ha beccato 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 niente, e un'estensione del browser ripresa due volte sotto due nomi diversi. Per un occhio umano, e per un LLM che ragiona sui nomi, erano file diversi. Per l'hash, doppioni esatti. In tutto novantacinque copie, roba riscaricata negli anni. Al modello il ragionamento, al codice i conti: cosi' quando il sistema dice "novantacinque doppioni" e' un fatto, non un "circa novanta, credo".

// Logga Qualsiasi Cosa Si Muova

Sezione 07. La differenza tra dieci minuti e due giorni

Questa e' la sezione che la gente salta, e poi se ne pente. Quando costruisci qualcosa che sposta e rinomina file, logga tutto: prompt, esito, confidenza, azione, errori, token, tempi. Una riga per file. Sembra paranoia finche' fila liscio. Poi arriva il giorno, e non e' ipotetico, e' martedi prossimo: il sistema decide che una classe di file e' "roba temporanea" e te ne sposta tremila, dentro ci finiscono venti contratti. Senza log hai una cartella diversa da ieri e nessuna idea di come: due giorni a bestemmiare. Coi log apri il registro, vedi la decisione e con quanta confidenza, e in dieci minuti sai cosa rimettere a posto.

E i log non servono solo a capire, servono a tornare indietro. Visto che ogni spostamento e' scritto, ci ho costruito l'annullamento: un comando rigioca il log al contrario, rimette ogni file dov'era e cancella le cartelle vuote. Verificato su centocinquanta file copiati a parte: organizzati, annullati, tornati tutti al punto di partenza. Con questa rete sotto, lanciarlo sulla cartella vera fa molta meno paura.

Prima o poi diventa un procione. Arrivera' il giorno in cui il tuo agente, sereno, si mettera' a fare il procione sotto metanfetamine alle tre di notte: classifica tutto come "fattura", entra in loop sui nomi, sposta nella cartella sbagliata a raffica. In quel momento i log sono l'unica differenza tra un fix da dieci minuti e due giorni buttati. Non li scrivi per quando va bene. Li scrivi per quel momento li', che arriva sempre.

E si', loggherai anche roba inutile, va benissimo. Meglio una riga di troppo che la riga che ti serviva e non c'e'. Una volta, in un altro sistema, nei log mi e' finita pure la mia cagna, Panna: passava di li' mentre debuggavo e l'ho loggata per sbaglio. E' rimasta. Non ha mai fatto male a nessuno, al contrario del log che non avevo scritto la volta che e' sparita una cartella.

// Git, Ma Per Il Lavoro Giusto

Sezione 08. Duecentotrentasei kilobyte contro ventisei gigabyte

A un certo punto arriva la domanda naturale: "e se usassi git per tenere traccia di cosa cambia nella cartella?". Idea sensata di pancia, e merita di essere presa sul serio, perche' nasconde proprio la lezione di tutto il pezzo. Mettere git dentro alla cartella Download, coi suoi ventisei gigabyte di foto, dmg e zip, e' una pessima idea: git e' fatto per versionare testo, su una montagna di binari diventa lento e per dirti cosa e' cambiato deve tenersi una copia di tutto. Raddoppi lo spazio per un risultato peggiore.

Pero' git un posto giusto ce l'ha. Non versiono i file, versiono il giornale: lo stato dell'organizzatore e i log delle run, che sono tutto testo. Cosi' la cronologia di cosa e' cambiato, e quando, ce l'ho con un semplice git log, una riga per passata. La differenza, misurata: il giornale completo pesa duecentotrentasei kilobyte, la cartella vera ne pesa ventisei miliardi. Stesso strumento, due usi: uno leggerissimo, uno che ti avrebbe fatto piangere.

Il principio, di nuovo. Non e' "git si' o git no". E' "git per cosa?". Per versionare il testo di quello che e' successo: perfetto, costa niente. Per fotografare ventisei gigabyte di binari a ogni giro: lo strumento sbagliato per il lavoro. Scegliere bene dove mettere uno strumento e' meta' del mestiere.

// Il Demone, E Come Stavo Per Rifare L'Errore

Sezione 09. Ogni ora, ma senza inventarsi un detective

Dopo la prima pulizia il passo dopo viene da se': bello, ma io continuo a scaricare roba. Mi serve qualcosa che ogni ora guardi se sono arrivati file nuovi e li sistemi. E qui, te lo confesso, stavo per cadere io stesso nella trappola: il primo pensiero e' stato "ogni ora controllo con git se sono cambiati dei file, e organizzo quelli cambiati". Cioe', appena devo costruire una cosa nuova, il riflesso mi porta dritto verso lo strumento piu' complicato. La stessa malattia che racconto da inizio articolo.

Per fortuna basta fermarsi un secondo e rifare la prova del foglio. I file nuovi dove atterrano? Nella radice della cartella, sempre. Le categorie sono sottocartelle, e quelle l'organizzatore le ignora. Quindi "i file cambiati" sono semplicemente "i file in radice". Non mi serve nessun detective che vada a caccia delle differenze: la radice e' gia', di suo, la coda di lavoro. Rilanciare il sistema sistema solo i nuovi arrivi, e la memoria degli hash becca quelli che sono copie di roba archiviata.

Cosi' il demone diventa banale, e banale e' un complimento: un lavoretto pianificato che ogni ora fa partire il sistema sulla cartella. Se in radice non c'e' niente di nuovo, il giro esce subito senza spendere un solo token. Se e' arrivata roba, la classifica, la sposta e scrive una riga nel giornale git. L'ho provato: a vuoto dice "niente da fare"; con due file nuovi, uno e' finito tra le fatture e l'altro, copia identica di uno gia' archiviato, e' stato riconosciuto come doppione. Duecentosettantasette token, per un giro che a mani vuote costa zero.

La morale, ed e' su di me. Il riflesso di tirare in mezzo git come "rilevatore di modifiche" era esattamente l'errore che predico di evitare: aggiungere un meccanismo furbo per un problema che la struttura giusta aveva gia' risolto da sola. La complessita' non te la propone solo il collega entusiasta: te la propone la tua testa, ogni volta che parti dallo strumento invece che dal problema. Anche dopo anni che lo sai.

// Parti Da Un Agente. Uno.

Sezione 10. Lo dividi quando ti fa male, non prima

Eccoci al punto che tiene insieme tutto. Quando, dopo la prova del foglio, hai deciso che un agente ti serve davvero, partine da uno. Uno solo. Un loop con un modello che capisce e codice deterministico intorno. Fallo girare sulla cosa vera, guardalo lavorare. E poi, solo se serve, lo dividi.

Quando si divide? Quando te lo chiede il sistema, non l'estetica. Se l'unico agente si rompe perche' ha troppe responsabilita', dividilo. Se diventa un collo di bottiglia, dividilo. Se emergono competenze davvero specialistiche, dividilo. Sempre come risposta a un dolore vero e misurato, non a tavolino il primo giorno. Il mio organizzatore e' rimasto uno: un agente per capire, tre fasi di codice intorno, e nessun motivo concreto per spaccarlo in sei. Se domani aggiungo l'OCR per i PDF scansionati o un riconoscitore di volti per le foto, quelle saranno competenze vere: allora, e solo allora, nasceranno altri agenti.

Perche' partire dritti con una colonia di agenti che discutono tra loro e' uno dei modi piu' rapidi che conosco per costruire qualcosa di costoso, lento e difficile da debuggare, prima ancora di sapere se il problema lo chiedeva. Che poi, a pensarci bene, e' una tradizione dell'informatica molto piu' antica degli LLM.

// Script e Codice

Sezione 11. Un file Python, niente dipendenze

Tutto il codice e' su GitHub, nella cartella scripts/diecimila-file-un-agente-solo/. Gira con la sola libreria standard di Python, la chiamata al modello e' una richiesta HTTP, niente da installare.

FileCosa fa
organize.pyIl sistema: tre fasi (hash e dedup, l'unico agente che classifica con DeepSeek, spostamento), retry e quarantena, --undo dal log, --journal su git
make_sandbox.pyGenera una cartella Download finta ma realistica (doppioni esatti compresi) per provarlo senza rischi
com.signalpirate.download-organizer.plistIl demone orario: un LaunchAgent macOS che sistema solo i nuovi arrivi in radice
README.mdComandi, modalita' dry-run e apply, note sui limiti

// Cosa Mi Tengo

Sezione 12. Gli agenti si', ma ragionati

Non ti sto dicendo che gli agenti sono una moda da buttare, anzi: li costruisco, mi pagano le bollette, ne vado fiero. Ti sto dicendo che vanno trattati come una soluzione, non come il punto di partenza. Parti dal problema. Usa un loop che verifica e riprova. Spezzetta le responsabilita'. Tieni i conti fuori dal modello. Misura il costo, non solo il fatto che gira. Diffida del framework della settimana. Dai al sistema la memoria del contesto che gia' possiedi. Logga tutto. Usa git per il testo, non per i gigabyte. E parti da un agente, uno, dividendolo solo quando il dolore e' reale.

La cartella Download e' l'esempio piu' stupido che mi venisse in mente, ed e' per questo che e' utile: lo capisce chiunque, e la sproporzione tra soluzione barocca e sobria la vedi a occhio nudo. Ma lo schema e' lo stesso su problemi molto piu' seri: cambia la posta, non la lezione. E la cosa piu' onesta: mentre costruivo la versione semplice, il riflesso verso quella complicata e' tornato a bussare piu' di una volta. Anche a me. Si combatte ogni giorno.

"Voglio usare gli agenti" non e' un requisito.
E' una soluzione possibile. Parti dal problema, poi decidi.

Questo pezzo nasce da una cosa buttata giu' di getto su X, dopo l'ennesima architettura vista nascere al contrario. Poi l'ho costruito davvero, perche' gli appunti valgono di piu' quando sotto c'e' qualcosa che gira. E gira, costa pochi centesimi, tiene in ordine la mia cartella anche mentre dormo, con un agente solo. Se ti ritrovi nel disegno coi sei riquadri, nessun giudizio: ci sono passato, e ci ricasco. Ma la prossima volta, prima di disegnarlo, apri la cartella Download e chiediti quanti agenti ti servono davvero. Spesso la risposta e' uno.

Nota. Il sistema e' un singolo file Python che usa la libreria standard e una chiamata HTTP a un modello DeepSeek economico per la sola classificazione; deduplica, conteggi, policy delle cartelle, collisioni e log sono codice deterministico. I numeri vengono dalla run reale sulla mia cartella Download (364 file, 0 errori) e dai test di annullamento su 150 file copiati a parte. I blocchi di codice qui sopra sono estratti, leggermente condensati per leggibilita'; la versione integra e' nel repo. Panna sta bene e continua a passare davanti allo schermo.