Articoli approfonditi sulla tecnologia che plasma il futuro.

Dentro una CLI agentica: loop, contesto e permessi

Cosa rende davvero utile una CLI per il coding agentico? Loop di tool-call, gestione del contesto, modelli di permessi e architettura client/server.

Un piccolo nucleo luminoso circondato da strati di ingranaggi, tubi e valvole, che illustra la differenza tra harness e modello.
In una CLI agentica per il coding, il modello è il piccolo nucleo; l'harness che lo circonda è la maggior parte della macchina.

Il modello è la parte facile. Almeno, questa è la tesi, e dopo aver visto nascere, fare fork e inciampare una dozzina di CLI agentiche per il coding negli ultimi due anni, credo sia vera: una CLI agentica per il coding riesce o fallisce in base al suo harness, non al modello. Il rilascio di OpenCode 2.0, un agente di coding open source da terminale che questa settimana ha preso parecchia trazione, è un buon pretesto per dirlo ad alta voce. Le cose interessanti di quella release non sono nuove integrazioni di modelli. Sono una separazione client/server, plugin con hot-reload, task eseguibili in background e un loop dell'agente riprogettato. In una parola, plumbing. Il plumbing è il prodotto.

Sono un ex ML engineer, quindi questa conclusione mi ha infastidito. Speravo che la qualità del modello fosse il vero elemento distintivo. Ma quando guardo perché continuo a usare o abbandono questi strumenti, la risposta non è mai «il modello è diventato più intelligente». È «lo strumento ha smesso di chiedermi di approvare ogni singolo comando», oppure «finalmente ricarica la configurazione senza riavvio», oppure «posso collegare una GUI a una sessione in esecuzione su una macchina remota». Smontiamo i tre sottosistemi che determinano davvero se un agente di coding è utile: il loop delle tool-call, la gestione del contesto e il modello dei permessi. Poi ammetterò dove la tesi controcorrente non regge.

Il loop delle tool-call è un sistema distribuito

Togliendo il marketing, ogni agente di coding è lo stesso programma: chiama il modello con un prompt e una lista di tool, riceve testo o una tool call, esegue il tool, aggiunge il risultato al contesto e ripete. Sembra un loop banale. Non lo è affatto, e chi lo tratta come tale risulta fragile dopo cinque minuti di uso reale.

Pensiamo a cosa deve gestire davvero quel loop. Le esecuzioni dei tool falliscono: il compilatore dà errore, la suite di test si blocca, il file non esiste a quel percorso perché il modello lo ha inventato. Il modello produce JSON malformato per gli argomenti dei tool. Una tool call impiega quaranta secondi e l'utente preme Esc a metà. Due tool call arrivano nello stesso turno e una delle due non deve partire se l'altra fallisce. La finestra di contesto si riempie a metà task e qualcosa deve cedere. Ognuno di questi è un problema di concorrenza e fallimento, non di intelligenza. Ho già sostenuto che gli agenti LLM sono sistemi distribuiti, e la CLI per il coding ne è l'esempio più puro: hai un componente remoto inaffidabile e non deterministico (il modello) che si coordina con componenti locali con effetti collaterali (shell, filesystem, LSP), e il loop è il protocollo di consenso tra di loro.

Ecco perché conta che OpenCode 2.0 abbia, a quanto pare, riscritto il loop attorno a Effect, un framework TypeScript per la concorrenza strutturata e gli errori tipizzati. La scelta indica che il team ha smesso di considerare il loop «un while con una fetch dentro» e ha iniziato a trattarlo per quello che è: un sistema di effetti interrompibili, componibili e ritentabili, con canali di errore espliciti. Quando il loop dell'agente ha una vera semantica di cancellazione, «l'utente preme Esc» diventa un evento di primo livello invece di uno stato corrotto. Quando i risultati dei tool sono tipizzati, puoi validare l'output del modello prima di eseguirlo, il che si abbina bene a tecniche come il constrained decoding, in cui limiti lo spazio di output del modello così che le tool call malformate diventino impossibili invece di essere solo intercettate.

Client/server è la funzione di cui nessuno fa marketing

Una delle decisioni di design più importanti per una CLI di coding è se l'agente sia un processo o un servizio. Il design ingenuo, un processo, un terminale, sessione morta quando muore il terminale, è come sono nati quasi tutti questi strumenti, ed è un tetto invalicabile. Il modello client/server di OpenCode, in cui un server di lunga durata possiede le sessioni e un numero qualsiasi di client (TUI, app desktop, interfaccia web) si collega ad esso, elimina quel tetto. In pratica significa: la sessione sopravvive alla chiusura del terminale, puoi far girare l'agente su una macchina di sviluppo remota e pilotarlo da un'app nativa sul tuo laptop, e più interfacce possono osservare la stessa sessione.

Se hai mai provato a far girare un agente su una VM via SSH e hai visto la sessione andare persa a causa di una riconnessione, sai che non è un optional. È la differenza tra un giocattolo e un'infrastruttura. Gli agenti a lunga esecuzione appartengono alla stessa categoria di build e suite di test: cose che avvii, lasci andare e controlli più tardi. I task eseguibili in background di OpenCode 2.0 vanno nella stessa direzione: l'agente smette di essere una conversazione sincrona e diventa un job runner con un'interfaccia di chat.

