Grazie, John
Prima di tutto il resto: grazie a John Kerl, l’autore di Miller. Da anni ci regala uno degli strumenti a riga di comando più belli e utili che esistano per lavorare con CSV, TSV e JSON, e continua a curarlo con una generosità e un’attenzione rare. Quello che leggerete qui sotto è farina del suo sacco: io ho avuto solo la fortuna di lasciare un commento su un issue al momento giusto. Ma andiamo con ordine.
Introduzione
Una delle vignette più famose di xkcd è quella della bomba da disinnescare ricordandosi a memoria la sintassi di tar. Ne ho già scritto: oggi un modello di linguaggio ci suggerisce il comando giusto e la bomba non esplode (lo so, non è del tutto vero, ma è per rendere l’idea).
C’è però un rovescio della medaglia. Quando è un agente AI a guidare un programma da riga di comando — non a suggerirti una riga, ma a comporre ed eseguire comandi da solo, in autonomia — succede una cosa fastidiosa: si può inventare le opzioni. Chiede un --formato che non esiste, sbaglia il nome di un verbo, allucina un valore.
Dietro c’è un principio. Un modello di linguaggio è bravissimo a capire cosa gli chiedi, ma inaffidabile quando deve generare i dettagli meccanici: il nome esatto di un’opzione, un valore, la sintassi. Il modo giusto di usarlo, allora, non è chiedere all’AI di produrre lei il risultato, ma di pilotare lo strumento che lo calcola: è l’agente a decidere cosa fare, ed è la CLI a eseguirlo in modo esatto. È lo stesso ragionamento che ho seguito costruendo opensdmx, una CLI per le statistiche ufficiali dove l’AI compone la query e lo strumento restituisce il dato pubblicato — non uno inventato.
Ma perché questo funzioni, lo strumento deve lasciarsi guidare: deve raccontarsi in modo strutturato, invece di costringere l’agente a indovinare leggendo un testo scritto per gli umani (prosa, fmt.Printf). Una CLI progettata così vale doppio: resta comoda per noi e si lascia guidare in modo affidabile dagli agenti, che la usano inventandosi molto meno.
Miller ha appena affrontato il problema alla radice. Dalla versione 6.20.2 espone la propria “superficie” — verbi, funzioni, opzioni, errori — in modo strutturato e leggibile da una macchina (tutto è raccolto nella pagina AI features della documentazione). E include, come ciliegina, un server MCP che lo rende collegabile direttamente ad agenti come Claude Code o Cursor.
Il bello è che è tutto già dentro il binario. Vediamolo all’opera.
Gli esempi qui sotto li ho eseguiti con una build di sviluppo mlr 6.20.0-dev per Linux, mentre scrivevo il post. Nel frattempo è arrivata la prima release stabile della serie, la 6.20.2 (le 6.20.0 e 6.20.1 sono state ritirate per un errore): tutto ciò che vedi qui sotto è ora disponibile in un binario ufficiale. Nessuna configurazione, nessuna dipendenza: Miller è un singolo file eseguibile.
Il CSV di prova (vendite.csv) non è dato reale: l’ho generato con fauxdata, una piccola CLI che produce dataset finti — ma verosimili — a partire da uno schema YAML. Il template che ho usato è questo:
name: vendite
rows: 200
seed: 42
columns:
id_ordine: { type: int, unique: true, min: 1000, max: 99999 }
cliente: { type: string, preset: name }
citta: { type: string, preset: city }
prodotto: { type: string, values: [libro, penna, quaderno, zaino, matita] }
categoria: { type: string, values: [cancelleria, accessori] }
quantita: { type: int, min: 1, max: 50 }
prezzo: { type: float, min: 1.0, max: 200.0, precision: 2 }
pagato: { type: bool }
data_ordine: { type: date, min: "2024-01-01", max: "2024-12-31" }Poi è bastato fauxdata generate vendite.yml --validate per avere 200 righe pronte (e validate).
Il catalogo, in JSON
Una delle novità che permettono a un agente AI di “parlare” con Miller è la creazione di un catalogo in JSON della sua intera superficie: verbi, funzioni, keyword e opzioni (le flag). L’agente legge questo testo strutturato e sa come usare lo strumento. Prima c’era soltanto un ottimo man file: un testo eccellente, ma non strutturato, scritto per gli occhi umani — non per una macchina. Ora invece l’agente ingerisce il catalogo e costruisce un modello accurato di Miller. Un solo comando restituisce l’indice di tutto — qui, per brevità, mostro solo un paio di verbi:
mlr help --as-json --index[
{ "kind": "verb", "name": "altkv", "summary": "Given fields with values of the form a,b,c,d,e,f emits a=b,c=d,e=f pairs." },
{ "kind": "verb", "name": "cat", "summary": "Passes input records directly to output. ..." }
]E quando serve il dettaglio di un singolo verbo, le opzioni arrivano strutturate — con tanto di tipo dell’argomento, che è esattamente la parte che una persona non legge ma su cui una macchina inciampa:
mlr help verb sort --as-json{
"name": "sort",
"options": [
{ "flag": "-f", "arg": "{a,b,c}", "type": "csv-list",
"desc": "Lexical ascending sort on the specified field names." }
]
}“Descrivimi il dato”, per non inventarselo
L’altro modo in cui gli agenti allucinano è sui valori. Il nuovo verbo describe profila l’input in una passata e dice all’agente com’è fatto davvero: nomi dei campi, tipi, cardinalità, e — dettaglio prezioso — l’insieme dei valori effettivamente presenti.
Sul vendite.csv generato poco fa:
mlr --icsv --ojson describe vendite.csv{
"field_name": "prodotto",
"types": { "string": 200 },
"count": 200,
"null_count": 0,
"distinct_count": 5,
"min": "libro",
"max": "zaino",
"values": ["libro", "quaderno", "penna", "matita", "zaino"]
}L’agente ora sa che prodotto può valere solo uno di quei cinque: non se lo deve immaginare.
“Quale verbo mi serve?”
Miller ha da sempre una ricerca nell’help. La novità agent-friendly è esporla come router: gli dici in linguaggio naturale cosa vuoi fare, ti risponde con i verbi giusti, ordinati per punteggio.
mlr which "join two files on a key"[
{ "kind": "verb", "name": "join", "score": 25,
"summary": "Joins records from specified left file name with records ..." }
]Validare prima di eseguire
Prima di spendere un’intera passata sull’input, l’agente può chiedere a Miller se un’espressione del suo linguaggio (il DSL di put/filter) è valida:
mlr put --explain '$z = $x + $y'
# mlr put: DSL expression is valid.Errori strutturati, con “forse intendevi”
E quando qualcosa va storto, l’errore non è più una frase da interpretare, ma un oggetto su cui ramificare. Sbaglio di proposito il verbo, scrivo sortt:
mlr --errors-json --icsv sortt -f prezzo vendite.csv{
"error": "mlr: verb \"sortt\" not found. ...",
"kind": "unknown-verb",
"token": "sortt",
"did_you_mean": ["sort"]
}Quel did_you_mean: ["sort"] chiude il cerchio dell’auto-correzione: da vicolo cieco a recupero in un passo.
Una parola sulla sicurezza
Far eseguire comandi a un agente richiede prudenza: un agente che compone pipeline da solo potrebbe, per errore o per un input malevolo, lanciare comandi di sistema non voluti. Miller lo ha previsto: con --no-shell disabilita l’esecuzione di comandi esterni dal DSL, così una pipeline costruita da un agente resta nella sua sabbiera.
mlr --no-shell -n put 'end{print system("hostname")}'
# (error) ← system() bloccatoIl pezzo forte: il server MCP
Tutto quanto sopra è impacchettato in un server MCP (Model Context Protocol), lo standard con cui agenti come Claude Code e Cursor si collegano a strumenti esterni. Registrarlo è una riga:
claude mcp add miller -- mlr mcpDa quel momento l’agente ha cinque strumenti pronti — list_capabilities, which, validate_dsl, describe_data, run — e soprattutto un playbook che gli insegna il ciclo: scopri → vincola → valida → esegui. Non solo la superficie dello strumento, ma il metodo per usarlo bene. E i valori di default del server sono sensati: gira con la shell disabilitata e gli errori in JSON.
L’ho registrato sul mio Miller locale e si è collegato al primo colpo:
miller: mlr mcp - ✔ Connected
E così puoi “chattare” con Miller da un qualsiasi client MCP — Claude, OpenCode, ChatGPT e così via — e chiedergli in linguaggio naturale di esplorare, filtrare e trasformare i tuoi dati.
Il mio (minuscolo) contributo
E qui la nota citata all’inizio. L’architettura di tutto questo è di John: l’aveva già disegnata nell’issue #2098 — il catalogo JSON come fondamento, gli errori strutturati, il describe, perfino il server MCP. Io ho lasciato un commento “dall’altra parte del banco”, raccontando qualche lezione imparata costruendo opensdmx, una CLI pensata proprio per essere guidata da un agente.
Un po’ di quelle idee sono finite nel piano (PR #2103, che è di sola documentazione): emettere l’insieme dei valori validi e non solo la forma dell’argomento; l’indice name + summary in una sola chiamata; il did_you_mean risolto sul catalogo; gli hint scritti come comandi copia-incollabili e non come frasi. Nulla di incredibile e non noto a John, e diverse erano rifiniture di cose che Miller già aveva. Ma John le ha accolte con la sua solita gentilezza: un buon promemoria del fatto che, nell’open source, a volte basta esserci al momento giusto e condividere ciò che si è imparato.
Note conclusive
Mi piace questa direzione perché è la stessa che inseguo con i dati aperti e con i tool che stiamo costruendo in questi mesi in onData: allargare la platea di chi può fare le cose. Uno strumento che si lascia guidare bene da un agente è uno strumento che diventa accessibile a chi l’agente lo usa — cioè, sempre di più, a molte persone. E che lo fa senza rinunciare al rigore: le allucinazioni non spariscono del tutto — qualcuna resterà sempre possibile — ma diventano molto, molto più rare: molti meno valori inventati, molte meno opzioni allucinate, ed errori che spiegano come rimediare.
E allora: se non hai mai usato Miller, è arrivato il momento di provarlo. Se lo usi già, aggiornalo e collegalo al tuo agente preferito.
Miller era già un gioiello. Ora si integra alla perfezione anche nel mondo degli agenti.
Grazie ancora, John. 🙏