1. Panoramica
Ho progettato LIA perché giri a casa tua, su hardware modesto. La piattaforma di riferimento è un Raspberry Pi 5 (ARM64), e tutto ciò che segue ne deriva: una sola macchina, un database PostgreSQL e un Redis locali, nessun database ospitato altrove. Le credenziali e le chiavi dei fornitori sono archiviate cifrate.
Tre principi guidano il resto. Prima di tutto la trasparenza: l'osservabilità fa parte dell'architettura fin dall'inizio, e ogni token consumato è tracciato con il suo costo. Poi la difesa in profondità: quote, conferme e protezioni contro le allucinazioni sono livelli indipendenti. Infine l'indipendenza dai fornitori: ogni ruolo del sistema può essere affidato a un modello diverso, il che mi protegge tanto da un aumento di prezzo quanto da un guasto.
Lo stack è volutamente classico:
| Livello | Scelta |
|---|---|
| Backend | Python 3.14, FastAPI, asincrono da cima a fondo |
| Orchestrazione degli agenti | LangGraph 1.x |
| Dati | PostgreSQL con pgvector: dati, checkpoint del grafo e ricerca vettoriale |
| Cache e coordinamento | Redis: cache, sessioni, limiti di frequenza, elezione di un leader |
| Frontend | Next.js 16 (App Router), React 19, TypeScript strict |
| Modelli | OpenAI, Anthropic, Google Gemini, DeepSeek, Perplexity, Qwen, e qualsiasi modello locale tramite Ollama |
| Log | JSON strutturato, dati personali filtrati |
Un modello locale gira senza costi di API, interamente a casa tua. L'API gira su più worker; un'elezione del leader in Redis riserva le attività pianificate a uno solo di essi. Ogni sottosistema opzionale si attiva con un flag, e la configurazione segue un'unica cascata: ciò che l'amministratore imposta nell'applicazione prevale sull'ambiente, che prevale sulle costanti del codice. Ho voluto un'unica cascata perché una costante che diverge in silenzio dalla configurazione reale produce i bug più difficili da vedere.
Un installer guidato fa qualche domanda, crea l'account amministratore senza scrivere segreti in chiaro, verifica che l'installazione funzioni davvero e sa riconfigurarla più tardi senza chiedere di nuovo le chiavi (installare LIA).
In ordini di grandezza: più di 350 ADR, centinaia di metriche Prometheus, decine di migliaia di test automatici, un'interfaccia in sei lingue (francese, inglese, tedesco, spagnolo, italiano, cinese).
Per approfondire: architettura generale · self-hosting · fornitori di modelli · capacità amministrabili
2. Un backend suddiviso per domini
Il backend segue il Domain-Driven Design. Ogni dominio di business (agenti, connettori, voce, memoria, promemoria, conversazioni…) è un contesto isolato in src/domains/, suddiviso allo stesso modo: router, servizio, repository, modello, schema. Accanto, un nucleo trasversale (core) porta la configurazione e le regole comuni, e un livello di infrastruttura condiviso raggruppa ciò di cui tutti hanno bisogno: la factory dei modelli, la cache, il client MCP, il browser, i limiti di frequenza, lo scheduler delle attività, l'osservabilità.
Ho scelto un'architettura esagonale per i servizi esterni. Gli strumenti conoscono solo protocolli Python (le porte), e i client Google, Microsoft, Apple o Philips Hue li implementano (gli adattatori). Aggiungere un fornitore non tocca quindi il codice che lo chiama.
Il repository è un monorepo: apps/api per il backend, apps/web per l'interfaccia, infrastructure/ per Docker e il monitoraggio, docs/ per la documentazione. Un amministratore che cambia un modello nell'interfaccia lo vede applicato subito, senza ridistribuzione, perché la configurazione viva è nel database.
Per approfondire: creare un connettore · creare un agente · creare uno strumento · aggiungere un canale · aggiungere un fornitore di modelli · aggiungere un'attività proattiva
3. Il percorso di un messaggio
Ho scelto LangGraph per orchestrare gli agenti perché offriva tre cose di cui avevo bisogno: uno stato persistito in checkpoint su PostgreSQL, i cicli, e interrupt(), che permette di sospendere un turno per chiedere una conferma e poi riprenderlo esattamente da dove si era fermato. Senza questo meccanismo, non c'è una conferma umana seria.
Ogni messaggio passa prima da un router, che lo classifica come semplice conversazione o come richiesta che chiama un'azione. Una conversazione va dritta alla risposta. Un'azione segue la modalità scelta nell'intestazione della chat.
In modalità Pipeline, quella predefinita, il pianificatore scrive prima di qualsiasi esecuzione un ExecutionPlan: un piano in JSON i cui passaggi sono collegati dalle loro dipendenze. Un secondo modello, distinto dal pianificatore, convalida questo piano, per evitare che un modello giudichi il proprio lavoro. L'orchestratore esegue poi i passaggi per ondate parallele. La modalità ReAct, al contrario, lascia che il modello chiami uno strumento, legga il risultato e decida il seguito, passo dopo passo.
Le ho tenute entrambe perché rispondono a due bisogni. La Pipeline pianifica, parallelizza, si controlla e prevede il suo costo: una chiamata al pianificatore e una alla risposta, contro una chiamata per iterazione per ReAct, ossia da 4 a 8 volte meno token. ReAct si adatta a ogni risultato, il che è meglio per una ricerca esplorativa o una richiesta ambigua. Le due modalità condividono il registro degli strumenti, le conferme, il nodo di risposta e l'osservabilità.
La scelta dei domini e degli strumenti passa da un router semantico a embeddings anziché da parole chiave o dal solo modello: costa meno e sbaglia meno. Ogni strumento dichiara un manifesto (parametri, output, costo, permessi, parole chiave in più lingue) che leggono il pianificatore e il router, e ogni famiglia di strumenti si dichiara una sola volta in un registro centrale. Gli Smart Services riducono ancora il consumo mettendo in cache l'analisi delle richieste e imparando i piani ricorrenti. Dopo un turno che richiede un'azione, un nodo di iniziativa guarda, in sola lettura, cosa implica il risultato altrove: una pioggia prevista rimanda alle attività all'aperto della tua agenda. Ogni altro turno lo salta e non costa nulla in più.
Alcune regole mantengono onesto l'insieme. Una richiesta senza intenzione di agire non può produrre un piano che modifichi alcunché, ed è una regola deterministica, verificata prima di qualsiasi chiamata al modello. Un piano di modifica che esaurisce i suoi tentativi di correzione non viene eseguito: LIA fa una domanda piuttosto che scrivere un dato sbagliato. Un rifiuto o un fallimento non viene mai presentato come un'azione compiuta, e una volta eseguito il piano, nulla viene rieseguito alle tue spalle: la risposta dice ogni fallimento, quale strumento, perché, e il totale esatto. Per una modalità che si corregge strada facendo, c'è ReAct.
La risposta arriva token per token in SSE. La generazione è disaccoppiata dalla connessione: un produttore distaccato pubblica in un flusso Redis proprio del turno, e la pagina ne è solo un abbonato. Chiudere la scheda quindi non ferma nulla, e la risposta ti aspetta.
Per approfondire: le due modalità · la scelta di LangGraph · il grafo nel dettaglio · il router · il piano e la sua esecuzione parallela · il pianificatore e le operazioni in blocco · il router semantico · i manifesti degli strumenti · un fallimento della pipeline viene detto, mai rieseguito · la restituzione dei fallimenti degli strumenti · i turni che sopravvivono alla scheda · la modalità ReAct
4. Agire con prudenza
Per MCP, scegli le conferme a livello di account, server e strumento. La sintesi del comportamento spiega la regola effettiva prima di modificarla. Un agente dedicato chiede prima del compito se uno dei suoi strumenti richiede approvazione; la risposta copre le chiamate di quel compito. «Non chiedere mai» viene sospeso se cambia la dichiarazione. La chat può rafforzare le protezioni; solo Impostazioni può ridurle. Una routine può usare i server che hai espressamente preapprovato nel suo modulo.
LIA chiede conferma prima di ciò che sta per accadere davvero: ogni bozza (un'email, un evento, una cancellazione), ogni operazione in blocco che modifica almeno un elemento, ogni strumento che dichiara di dovere una conferma. La conferma si chiede azione per azione, là dove l'azione si prepara, e non sull'intero piano. Al contrario, un'azione che nulla può deviare non chiede nulla: un'email che mandi a te stesso parte direttamente.
Ciò che confermi è esattamente l'ultimo contenuto mostrato, e ogni modifica viene eseguita una sola volta. Più bozze di uno stesso turno vengono riviste una per una. Puoi rispondere in linguaggio libero: un modello classifica la tua risposta come consenso, rifiuto o richiesta di modifica. Fuori dalla conversazione, un ticket chiede la sua conferma sul ticket stesso, e una routine che gira senza nessuno davanti allo schermo non fa domande: rifiuta l'azione e lo dice, altrimenti il tuo messaggio successivo passerebbe per una risposta. Un comando dato alla chat («mostra le mie risposte come schede») segue la stessa regola, e quando richiede di collegare un account, inserire una chiave o scegliere un file, quel passaggio avviene nel browser autenticato. LIA verifica il risultato richiesto su ciò che può osservare, ma una delega a uno strumento non prova da sola tutti i suoi effetti interni. Allo stesso modo, un lock impedisce una doppia esecuzione senza poter garantire che un fornitore esterno agisca una sola volta.
Anche la risposta è vincolata: il modello scrive solo a partire dai risultati reali degli strumenti. E tutto ciò che LIA fa è iscritto in tre registri distinti: le azioni, le consultazioni e i turni. I loro totali non si sommano, perché un turno può consultare cinque fonti senza cambiare nulla. Un'azione viene iscritta prima di avvenire e chiusa solo su un risultato esplicito. Cancellare una conversazione rimuove i file che vi hai inviato, non quelli che LIA ha prodotto per te.
Per approfondire: le conferme · la catena di autorità e i registri · i registri lato utente · i ticket · i comandi applicativi · ciò che una delega prova · esempi di strumenti, email a sé stessi e bozze · la sintesi della risposta
5. Memoria e contesto
Quando indichi un risultato tramite mittente, titolo o posizione, LIA usa prima gli elementi che ha effettivamente mostrato. Se ne corrispondono diversi, chiede quale. Anche una ricerca pianificata che agirebbe sul primo di più risultati attende la tua scelta, poi prosegue con quell’elemento.
Lo stato di una conversazione è un MessagesState di cui LangGraph salva un checkpoint in PostgreSQL dopo ogni nodo. È ciò che permette di riprendere un turno dopo una conferma, anche ore dopo. I risultati degli strumenti non vivono nella cronologia dei messaggi ma in un registro di dati a parte, così sopravvivono quando la cronologia viene troncata. Oltre una soglia, gli scambi più vecchi vengono riassunti; un indicatore mostra il riempimento del contesto, e /resume avvia questo riassunto su richiesta. In modalità ReAct, ciò che un ciclo ha già letto viene salvato in un checkpoint e riutilizzato nel turno; un arresto prima di quel checkpoint può far rileggere una fonte.
La memoria a lungo termine, gli spazi di conoscenza e il contesto degli strumenti vivono nello stesso database, con pgvector. Dopo ogni scambio, un'attività in background estrae i ricordi dall'ultimo messaggio; i ricordi vicini alla domanda tornano nel contesto successivo, e la memoria è anche uno strumento che il pianificatore e ReAct consultano. Un'istruzione duratura («rispondi più breve») è fissata e visibile. Quando si tocca un argomento sensibile, una direttiva vieta di scherzare o di minimizzare.
Attorno a questa memoria, ho aggiunto diversi livelli. I diari sono i taccuini di LIA, in prima persona; ne emerge un ritratto della persona, con le fonti che l'hanno alimentato, che si corregge solo tramite leve indirette. Il Psyche Engine dà a LIA uno stato psicologico che evolve a ogni scambio e traspare nel suo stile senza essere annunciato; impara anche, ambito per ambito, dove lei è affidabile, a partire da prove; è un progetto pilota, per ora senza alcun guadagno rivendicato. Ha un limite dichiarato: non riporta quasi mai un'emozione calma e poco intensa, e questa parte della sua attivazione resta bloccata. Gli interessi vengono rilevati nelle tue richieste e alimentati da più fonti. Le abitudini, infine, vengono apprese senza un modello addestrato, con test statistici, e avevo tre ragioni, ciascuna sufficiente: un Raspberry Pi non ha un budget di addestramento, una formula si può rendere nota alla persona che riguarda, e su volumi così piccoli un modello imparerebbe soprattutto il rumore.
Per approfondire: i checkpoint · il registro dei dati · la compattazione · le letture con checkpoint di ReAct · la memoria in PostgreSQL · la memoria a lungo termine · la memoria come strumento di ricerca · le regole durature · i diari e il ritratto · il Psyche Engine · il suo limite misurato · la psiche lato utente · l'efficacia appresa dalle prove · gli interessi · le abitudini · la ricerca nei diari e nei registri
6. I modelli
Un'unica factory istanzia il modello di qualsiasi fornitore supportato e gli applica il suo adattatore. Ogni ruolo ha il suo slot di modello (la conversazione, la pianificazione, l'immagine, la trascrizione, la sintesi vocale…), impostato nell'amministrazione senza ridistribuzione. Il catalogo dei modelli vive nel database e dichiara, modello per modello, le sue capacità, i parametri che accetta e la scala di ragionamento che conosce; la schermata di configurazione offre così solo ciò che l'API del modello accetta davvero.
Alcuni compiti non sono generazioni ma decisioni delimitate: scegliere un formato, qualificare un'email. Possono essere affidati a un motore decisionale nativo, ciascuno dietro il proprio interruttore e un interruttore generale. Una decisione incerta ricade sull'elaborazione abituale, e né i diritti né le conferme gli vengono delegati. Un parere di qualificazione non autentica alcun mittente, e un guadagno misurato su un corpus di test non vale come promessa per ogni richiesta.
Ogni chiamata a un modello, nella pipeline come nelle attività in background, è tracciata e valorizzata in base alle tariffe nel database, poi raggruppata per turno. Per il costo, il risparmio principale viene dalla cache dei fornitori. Ogni prompt mette il suo contenuto stabile prima di un marcatore e il contenuto proprio della richiesta dopo, e l'infrastruttura traduce questo marcatore nel meccanismo di cache di ciascun fornitore. Poiché ogni slot può cambiare modello, non regolo questa cache per un modello in particolare: punto alla media migliore, e un modello senza cache non paga nulla di più. Gli studi condotti su altri motori locali (oMLX, llama.cpp, LM Studio) sono qualificazioni, non integrazioni consegnate: contano solo i fornitori che l'istanza propone.
Per approfondire: la selezione dei modelli · il catalogo nel database · la schermata di configurazione · le decisioni native · la loro integrazione · la qualificazione delle email · un layout per tutte le cache · il filtraggio del catalogo degli strumenti · i motori locali studiati
7. Connettori e strumenti
Ogni categoria di connettori (email, agenda, contatti, attività, file, casa) è un protocollo Python. Un solo fornitore è attivo alla volta per categoria, e il consenso OAuth si dà una volta per account Google o Microsoft. I servizi che non chiedono nulla alla persona (Wikipedia, la lettura di pagine, i luoghi, il meteo) appartengono all'istanza e sono attivi per tutti. Sul fronte email, ogni messaggio si riassume una volta e poi si riutilizza, un allegato si legge come testo o tramite un modello di visione, e un invio non viene mai ritentato dopo una conferma di ricezione persa.
Gli strumenti poggiano su cinque livelli componibili: una base comune di connettore (OAuth, cache, iniezione), un decoratore, dei formattatori per dominio, il manifesto letto dal pianificatore e un caricatore di catalogo. Gli strumenti MCP vengono convertiti in normali strumenti LangChain ed entrano nello stesso catalogo, conservato da un messaggio all'altro perché di solito un messaggio non aspetti nessun server; un server MCP deve essere in HTTPS, le sue credenziali sono cifrate, e LIA crede a un'annotazione che annuncia una modifica, mai a una che pretende di essere in sola lettura. I plugin in formato Agent Plugins si installano in un blocco unico, senza server locale. Le skills dello standard agentskills.io possono mostrare una piccola applicazione nella chat o eseguire i propri comandi in una copia della loro cartella; una skill venuta da fuori gira isolata e non riceve mai il token di un connettore.
Quando un passaggio richiede un vero calcolo, la modalità ReAct può scrivere un breve script Python ed eseguirlo in un container usa e getta. Questo script raggiunge il web solo attraverso un proxy unico, non vede la chiave della persona (il proxy la sostituisce solo sull'host autorizzato), e per un host sconosciuto viene posta una domanda con tre risposte possibili. Nelle due modalità, i calcoli, le durate e le conversioni di valuta passano da strumenti esatti anziché dal modello. Un agente sa anche pilotare un browser (Playwright) o convertire una pagina in testo.
I documenti degli spazi di conoscenza vengono suddivisi, vettorizzati e archiviati in PostgreSQL anziché in un database vettoriale esterno, poi ritrovati con una ricerca ibrida, semantica e lessicale. Uno spazio può seguire una cartella Google Drive o OneDrive con le sue sottocartelle, o un'etichetta Gmail, senza indicizzare tutta la casella. Non c'è riconoscimento dei caratteri: un PDF scansionato non viene indicizzato così com'è. I dati inviati dall'esterno (SMS, numero di passi, frequenza cardiaca, da un iPhone, un Android o qualsiasi sistema capace di inviare JSON) entrano da endpoint autenticati con un token a portata limitata e revocabile, distinto dalla sessione.
Per un compito complesso, LIA delega a sub-agenti effimeri, in sola lettura. Produce anche file: documenti, immagini, screenshot, archiviati in una galleria, conservati, inviati per email o, per un'immagine, condivisi con una persona collegata all'istanza.
Per approfondire: Strumenti · le pagine interattive · i pattern dei connettori · il consenso OAuth raggruppato · i servizi dell'istanza · i connettori lato utente · le email e i loro riassunti · gli allegati · i timeout per slot · la base degli strumenti · MCP · lo standard Agent Plugins · i plugin · le skills · le skills lato utente · le skills di terze parti · modificare una skill · condividere una capacità · il Python effimero · l'uscita di rete della sandbox · le risposte calcolate · il browser · la lettura di pagine · gli spazi di conoscenza · la loro guida · il loro uso · una cartella Drive è un albero · un'etichetta Gmail come fonte · la FAQ integrata · allegare un documento di uno spazio · i sub-agenti · i dati sanitari · la loro acquisizione · la salute lato utente · gli SMS · la generazione di documenti · i documenti lato utente · le immagini · i file generati · conservare un file · inviare per email · gli allegati del compositore · le risposte conservate
8. Voce, modalità Live e telefono
La voce classica è fatta di componenti indipendenti. La parola di attivazione «Dis LIA» viene rilevata nel browser e non invia nulla prima di essere sentita; per ora viene fornito solo il modello francese, in beta. La dettatura viene trascritta nella lingua dell'account, sul server e offline con Whisper, oppure da un motore remoto secondo l'impostazione. La sintesi vocale ha più fornitori, Edge gratuito come predefinito, e la risposta inizia a essere letta frase per frase mentre il seguito viene scritto. Un avatar parlante sperimentale può aggiungersi, sulla chiave Simli della persona, che fattura anche i silenzi finché la connessione resta aperta.
La modalità Live è una vera conversazione da voce a voce. Ci ho messo due intelligenze e una sola cucitura: il modello vocale tiene la conversazione, e ogni richiesta di dati o di azione passa al motore della chat attraverso una sola funzione, send_to_lia. La richiesta diventa un normale turno di chat, con le sue conferme, i suoi registri e le sue quote. La voce non possiede né strumenti, né credenziali, né una memoria propria.
Una sessione Live si addormenta invece di morire: durante lo standby, la connessione con il fornitore è chiusa e nulla viene fatturato. La spesa del fornitore Live è pagata dalla chiave della persona e non viene mai registrata da LIA. In Live diretto, la voce legge da sé i tuoi dati senza modificare nulla.
Il telefono riprende le stesse due modalità. LIA può chiamare un terzo per te dopo conferma, o chiamare te su un numero verificato, e allora al telefono hai i tuoi dati e tutte le sue funzioni, come nella chat. Quando LIA chiama un terzo, l'agente di chiamata ha solo uno strumento libero/occupato in sola lettura: la protezione della tua agenda è strutturale, non dipende da un'istruzione in un prompt. Al telefono, la modalità Live esiste solo se il fornitore può raggiungere la tua istanza.
Una riunione si registra dal browser: l'audio parte in piccoli segmenti, poi viene trascritto e riassunto in un verbale strutturato, archiviato in uno spazio di conoscenza. La radio personale, infine, produce solo durante una sessione di ascolto e non conserva nulla per un riascolto.
Per approfondire: la voce lato utente · lo standby Live e la parola di attivazione · le voci e la sintesi · l'avatar parlante · la modalità Live · la telefonia lato utente · il telefono come canale · la radio · le riunioni · il verbale strutturato · i modelli di verbale
9. L'iniziativa
LIA prende iniziative in tre forme. Un heartbeat periodico guarda le tue fonti e ti scrive solo se un segnale lo giustifica, nominando uno o due fatti verificati. Essere collegati a una fonte e accettare di esserne interrotti sono due decisioni separate. Una notifica push di Google può risvegliare questo heartbeat per una sola persona, e un momento (la fine di una riunione, l'arrivo di una mail attesa) viene servito al minuto anziché al passaggio successivo. Le discussioni proattive, infine, chiedono una risposta, con una propria politica: orari, massimo al giorno, argomenti sensibili tenuti privati, spesa riservata prima che avvenga.
Le routine si attivano su un calendario o su una condizione, non su entrambi, e una vista della settimana mostra ciò che è realmente successo. Un promemoria può ripetersi. La lavagna dei ticket dà a un compito un ciclo di vita, un titolare e un risultato.
Due regole delimitano tutto ciò. L'iniziativa si limita a leggere: una scrittura viene proposta, mai eseguita da sola. E un nuovo tentativo di una stessa esecuzione viene rifiutato non appena il registro degli effetti mostra l'azione già fatta. La pagina Attività mostra in un unico posto tutto ciò che LIA ha intrapreso da sola, con totali esatti.
Per approfondire: il heartbeat · il permesso di interrompere · il risveglio tramite push · i momenti e le vigilanze · le discussioni proattive · la loro consegna · il loro contesto · il loro budget · le routine · la settimana delle routine · i promemoria · un promemoria che ha sempre un testo · l'iniziativa in sola lettura · la lavagna dei ticket · la pagina Attività · valutare una notifica
10. Sicurezza e privacy
La sicurezza è pensata a livelli indipendenti, e ogni tipo di attacco ha la propria protezione. Gli account si collegano tramite OAuth 2.1 e i diritti seguono dei ruoli. Per le sessioni, ho scelto il pattern BFF: un cookie HTTP-only e una sessione in Redis anziché un token JWT, che sarebbe esposto allo XSS se vivesse nel browser, pesante da trasportare e impossibile da revocare. Un account può proteggersi con passkey e un secondo fattore, ogni dispositivo collegato si può revocare, e collegare un'identità Google non cambia mai lo stato di un account. I limiti di utilizzo si applicano a tutte le porte d'ingresso, routine comprese.
Tutto ciò che viene eseguito per la persona è trattato come ostile: il codice delle skills gira in un container usa e getta, una pagina scritta dal modello gira in un riquadro senza origine né host proprio, e un compito su un server remoto viene preparato e poi confermato. Allo stesso modo, ciò che LIA legge resta un dato e non diventa un'istruzione. La provenienza è una proprietà del dato: ogni tipo di contenuto viene classificato una volta, un tipo sconosciuto è trattato come esterno, e un tipo che nessuno ha classificato impedisce all'applicazione di avviarsi.
Al di sopra del livello debug, una riga di log porta fatti (numeri, identificativi, codici, l'host di un indirizzo), non ciò che hai scritto né ciò che un modello ha scritto su di te. Due riserve: il messaggio d'errore di un servizio esterno in un formato sconosciuto può ancora citarne un frammento, e il livello debug, disattivato in produzione, ne conserva di più. Il database viene salvato automaticamente: con le impostazioni predefinite si può perdere al massimo una giornata di dati. La copia fuori sede e gli allegati restano esclusi da questi backup.
Ci sono due limiti da conoscere. Queste misure non dimostrano da sole la conformità al GDPR di un deployment. E cancellare le misurazioni grezze, come i tuoi dati sanitari, non cancella i ricordi che ne derivano, che hanno i propri controlli.
Per approfondire: la sicurezza · l'autenticazione BFF · le passkey · i dispositivi · i limiti di utilizzo · la sandbox · la provenienza dei contenuti · la privacy lato utente · log senza dati personali · una riga di log porta fatti · i backup · la provenienza di un ricordo
11. Osservare e misurare
Il monitoraggio è integrato nel deployment: Prometheus e Grafana per le metriche, Loki per i log, Tempo per le tracce, Langfuse per le chiamate ai modelli, Alertmanager per gli alert. Il codice definisce centinaia di metriche, una trentina di dashboard sono fornite pronte all'uso, e l'emissione delle metriche è difensiva: una metrica che fallisce non fa cadere una richiesta.
Ho messo anche il debug nell'interfaccia, perché un agente è difficile da debuggare: il pannello di debug mostra per ogni scambio l'intenzione, il piano, ogni strumento e ogni chiamata al modello, e si vede perché una richiesta è costata cara o perché è stato scelto un certo dominio senza aprire Grafana.
Sul fronte dei costi, ogni euro che la piattaforma paga per una persona è tracciato, mostrato, attribuito e conteggiato per lei, qualunque sia il modo in cui è stato speso. Accanto alle quote per account esiste un tetto di spesa dell'istanza, perché più account moltiplicati per la loro quota restano una spesa senza limite. Un operatore spegne una capacità senza ridistribuire: ciò che il deployment autorizza e ciò che l'operatore sceglie si combinano, e prevale il più piccolo dei due limiti.
LIA legge anche la propria telemetria per autodiagnosticarsi: ogni alert vitale viene inviato per email con la sua procedura e diventa un incidente, e le sue raccomandazioni restano proposte. Un amministratore può persino pilotare Claude Code CLI dalla chat per esaminare il suo server.
Una precisazione sulle prestazioni: le latenze misurate sono quelle dell'infrastruttura. Il tempo di risposta che percepisci dipende soprattutto dalla sequenza di chiamate ai modelli, da qualche secondo a diverse decine secondo la richiesta e l'hardware. E la scalabilità oltre una sola macchina (più nodi, repliche, Redis in cluster) resta un'evoluzione possibile, non una realtà. Restano aperte due questioni: la crescita della memoria di un worker su più giorni e la conservazione dei punti di ripresa di LangGraph.
Per approfondire: lo stack di osservabilità · il pannello di debug · la sua architettura · il tetto dell'istanza · ogni spesa risponde a due tetti · l'amministrazione tramite foglio di calcolo · gli annunci mirati · l'istanza dimostrativa · l'autodiagnosi · gli alert e la loro resilienza · Claude CLI dalla chat · l'anatomia di un worker · le estrazioni complete dei registri · l'organizzazione delle attività in background
12. Le interfacce
Il pulsante al centro dell’intestazione della chat apre Principale e le conversazioni con un titolo. Assegna un titolo quando crei una conversazione. Dall’elenco puoi aprirla, rinominarla o eliminarla. Principale riceve i messaggi automatici e offre Reimposta. L’indicatore della sessione mostra token, costo e contesto della conversazione aperta. Preferenze, memoria e diario valgono per l’intero account.
Lo spazio Strumenti ha endpoint REST propri, esterni al grafo della conversazione. Riutilizza i client dei connettori e ne pubblica comandi e limiti nell’interfaccia. Le sessioni del database si chiudono prima delle chiamate al fornitore; gli invii sono protetti dalle ripetizioni e una risposta persa viene dichiarata come esito sconosciuto. L’HTML della posta resta in un riquadro isolato che blocca i contenuti remoti. Un gesto manuale rimane così distinto da un’azione delegata a LIA; ciò che un clic le chiede da queste schermate passa da una porta a parte, una chiamata e una proposta, mai una scrittura presso il fornitore.
L'interfaccia web è un'applicazione Next.js che si installa come un'app (PWA). La chat aggiorna la conversazione senza ricaricare, visualizza le formule matematiche e il codice colorato, conserva una bozza per account, ed è ricercabile; le impostazioni hanno una propria ricerca, un tema nero assoluto per gli schermi OLED che può seguire i tuoi orari, tre stili di icone e una dimensione del testo regolabile. Un solo strato di selezione serve ogni schermata: un testo selezionato si spiega, si traduce o passa al modulo abituale, mai a un secondo, e una sostituzione conserva l'annullamento del browser.
Nella visualizzazione a schede, una delle tre modalità a scelta insieme all'HTML semplice e al Markdown, una risposta che porta dati non viene formattata dal modello: il server la rende in schede HTML deterministiche, indipendenti dal modello e dalla modalità, archiviate con il messaggio. Una scheda mostra per intero un fatto ricevuto e non inventa nulla, e un'azione da una scheda prepara una richiesta invece di agire. Per mantenere coerente l'interfaccia, ho scritto regole che vengono verificate da test: uno stato prende il suo tono da un'unica tabella, un'azione ha un livello di importanza, e una riga di elenco espone le sue azioni in un solo modo. I test di browser e di accessibilità puntano al livello WCAG AA, senza certificare la resa su un dispositivo fisico né la fedeltà delle traduzioni.
Le app mobili sono shell native pubblicate una sola volta per store. Caricano l'indirizzo del server inserito al primo avvio e non duplicano l'interfaccia, perché una sola app deve servire tutti quelli che ospitano LIA. Su Android, le notifiche passano dal progetto Firebase del tuo server; su iOS, dove solo l'editore di un'app può inviare notifiche, un relay minimale sveglia il telefono. Condividere un testo o un link verso LIA dal telefono prepara una bozza di chat, che non parte da sola.
Attorno alla chat, un compagno dagli occhi espressivi reagisce a tutto il ciclo di una risposta, e ogni risposta porta un piccolo avatar d'umore. Il mondo di LIA è un'altra interfaccia: una casa e un giardino condivisi in cui si passeggia, senza che nessuna stanza conceda diritti aggiuntivi. La voce tra persone e la federazione tra istanze non vi sono implementate. Il video di presentazione, invece, mostra repliche dell'interfaccia e dati dimostrativi.
Due persone di una stessa istanza possono collegare i loro assistenti perché si trasmettano dei messaggi, e la pagina Relazioni riunisce ciò che riguarda ciascuna persona, con un debriefing redatto dal modello.
Per approfondire: la chat lato utente · le impostazioni · la ricerca nelle impostazioni · i primi passi · la chat che segue il server · le schede · la loro resa · i luoghi e i percorsi · la posizione nota · le primitive e il loro contratto · l'app Android · le notifiche native · le app lato utente · gli occhi espressivi · i compagni nativi · il video di presentazione · la mappa delle capacità · il mondo · il suo prototipo · le connessioni tra assistenti · le relazioni · il debriefing di una relazione · Telegram
13. Come è mantenuto questo codice
Ogni modifica passa dei controlli locali (prima del commit, prima del push) e poi l'integrazione continua su GitHub Actions, che chiama esattamente gli stessi comandi di uno sviluppatore. Le dipendenze Python sono manifesti di intenti compilati in file di lock universali che portano l'impronta di ogni pacchetto: uno stesso commit installa sempre le stesse versioni, verificate, su ogni architettura.
Le regole del progetto non poggiano sulla memoria di chi rivede: le mantengono delle guardie automatiche. Diverse di esse ricalcolano la protezione dalla fonte di verità (il codice stesso, le route realmente montate, il registro dei connettori) invece di fissare un elenco scritto a mano, e sono state volutamente messe in fallo per verificare che falliscano davvero. Altrove, degli elenchi di riferimento possono solo restringersi, perché nessun debito cresca in silenzio. I prompt sono file di testo versionati, uno per prompt, letti da un solo caricatore. Più di 350 ADR descrivono le decisioni, le alternative scartate e i compromessi accettati, e decine di migliaia di test, l'integrazione continua e la tipizzazione rigorosa permettono di far evolvere il sistema senza regressioni.
Un audit tecnico completo, a 360 gradi, è pubblicato nel repository con il suo protocollo, perché chiunque possa ripeterlo. Il modo in cui questo codice è stato scritto, quasi interamente da un'IA diretta e controllata, è raccontato nel resoconto di esperienza.
Per approfondire: l'integrazione continua · il lock delle dipendenze · i prompt versionati · l'audit pubblico
14. Per approfondire
Questa guida si basa sul codice, su più di 800 documenti tecnici e sugli ADR. Per approfondire:
- l'indice della documentazione e l'indice degli ADR;
- l'architettura degli agenti e il grafo LangGraph;
- le mappe vive, che mostrano le funzioni, la tecnica e la storia delle decisioni;
- la FAQ per l'uso quotidiano, e il resoconto di esperienza per il metodo.
Il codice è sotto AGPL-3.0, su GitHub.