Vai al contenuto

Gestione progetti con un assistente IA che rispetta i permessi

TaskTracker

Stage in ABA Technology (Nextronic) · lug–ago 2026 · realizzato da solo, nove sprint

In breve

TaskTracker è la piattaforma di gestione di progetti e attività che ho costruito per ABA Technology durante lo stage — requisiti, progettazione, codice e deployment, da solo, in nove sprint agili.

Copre il lavoro quotidiano di un team di delivery: progetti e clienti, attività su una bacheca Kanban, fogli ore, messaggistica in tempo reale, statistiche. E un assistente che risponde a domande come «quali delle mie attività sono in ritardo?» in linguaggio naturale, a partire dai dati dell'organizzazione. La parte che vale la pena leggere è come si impedisce a quell'assistente di rispondere con dati che chi chiede non dovrebbe vedere.

Il problema

Il brief era uno strumento di gestione; i vincoli sono arrivati dagli stakeholder. 19 requisiti funzionali, raccolti e formalizzati con loro prima di scrivere codice, e un modello di permessi a cinque ruoli — amministratore, project manager, team leader, collaboratore e osservatore in sola lettura — ciascuno con una porzione diversa della stessa organizzazione.

È il secondo punto che rendeva difficile la parte di IA. Un assistente basato sul retrieval cerca in tutto ciò che ha indicizzato. Se ha indicizzato l'intera organizzazione, fargli una domanda aggira ogni regola di accesso che il resto dell'applicazione applica: l'assistente diventa l'unico posto dove un osservatore può leggere ciò che ha scritto un project manager. Aggiungere un assistente era facile. Aggiungerne uno che non perde dati era il vero requisito.

Architettura

Un'applicazione Next.js 16 a livelli: le server action chiamano i servizi, i servizi contengono le regole di business, Prisma gestisce il database. Niente di ciò che arriva dal browser vale per le autorizzazioni.

  • Le sessioni sono firmate con HMAC-SHA256 e verificate con un confronto a tempo costante. Ogni azione ricontrolla ruolo e organizzazione sul server; il confine dell'organizzazione è applicato in ogni query, mai dedotto dall'URL.
  • PostgreSQL con pgvector, undici tabelle. Progetti, attività e note di progetto sono vettorializzati con nomic-embed-text e salvati come frammenti di conoscenza accanto ai dati di origine — reindicizzati a ogni creazione, modifica o cancellazione.
  • L'assistente gira su Ollama (llama3.2:3b di default), quindi i dati dell'organizzazione non finiscono mai presso un'API di terzi. Risponde soltanto dal contesto recuperato, e ogni risposta torna con le sue fonti.
  • La messaggistica in tempo reale gira su un server WebSocket autonomo che autentica le connessioni con lo stesso cookie di sessione firmato dell'applicazione; l'app gli invia gli eventi tramite un endpoint protetto da un segreto.
  • Docker Compose avvia tutti e quattro i servizi — app, database, server dei modelli, server WebSocket — e crea un'organizzazione demo riproducibile al primo avvio.

Decisioni e compromessi

Il filtro dei permessi agisce dentro la ricerca vettoriale

La forma abituale del RAG: cercare nell'indice, poi filtrare i risultati. Fallisce due volte. Perde dati — righe invisibili hanno comunque deciso quali cinque frammenti tornano. E affama — se le corrispondenze più vicine sono tutte fuori ambito, la risposta è vuota anche se esistono dati pertinenti e visibili.

Quindi l'ambito viene prima: gli id di progetti, attività e note che questo utente può leggere. È applicato nella clausola WHERE, prima di ORDER BY embedding <=> query, così i vicini più prossimi sono scelti solo tra le righe permesse. Il costo è una query di ambito per domanda; il vantaggio è che non esiste un percorso nel codice in cui il modello veda una riga che l'utente non potrebbe aprire.

Risposte fondate e con fonti, da un piccolo modello locale

Un modello da 3 miliardi di parametri inventa volentieri una scadenza. Il prompt lo limita al contesto recuperato e gli chiede di dirlo quando il contesto non contiene la risposta; la risposta riporta gli id dei frammenti usati, così ogni affermazione si può ricondurre all'attività da cui viene. In uno strumento di team, un modello piccolo che dice «non lo so» vale più di uno grande che tira a indovinare — e un modello locale tiene i dati in casa.

Una sola sessione per due server

Il server WebSocket è un processo a sé, perché un deployment serverless non può tenere connessioni aperte. La scorciatoia allettante è un secondo token, più semplice, per il socket. Invece legge lo stesso cookie firmato HMAC che l'app emette: un solo schema di autenticazione da fare bene, non due da tenere allineati. E l'unico modo per trasmettere ai client passa dall'app, dietro un segreto condiviso.

La demo online rinuncia a funzioni invece di simularle

Vercel non può far girare né un server WebSocket né un modello locale. Invece di simulare l'uno o l'altro, la versione online dice chiaramente che il tempo reale e l'assistente richiedono l'installazione Docker, e il README offre tre modi per valutarla: online, Docker completo o sviluppo locale. Una demo che finge vale meno di una che è onesta sui propri limiti.

Cosa è stato difficile

Far girare un modello su CPU. In Docker una risposta richiede da 30 secondi a due minuti, la prima più lenta mentre il modello si carica. Da qui le scelte pratiche: un timeout generoso e configurabile, un piccolo modello di default sostituibile con una variabile d'ambiente, e messaggi di errore che indicano quale delle cause probabili si è verificata — modello non scaricato, indice vuoto, macchina troppo lenta, assistente disattivato — invece di un errore generico.

Tenere onesto l'indice. Un embedding è una copia dei dati, e le copie invecchiano. Ogni scrittura che modifica un progetto, un'attività o una nota aggiorna o rimuove il suo frammento nella stessa chiamata di servizio, e uno script di reindicizzazione ricostruisce tutto da zero quando serve. Un indice scaduto non va in crash: risponde, con sicurezza, con la scadenza della settimana scorsa.

Risultato

Un'applicazione completa e containerizzata: il piano degli sprint portato a termine, cinque ruoli, messaggistica in tempo reale e un assistente funzionante, consegnati come quattro servizi che partono con un solo comando e creano gli stessi dati demo su ogni macchina. Gira online per la consultazione e in Docker per la valutazione completa.

Cosa ne ho imparato: possedere un servizio dall'inizio alla fine significa possedere anche le parti poco gloriose — i dati demo, la tabella di risoluzione dei problemi, il messaggio di timeout — e sono quelle che un valutatore incontra per prime.

I prossimi passi

Portare il limite dei tentativi di login fuori dalla memoria del processo. Conta i tentativi falliti per istanza, il che va bene su un container e diventa sbagliato dietro un load balancer; Postgres o Redis lo risolverebbero.

Poi misurare l'assistente invece di fidarsi: un piccolo insieme di domande con risposte note, rieseguito a ogni modifica del prompt o del modello, perché la qualità del retrieval sia un numero e non un'impressione.