Categorie
artificial intelligence okr tecnologia

Definition of Done per LLM

TL;DR: prima del prompt, il verdetto

Dare a un LLM un comando vago come “ottimizza il SEO” o “migliora il documento” è il modo più rapido per bruciare token e ritrovarsi con un lavoro perfetto e inutile. Prima del prompt serve una Definition of Done (DoD), una condizione di “fatto” da cui ricavare un verdetto. Il punto scomodo è doppio: una DoD può essere precisa al 100% e comunque sbagliata, perché il suo valore dipende dall’obiettivo; e può essere giusta nello spirito e impossibile da applicare. Da “DoD giusta” a “DoD applicabile” usando il comando /goal di Claude.

Con l’arrivo degli agenti e della loro autonomia operativa, dare a un LLM un compito complesso è diventato molto più semplice: glielo descrivi e si mette al lavoro da solo, aprendo i file e lanciando i comandi necessari, iterando senza che io debba intervenire a ogni passaggio. Il rovescio della medaglia è che un agente lasciato a briglia sciolta parte volentieri per la tangente. Per questo Claude Code ha aggiunto /goal, un comando pensato per favorire la convergenza: gli dai una condizione di completamento e l’agente continua a lavorare turno dopo turno finché quella condizione non risulta soddisfatta, senza che tu lo solleciti ogni volta.

È uno strumento utilissimo, con un dettaglio che però cambia tutto: converge verso la condizione che gli scrivi tu e, quando la condizione è buona, l’autonomia lavora per te; quando è vaga, lavora comunque, solo che a vuoto, e te ne accorgi dal conto dei token.

Per essere precisi, l’effetto anti-tangente è una conseguenza del meccanismo, non una funzione a parte: /goal fissa un traguardo e tiene l’agente in cammino verso quello, ricontrollando a ogni turno se ci è arrivato. E qui sta il perno di tutto il resto: a fare il controllo, dopo ogni turno, è un modello piccolo e veloce che legge quello che l’agente ha fatto comparire nella conversazione, senza eseguire lui i controlli (documentazione ufficiale). Il verdetto si basa su ciò che finisce in chat.

Adesso prova a scrivere il goal come lo scrivono quasi tutti:

/goal ottimizza il SEO di questa pagina

? loop infinito, oppure vittoria dichiarata su un output che non è valutabile

Parlare di una generica ottimizzazione SEO non fa capire all’agente cosa voglia dire “ottimizzato” e, pertanto, l’agente convergerà verso qualcosa, ma non è detto che sia verso il mio obiettivo, perché non gliel’ho dato chiaramente. E questo costa più di quanto sembri: gli LLM si pagano, in denaro e in energia, e una condizione vaga raddoppia il conto.

A quel goal manca una Definition of Done (DoD), la condizione di “fatto” su cui basare un verdetto. Senza, il risultato ha un nome che ormai conosciamo: slop, cioè roba formalmente corretta e sostanzialmente inutile, di quelle che superano quasi sempre il test del “sembra a posto”. Ed è proprio per questo che conviene decidere la condizione, e decidere bene, prima di premere invio.

Stessa metrica, verdetti opposti

Prendiamo la metrica più rassicurante che esista, il 100% di test coverage (la copertura dei test, cioè la percentuale di codice attraversata almeno una volta dai test automatici). È deterministica, la calcola una macchina e non si presta a discussioni, al punto che sembra la DoD perfetta.

Lo è, ma dipende sempre da quale obiettivo le si mette dietro, e per mostrarlo provo a salire di livello tenendo ferma la metrica.