Ora la precisazione, perché sostengo questa opinione in modo coerente: un'architettura con server non significa un daemon permanente. Diversi commentatori hanno notato con sospetto che ogni harness per agenti ora vuole un processo in background sempre attivo sulla tua macchina. Quell'istinto è corretto. Ho già scritto che le CLI non dovrebbero installare daemon in background per default, e lo confermo. Esiste un punto di equilibrio pulito: avviare il server on demand, limitato alla directory del progetto, e chiuderlo quando si disconnette l'ultimo client. Ottieni persistenza della sessione e collegamento multi-client senza un processo sempre attivo che manda dati dalla tua macchina. Il fatto che OpenCode carichi la configurazione dalla cartella della sessione invece che dalla working directory del server, altra modifica della 2.0, suggerisce che abbiano capito che il server deve seguire il progetto, e non viceversa.

La gestione del contesto è dove vive la qualità

Chiedi a un qualsiasi professionista perché un agente «si è rimbambito» dopo un'ora di lavoro e quasi mai la risposta è una regressione del modello. È il context rot. La finestra si è riempita di output di tool obsoleti, vicoli ciechi e il testo completo di file rilevanti quaranta turni prima. Il modello ora prende decisioni su un pagliaio, e il recall dell'ago degrada molto prima di raggiungere il limite nominale di token.

Un buon harness tratta il contesto come un budget con una politica di allocazione, non come una pipe. Concretamente, questo significa diverse cose che lavorano insieme:

  • Troncamento dell'output dei tool con vie di recupero. Un log di test da 5.000 righe dovrebbe entrare nel contesto come le quaranta righe che falliscono più una nota che indica dove si trova il log completo, da cui il modello può rileggere. L'harness decide cosa è saliente; il modello decide quando ha bisogno di altro.
  • Sotto-sessioni e sotto-agenti. «Esplora come funziona l'autenticazione in questo repo e riferisci» dovrebbe girare in un contesto isolato che restituisce un digest, non l'intera trascrizione dell'esplorazione. Il miglioramento della comunicazione tra sotto-sessioni in OpenCode 2.0 è esattamente questa idea.
  • Compattazione che preserva l'intento. Quando la finestra si riempie, qualcosa deve riassumere la conversazione. Un harness che riassume in modo ingenuo perde i vincoli dell'utente («non toccare il modulo di fatturazione») insieme al rumore. Una buona compattazione mantiene decisioni e vincoli alla lettera e comprime solo la narrazione.
  • Configurazione e skill come contesto, non come codice. Istruzioni a livello di progetto, tool definiti da plugin e file di skill consumano tutti finestra. Il loro hot-reload, altro fix della 2.0, conta perché modificare il contesto dell'agente senza riavviare la sessione è il modo in cui si itera sul comportamento dell'agente stesso.

Niente di tutto questo è intelligenza del modello. Tutto questo determina quanto il modello appaia intelligente. Un modello di frontiera con una politica di contesto approssimativa perderà contro un modello più economico dietro un harness disciplinato in qualsiasi task più lungo di venti minuti.

Un imbuto di vetro che comprime flussi di documenti in un sottile raggio di luce, a simboleggiare la compressione del contesto.
La gestione del contesto è compressione con giudizio: tutto entra, esce solo ciò che è saliente.

Modelli di permessi: la funzione noiosa che decide l'adozione

Ecco una domanda dalla discussione su HN che considero la più importante questione di prodotto in questo spazio: esiste qualcosa tra «approva ogni comando» e «modalità yolo»? L'auto-approvazione completa è davvero pericolosa: un agente di coding con accesso alla shell può eseguire rm -rf, git push --force ed esfiltrare le tue variabili d'ambiente in un solo turno. L'approvazione manuale completa è davvero inutilizzabile: approvare ottanta comandi all'ora significa che non stai supervisionando l'agente, stai facendo da tastiera lenta per gli interrupt. Lo strumento che azzecca il mezzo vince tra i pragmatici.

Il mezzo è un modello di permessi graduato, e il suo spazio di design è meglio compreso di quanto si pensi:

  • Lettura, scrittura ed esecuzione. Le letture sono quasi gratuite; le scritture dovrebbero essere limitate all'albero del progetto; l'esecuzione è dove la policy conta di più.
  • Allowlist consapevoli degli argomenti. git status si approva automaticamente; git push chiede conferma. Gli strumenti ingenui confrontano il nome del binario; quelli buoni confrontano verbo e flag, perché npm test e npm publish sono lo stesso binario con raggi d'impatto completamente diversi.
  • Permessi con scope di sessione. «Consenti sempre questo comando in questa sessione» è la funzione di permessi con il maggior valore in assoluto. Trasforma settanta richieste in una sola.
  • Contenimento per l'irreversibile. L'accesso alla rete, qualsiasi cosa fuori dalla directory del progetto e qualsiasi cosa che tocchi credenziali dovrebbero essere limitati strutturalmente, non semplicemente oggetto di una richiesta di conferma.

