KINDLE HOME

EmiBrain — Architettura, Errori e Lezioni

Deploy del protocollo Brain AI per Emisfera: stack completo, domino di undici errori durante l'onboarding, soluzione canonicalizzata.

Cos'è EmiBrain

EmiBrain è il deploy del protocollo Brain AI per Emisfera cooperativa. Non è un chatbot con qualche documento allegato — è qualcosa di strutturalmente diverso. Ogni utente ha il proprio brain: una cartella di file markdown versionata con git, montata come volume in un container Docker dedicato, con dentro un'istanza di Claude Code che gira in tmux.

In altre parole: ogni persona in Emisfera ha un terminale AI privato, con memoria persistente, isolato dagli altri, accessibile dal browser tramite una sessione WebSocket cifrata. La chat non è l'interfaccia principale — il terminale lo è. E il brain è la memoria che sopravvive a tutto: a sessioni chiuse, a modelli sostituiti, a crash notturni.

L'idea di fondo è che il valore non sia nell'AI ma nei dati strutturati che l'AI aiuta a costruire e mantenere. Il modello è sostituibile. Il brain no.

L'architettura completa

La pipeline che porta una richiesta dal browser al terminale AI passa attraverso sei strati distinti, ognuno con le sue responsabilità e i suoi punti di rottura.

Il browser apre una connessione HTTPS verso nginx, che opera sull'IP statico 172.20.0.2 e gestisce SSL termination e proxy WebSocket. Da lì la richiesta arriva a Laravel/PHP su 172.20.0.4, che si occupa di autenticazione tramite magic link, gestione della dashboard utenti, generazione di JWT e API REST. Una volta autenticato, il traffico WebSocket viene inoltrato al Node.js server su 172.20.0.3:3100, che è il cuore del bridge tra browser e container.

Il Node.js admin valida il JWT, identifica il container dell'utente, fa docker exec nel container giusto, e aggancia la sessione tmux già in esecuzione con Claude Code. Da quel momento in poi il browser vede un terminale interattivo — ma dietro c'è una catena di sei componenti che devono essere tutti attivi e allineati.

Ogni workspace utente è un container Docker isolato con sshd, tmux e Claude Code. Il brain dell'utente è montato come volume host in /home/brain/workspace all'interno del container, e corrisponde fisicamente a /var/emibrain/brains/{slug}/ sul server. Quando il container si riavvia, la memoria rimane — i file sono sull'host, non nel container.

Le reti Docker sono due: emibrain-internal su 172.20.0.0/24 connette tutti i container, mentre emibrain-ssh è riservata all'admin per la gestione SSH. Gli IP sono assegnati staticamente: 172.20.0.2 per nginx, 172.20.0.3 per admin, 172.20.0.4 per PHP, e da 172.20.0.10 a 172.20.0.16 per i workspace degli utenti.

Il disastro del 15 aprile

Undici problemi in cascata durante una call di onboarding con i clienti. Questo è il riassunto.

Il problema radice era strutturale: due docker-compose separati che gestivano lo stesso stack. Quando hai due fonti di verità, prima o poi divergono. E la divergenza emerge sempre nel momento peggiore.

Il primo domino è stato nginx che cachava il vecchio IP dell'admin dopo un restart. IP non più valido, proxy verso il vuoto, 502 su tutta la linea. Fin qui gestibile. Ma nel tentativo di risolvere, qualcuno ha aggiunto resolver e set $admin alla configurazione nginx per forzare la risoluzione DNS dinamica. Il side effect non ovvio: con quel pattern la query string veniva troncata, i JWT non arrivavano completi, autenticazione 401 su tutto.

Nel tentativo di unificare i compose e canonicalizzare la situazione, l'admin Node.js è stato buildato con il Dockerfile sbagliato — quello Python invece di quello Node.js. Risultato: container admin avviato correttamente, servizio completamente morto dentro, nessun errore esplicito al boot.