Se l’obiettivo è la copertura del codice, il 100% di coverage è applicabile e corretto: l’obiettivo e la metrica coincidono. Se l’obiettivo è la qualità del codice, la stessa metrica resta applicabile e diventa sbagliata, perché il codice testato non significa codice scritto bene. Se l’obiettivo è codice utile, resta applicabile e, di nuovo, sbagliata, e per giunta l’utilità scavalca il recinto dell’output ed entra nel dominio dell’outcome, dove una percentuale di righe coperte non arriva. La metrica non si è mossa di una virgola, eppure, salendo di obiettivo, il suo verdetto è cambiato sotto i nostri occhi.

Qui torna utile un vocabolario proveniente dal mondo del prodotto e degli OKR. La distinzione è tra output e outcome.

  • L’output è ciò che faccio: l’artefatto, i test scritti, la pagina modificata.
  • L’outcome è ciò che ottengo: il risultato che volevo, l’utente che porta a casa il lavoro per cui ha “assunto” il software.

La coverage misura l’output e tace sull’outcome.

E qui sta il buco da non lasciare aperto: applicabile non vuol dire giusto. Una DoD può essere perfettamente applicabile e perfettamente fuori bersaglio, ed è il modo più educato per produrre slop, perché chiudi il tuo “fatto” con la coscienza a posto, pur consegnando la cosa sbagliata. L’antidoto sta prima del prompt: ragionare sull’obiettivo reale prima di scrivere il comando. È il cuore di Pensare con gli LLM, the Right Way. La questione delle metriche la sviscero invece in KPI the right way; qui mi basta il principio: una metrica può essere precisa, deterministica e muta sul risultato che ti interessa.

Sei mosse per ricavare una DoD dall’obiettivo

Sapere che la DoD dipende dall’obiettivo serve a poco se poi non so come ottenerla. Queste sono le mosse che uso, e hanno tutte lo stesso vincolo: devono produrre qualcosa che l’agente possa muovere e mostrare in conversazione, perché è quello che /goal sa leggere.

Caccia all’aggettivo. Ogni obiettivo vago nasconde un aggettivo: “migliore”, “ottimizzato”, “pulito”. La mossa è sostituirlo con un numero o con un sì/no.

"migliora il documento"
  ? 0 errori ortografici
  ? lunghezza 1500-2000 parole
  ? 100% degli acronimi spiegati al primo uso

Checklist a esito binario. Trasformo l’obiettivo in una lista in cui ogni voce si chiude con un sì o un no e con un’evidenza accanto.

/goal la checklist SEO è verde al 100%: title sotto 60 caratteri, una sola H1,
tutte le immagini con alt text, meta description tra 120 e 158 caratteri

La DoD è il 100% delle voci, oppure il 90% se decido di accettare un margine. L’importante è che il margine lo dichiari prima, non dopo aver visto il risultato.

Ancora un exit code. La DoD più forte è un comando il cui codice di uscita è il verdetto.

/goal pytest restituisce 0 ed eslint --max-warnings 0 restituisce 0, senza toccare i file di test

Qui non c’è spazio per l’interpretazione, ed è il tipo di DoD che vorrei avere sempre.

Baseline più delta. Quando l’obiettivo è un miglioramento, fisso il punto di partenza e la variazione attesa. “Migliora le performance” diventa: “tempo di risposta da X a Y millisecondi, dimensione del bundle ridotta del 30%, query N+1 portate da 12 a 0.” Il “+40%” che ogni tanto scrivo nei prompt funziona solo se indico rispetto a cosa. Un delta senza baseline è un auspicio.

Invarianti, cioè cose che non devono rompersi. Metà DoD è spesso un negativo verificabile: zero test esistenti rotti, diff confinato ai file previsti, zero nuove vulnerabilità in npm audit. È la parte che dimentichiamo di chiedere, e quella che fa più danni quando manca.

Campione più soglia. A volte il 100% non è verificabile per costruzione. Un endpoint che accetta input liberi dagli utenti ha uno spazio dei casi, di fatto, sterminato: non lo collaudi “tutto“. Qui la mossa è fissare un campione e un criterio di accettazione.