L'ultimo punto è quello in cui la maggior parte degli harness investe troppo poco, perché chiedere conferma è facile e il contenimento è difficile. Ma un prompt è una domanda a cui il modello spera che tu risponda correttamente alle 18 di venerdì. Isolare l'albero dei processi dell'agente, con rete ristretta e mount del filesystem limitati, rende impossibili intere classi di errori, indipendentemente da ciò che fa il modello o l'utente. Ho esposto il compromesso in dettaglio in contenimento vs. monitoraggio per gli agenti AI; la versione breve è che i prompt sono monitoraggio, la sandbox è contenimento, e per ciò che è irreversibile serve il contenimento. L'ecosistema delle CLI sta imparando questa lezione lentamente. Chi la impara più in fretta si guadagnerà la fiducia necessaria perché gli utenti abilitino davvero l'auto-approvazione per il noioso 90%.

Estensibilità e il problema dei modelli self-hosted

Due lezioni minori dalla discussione sulla 2.0 meritano attenzione. La prima: «tutto è un plugin» sta vincendo. Gli utenti costruiscono skill, editor di configurazione e hook di workflow sopra questi harness, e l'harness che rende i plugin di prima classe (con hot-reload, capaci di osservare le sessioni e di modificare il comportamento) fa crescere lo sforzo della community invece di combatterlo. Il segnale che OpenCode lo prende sul serio: la loro app GitHub fa il triage delle segnalazioni di bug riproducendo il problema e commentando i risultati, usando di fatto l'agente per mantenere l'agente. Questa è l'«autoconsapevolezza» apprezzata dagli utenti, ed è un risultato dell'architettura a plugin, non del modello.

La seconda: l'agnosticismo rispetto al modello è più difficile di quanto sembri, ed è un problema di formato del contesto. Un utente ha provato un ottimo modello locale addestrato a emettere comandi in bash, mentre l'harness si ostinava ad aspettarsi tool call in stile JavaScript, e i due semplicemente non riuscivano a interoperare. È l'accoppiamento nascosto di ogni harness: il formato delle tool call, le assunzioni del system prompt e la distribuzione di training del modello devono allinearsi. Un harness che codifica in modo fisso le convenzioni di una famiglia di modelli non è agnostico rispetto al modello, al massimo è tollerante. Se i modelli locali devono contare per i coding agent, e per ragioni di costo e privacy dovrebbero, gli harness hanno bisogno di dialetti di tool call intercambiabili, non solo di endpoint API intercambiabili.

Dove la tesi controcorrente si rompe

Ho detto che il modello è la parte facile. È il momento di concedere i margini. La tesi regge per il design dell'harness (loop, contesto, permessi), ma ci sono punti in cui il modello è davvero il collo di bottiglia. L'affidabilità nell'uso dei tool è addestrata nei modelli: un modello che non riesce a emettere in modo coerente chiamate di funzione valide fa sembrare rotto ogni harness, e nessuna quantità di concorrenza strutturata basata su Effect sistema un modello che litiga con il proprio schema. La pianificazione multi-step su orizzonti lunghi continua a separare i livelli dei modelli in modi che si sentono già in una singola sessione. E la cornice harness-contro-modello ha una data di scadenza: man mano che i modelli assorbono il comportamento agentico nel training (uso nativo dei tool, pianificazione nativa a lungo orizzonte), le capacità migrano dall'harness ai pesi, e la funzione intelligente di oggi diventa lavoro sprecato domani. Chi costruisce harness dovrebbe assumere di costruire impalcature che i modelli renderanno obsolete prima o poi, ed è, onestamente, un argomento a favore degli harness open source con ecosistemi di plugin, perché questi sopravvivono grazie alla community e non grazie a un vantaggio temporaneo di capacità.

C'è anche un livello minimo di UX che nessuna architettura giustifica. Alcuni utenti hanno provato OpenCode 2.0 e l'hanno trovato comunque goffo; altri si sono lamentati di avvii lenti e di un database di cronologia chat morte sempre più grande. Non sono problemi profondi, ma sono quelli che fanno scappare le persone nei primi dieci minuti, prima che l'elegante loop dell'agente abbia la possibilità di mostrarsi. Anche la rifinitura è una funzione, ed è quella che i progetti open source finanziano più spesso in modo insufficiente.

Il modello scrive il codice, ma è l'harness a decidere se ti fidi abbastanza da lasciarglielo fare.

Quindi il mio consiglio pragmatico, da chi ha visto questa categoria evolvere: scegli il tuo agente di coding come sceglieresti un database, non come sceglieresti il vincitore di un benchmark sui modelli. Guarda la gestione dei fallimenti del loop, la politica sul contesto, la granularità dei permessi e se le sessioni sopravvivono alla chiusura del terminale. Sono le parti che conteranno ancora dopo il prossimo rilascio di modelli, quando tutti saranno di nuovo ugualmente intelligenti.