Tre fix successivi, tre nuovi problemi. La call è andata avanti comunque, in modalità demo parziale. Non ideale.

La lezione non è tecnica — è di processo. Quando hai due compose separati che gestiscono lo stesso stack, non sai mai quale sia la fonte di verità. Quando aggiungi un fix a un componente senza capire gli effetti sugli altri cinque, il domino è garantito. La complessità nascosta in sistemi multi-layer emerge sempre in produzione, mai in test.

La soluzione canonicalizzata

Un solo compose in /var/emibrain/docker/docker-compose.yml con tutto dentro: nginx, PHP, admin Node.js, workspace utenti. Nessuna ambiguità su dove cercare la definizione di un container.

IP statici su ogni container eliminano il problema del DNS caching di nginx. Non serve più il resolver con variabili — gli IP sono hardcoded nel compose e non cambiano mai. Nginx punta a 172.20.0.3 e ci troverà sempre l'admin, indipendentemente da quante volte il container sia stato riavviato.

I contesti di build sono ora espliciti e corretti per ogni servizio. L'admin usa context: /home/web/emibrain.it/repo/docker/ dove ci sono Node.js e il webssh-server.js. I workspace usano context: /var/emibrain/docker/. nginx e PHP usano il repo dell'applicazione. Nessuna ambiguità, nessun Dockerfile sbagliato che può essere caricato per errore.

Problemi ancora aperti

Lo scroll del terminale web è parzialmente fixato — la wheel event viene intercettata correttamente, la scrollbar è visibile, ma il comportamento non è ancora perfetto in tutti i browser. Non bloccante, ma fastidioso per gli utenti che lavorano con output lunghi.

La pipeline ha sei giunture. Nessuna è monitorata automaticamente. Se una cade di notte, nessuno lo sa finché un utente non apre il browser e trova il terminale morto. Il TODO #217 traccia la costruzione di un watchdog che verifichi periodicamente ogni strato — dal DNS al container al tmux — e notifichi su Discord. È il prossimo task non rinviabile.

Il reload automatico di nginx non è più necessario ora che gli IP sono statici, ma rimane il problema più ampio: non c'è un health check sulla pipeline end-to-end. Un ping su nginx non dice nulla sulla salute dell'admin o dei workspace. Serve un test sintetico che simuli un login completo.

Gli utenti attivi

Al momento del deploy, sette utenti configurati con container dedicati e brain inizializzati. Giobi su 172.20.0.10 come root e consulente esterno. Puddu su .11 con ruolo admin. Ricci su .12 come PM di progetto. Franzini (Paola) su .13 nel ruolo di CFO. Un workspace su .14 riservato allo stagista ancora da assegnare. Bodini su .15 come utente standard. Valentina su .16, aggiunta di recente come nuovo ingresso.

Ogni utente ha il suo container isolato, il suo brain privato, la sua sessione tmux indipendente. Nessuno vede il brain degli altri. Nessuno può interferire con la sessione degli altri. L'isolamento è strutturale, non configurativo.

Cosa rimane da fare

Il watchdog della pipeline è la priorità più alta. Sei strati senza monitoraggio è un debito tecnico che andrà pagato, probabilmente durante un'altra call con i clienti se non si fa prima.

Lo scroll del terminale va risolto completamente — è un elemento di esperienza utente che si nota subito, specialmente per chi usa il terminale per la prima volta.

La documentazione del compose unificato va scritta nel brain di EmiBrain con la nuova architettura, i comandi di manutenzione, le procedure di aggiunta di nuovi utenti. Oggi quella conoscenza è distribuita tra la chat, i file di config e la testa di chi ha fatto il deploy. Va centralizzata.

Il prossimo utente da aggiungere — lo stagista su .14 — sarà il primo test del processo canonicalizzato. Se funziona senza incidenti, l'architettura è stabile. Se emergono problemi, meglio scoprirli in autonomia che durante la prossima onboarding.

- FINE -
1