/goal 300 input casuali non provocano nessun crash né eccezione non gestita,
oppure fermati dopo 30 turni

Dichiaro il limite invece di fingere una certezza che non ho. Su questa torno alla fine, perché è quella che separa una DoD giusta da una DoD applicabile.

Il caso che mi riguarda di persona: il TOV

100% allineato al TOV” (TOV sta per Tone of Voice, il tono di voce editoriale) suona proprio come la DoD da evitare. Nel mio caso regge, e il motivo è istruttivo. Il mio documento di TOV è già scritto come una checklist deterministica, e quindi diventa un goal vero:

/goal il documento rispetta il TOV: 0 trattini lunghi, 0 costruzioni "non X ma Y",
ogni acronimo spiegato al primo uso, "tensione" mai usata da sola

Questo articolo lo dimostra mentre lo leggete: “100% allineato al TOV” diventa verificabile esattamente nella misura in cui ho trasformato il gusto in regole discrete. Dove la scrittura resta al “gusto“, la DoD non si applica e tocca a me.

Il goal che non sa quando fermarsi

C’è un tipo di goal che sembra perfetto ma non può funzionare: quello che richiede una validazione asincrona, cioè un verdetto che dipende da qualcosa che arriva dopo, dall’esterno della sessione. L’esempio tipico è: “migliora la pagina finché l’NPS supera 40” (Net Promoter Score, l’indice di raccomandazione ottenuto dalle risposte di utenti reali).

L’agente quel numero non lo può muovere nei suoi turni, perché l’NPS si aggiorna sull’orologio degli utenti, in giorni o settimane, non su quello dell’assistente, in secondi o minuti. Se lo metti in un goal, l’agente entra in un’attesa indefinita: ricontrolla un valore che alla sua cadenza non cambia, e brucia token senza sapere quando fermarsi. Una condizione è applicabile solo se l’agente può soddisfarla durante la sessione. “NPS > 40” resta un ottimo obiettivo, e si verifica altrove, nel monitoraggio, fuori da un goal.

Da giusta ad applicabile

Il criterio “applicabile” si regge, in fondo, su una sola cosa: che l’agente, all’interno della sessione, possa davvero muovere e mostrare il numero che il valutatore legge. Questo funziona per l’output, l’unica cosa che esiste come artefatto a portata dei suoi turni. L’outcome accade invece più tardi e fuori dalla sessione, IRL (direbbero i gggiovini, o forse i più giovani di me), e lì lo strumento non arriva: sa verificare ciò che produce, non ciò che cambia.

C’è anche un motivo più sottile per tenersi l’outcome in mano, e lo offre lo strumento stesso. /goal aumenta l’agenticità dell’assistente, la sua capacità di andare avanti da solo, e quell’autonomia ha una sua deriva: più l’agente procede senza di me, più mi viene comodo smettere di guardare. In Pensare con gli LLM, the Right Way la chiamo “deriva da agenticità“, ed è la ragione per cui una DoD applicabile serve anche da guinzaglio, proprio dove l’agente, lasciato solo, resterebbe a girare a vuoto su un valore che non si muove.

Mettendo insieme i pezzi, conviene tenere l’outcome fuori dal goal. La ragione nuova è che può scivolarti via mentre lo misuri: quando un indicatore diventa il bersaglio, smette di indicare. Le altre due le abbiamo già incontrate e basta richiamarle: l’outcome vive fuori dalla sessione e, forzarlo nel loop, si brucia budget a vuoto, che è poi l’argomento più concreto da portare a chi guarda le fatture.

Che è poi la solita storia, e qui chiudo come sempre: sapere cosa fare (l’outcome, che è mio) e come farlo (l’output, dove vive la DoD) è già parte della soluzione. Il difficile è ricordarsi di decidere il “fatto” prima di premere invio, e di non chiedere all’agente di chiudere un numero che non può muovere.

E voi, come scegliete i criteri per chiudere un goal?