Il sistema ha quattro livelli di memoria, indipendenti fra loro. Tenerli separati è ciò che permette di far crescere ciascuno senza rompere gli altri.
| Livello | Dove vive | Durata | A cosa serve |
|---|---|---|---|
| Stato del grafo (checkpoint) | tabelle di LangGraph | per thread | riprendere l'esecuzione da dove era rimasta |
| Storico messaggi | liagraph.messages | per thread | mostrare la conversazione, analizzarla, esportarla |
| Riassunto | liagraph.thread_summaries | per thread | tenere corto il contesto nelle chat lunghe |
| Memoria a lungo termine | liagraph.memories | per utente | ricordare fatti oltre la singola conversazione |
Il collante è il thread_id: è lo stesso identificativo per il checkpointer di
LangGraph e per le nostre tabelle.
1. Stato del grafo (breve termine)
Modulo: app/memory/checkpointer.py.
Ad ogni passo LangGraph serializza l'intero stato e lo associa al thread. Serve a:
- continuità: il messaggio successivo riprende con tutto il contesto;
- time travel: si può rileggere e ripartire da un checkpoint precedente;
- human-in-the-loop: una run può essere sospesa e ripresa dopo
un'approvazione (
interrupt).
Con DATABASE_URL configurata si usa AsyncPostgresSaver, che crea da sé le
proprie tabelle nello schema public al primo avvio. Senza database si usa
InMemorySaver: stessa interfaccia, nessuna persistenza.
Con Supabase usa la connection string del pooler in modalità session (porta 5432). In modalità transaction i prepared statement non sopravvivono fra le transazioni; per questo il pool viene aperto con
prepare_threshold=None.
2. Storico dei messaggi
Modulo: app/memory/threads.py.
I checkpoint sono ottimizzati per l'esecuzione, non per la lettura. Per la UI e per le analisi serve una forma interrogabile: chi ha detto cosa, quale agente ha risposto, in quale run. Da qui nascono l'elenco delle conversazioni e la colonna "Richiesta" nella tabella delle esecuzioni.
3. Riassunto progressivo
Modulo: app/memory/summarizer.py.
Gli ultimi MEMORY_WINDOW messaggi (default 20) restano testuali. Tutto ciò che
li precede viene condensato in un riassunto aggiornato di volta in volta con il
modello economico (FAST_MODEL), e reso disponibile agli agenti come parte del
contesto.
L'aggiornamento avviene dopo aver risposto all'utente, in un task in background: comprimere non deve aggiungere latenza percepita.
Disattivabile con MEMORY_SUMMARY_ENABLED=false.
4. Memoria a lungo termine
Modulo: app/memory/long_term.py.
Fatti che devono valere anche fra un mese: preferenze, ruolo, vincoli, progetti. Sono gli agenti a decidere cosa memorizzare, tramite due tool:
remember_fact(content, importance)— salva un fatto;recall_facts(query)— cerca fra i fatti salvati.
user_id e thread_id non sono parametri del tool: vengono letti dalla
RunnableConfig della run. Il modello non può quindi scrivere o leggere nella
memoria di un altro utente, nemmeno se glielo si chiede esplicitamente.
Oltre ai tool, ad ogni turno le memorie pertinenti al messaggio vengono recuperate e iniettate nel contesto: l'agente le ha davanti anche senza chiedere.
Kind usati dal sistema:
| Kind | Chi lo scrive | A cosa serve |
|---|---|---|
fact | remember_fact | fatti generici |
rule | Relationship, onboarding | regole relazionali durature |
kpi | Relationship | KPI emozionali |
platform | Relationship, onboarding | come presentarsi su un canale |
nudge_outcome | workflow sollecito | cosa ha funzionato / no |
client_setup | workflow onboarding | configurazione comunicazione del creditore |
team_policy | workflow onboarding | vincoli operativi del team agentico |
Ricerca: perché non ci sono embedding
Il recupero usa la similarità trigram di Postgres (pg_trgm). Motivi:
- zero infrastruttura aggiuntiva e zero costi di embedding;
- risultati ispezionabili con una query SQL;
- su poche migliaia di fatti brevi funziona bene.
Quando servirà la ricerca semantica, il punto da cambiare è uno solo: la query
dentro recall().
-- 1. estensione e colonna
create extension if not exists vector;
alter table liagraph.memories add column embedding vector(1024);
create index on liagraph.memories using hnsw (embedding vector_cosine_ops);
# 2. in recall(): sostituire similarity(content, $2) con la distanza coseno
# order by embedding <=> $2::vector
# calcolando l'embedding con Amazon Titan Embeddings via Bedrock.
Il resto del sistema non cambia: remember, recall e i tool mantengono la
stessa firma.
Il contesto che arriva agli agenti
services.chat.build_context compone il testo iniettato nei prompt:
Riassunto della conversazione finora:
<riassunto progressivo>
Cosa sai già dell'utente (memoria a lungo termine):
- lavora su un framework multi-agent chiamato LiaGraph
- preferisce risposte concise in italiano
Ogni agente lo riceve nella parte finale del proprio system prompt
(agents/builder.py), dopo l'identità comune e le istruzioni specifiche.
Regolazioni
| Variabile | Default | Effetto |
|---|---|---|
MEMORY_WINDOW | 20 | messaggi tenuti testuali prima della compressione |
MEMORY_SUMMARY_ENABLED | true | attiva il riassunto progressivo |
LONG_TERM_MEMORY_ENABLED | true | attiva memorizzazione e recupero dei fatti |
FAST_MODEL | claude-haiku-3-5 | modello usato per i riassunti |
Ispezionare e correggere la memoria
Un sistema che ricorda senza che si possa vedere cosa ricorda è ingestibile.
Gli endpoint /api/memories permettono di elencare, cercare, aggiungere a mano
e cancellare i fatti memorizzati.
curl "http://localhost:8000/api/memories?user_id=anonymous"
curl "http://localhost:8000/api/memories/search?q=preferenze&user_id=anonymous"
curl -X DELETE "http://localhost:8000/api/memories/<id>"