-
Notifications
You must be signed in to change notification settings - Fork 0
Memoria
🇮🇹 Italiano · 🇬🇧 English
Generated from docs/memory-it.md — edit that file in the repository, not this page.
Read in 🇬🇧 English
Premessa Didattica: I Large Language Model sono funzioni senza stato (stateless): ogni richiesta riparte da zero se non viene fornito contesto. Ma come possiamo dotare un agente di memoria a lungo termine senza saturare la finestra di contesto e senza ricorrere a pesanti e complessi database vettoriali esterni?
Questa guida analizza l'architettura della memoria persistente di TSUKA: i concetti chiave, le scelte ingegneristiche, il funzionamento passo dopo passo e le lezioni apprese dagli errori commessi durante lo sviluppo.
Prima di analizzare formule e codice, è fondamentale distinguere i tre livelli di stato in un harness agentico:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Cronologia del Turno (RAM) │
│ • Ambito: Turno di conversazione corrente │
│ • Ciclo di vita: Effimero (azzerato al riavvio, potato durante il turno) │
│ • Scopo: Messaggi del ciclo ReAct (richieste utente, chiamate tool, log) │
├─────────────────────────────────────────────────────────────────────────────┤
│ 2. Lavagna di Esecuzione / Blackboard (AsyncLocalStorage) │
│ • Ambito: Singolo workflow multi-agente (/team o /goal) │
│ • Ciclo di vita: Singola esecuzione (distrutta al termine del goal) │
│ • Scopo: Spazio condiviso per scambiare note intermedie tra gli agenti │
├─────────────────────────────────────────────────────────────────────────────┤
│ 3. Memoria Persistente a Lungo Termine (memory/memory.json) │
│ • Ambito: Condivisa tra tutte le sessioni, i workspace e gli agenti │
│ • Ciclo di vita: Permanente (su disco, gestita da eviction a punteggio) │
│ • Scopo: Decisioni architetturali, convenzioni e lezioni apprese │
└─────────────────────────────────────────────────────────────────────────────┘
Un errore tipico quando si costruisce un harness è accumulare tutta la cronologia passata nel prompt di sistema.
- Il problema: I modelli linguistici piccoli e locali (<30B parametri) soffrono di "diluizione dell'attenzione" (attention dilution): quando migliaia di token di log passati inondano il prompt, il modello si confonde, dimentica le istruzioni recenti e sbaglia i parametri dei tool.
- La regola architetturale: I log transitori restano nella RAM di turno; le note di lavoro tra agenti restano nella Blackboard temporanea; solo la conoscenza solida e curata viene promossa nella Memoria Persistente.
La memoria nei sistemi AI non è una soluzione unica, ma una scala di compromessi (trade-offs): ogni gradino superiore offre maggiore astrazione semantica a fronte di maggiore complessità architetturale, latenza e perdita di determinismo:
Gradino 6: Grafi di Conoscenza Temporale (Zep, Mem0) ── Infrastruttura pesante, motori a grafo
Gradino 5: Memoria Auto-Curata Continua (Letta) ── LLM costantemente in loop per auto-editing
Gradino 4: Vettori & RAG Semantico ── Richiede modelli di embedding e vector DB
────────────────────────────────────────────────────────────────────────────────────────────────
Gradino 3: Ranking Lessicale + Emivita (TSUKA) ◄── ZERO dipendenze, 100% deterministico e locale
────────────────────────────────────────────────────────────────────────────────────────────────
Gradino 2: Sintesi Mobile (Rolling Summary) ── Perde dettagli puntuali, costosa in prompt
Gradino 1: Buffer Grezzo di Chat ── Esaurisce immediatamente la finestra di contesto
| Dimensione | RAG Vettoriale / Embedding (Gradino 4) | Lessicale + Emivita (TSUKA - Gradino 3) |
|---|---|---|
| Dipendenze Esterne | Richiede modello di embedding + librerie native vector DB |
Zero (TypeScript puro + node:fs) |
| Latenza & Risorse | 50–500ms per ogni embedding, RAM GPU/CPU aggiuntiva | 0ms, scoring istantaneo su CPU |
| Ispezionabilità & Debug | Vettori di numeri opachi, ranking difficile da verificare | File JSON in chiaro (memory/memory.json), grep-pabile |
| Affidabilità Locale | Può fallire se il server di embedding va in crash | Totalmente autonomo, funziona sempre offline |
| Compromesso Accettato | Riconosce parafrasi ("auto" = "automobile") | Cerca radici e prefissi esatti ("costruire", "costruzione") |
🔑 Intuizione Chiave: Nello sviluppo software e nell'ingegneria dei prompt, le ricerche riguardano quasi sempre nomi di file esatti, identificatori, codici di errore, tecnologie e convenzioni specifiche piuttosto che parafrasi poetiche. L'algoritmo BM25 combinato allo stemming morfologico copre circa il 90% delle reali esigenze con zero complessità infrastrutturale.
Non tutti i ricordi hanno lo stesso valore nel tempo. Un errore di compilazione di dieci minuti fa diventa inutile una volta risolto, ma una convenzione architetturale ("In PowerShell usa sempre UTF-8 senza BOM") deve durare per mesi.
TSUKA classifica i ricordi in 4 livelli di durabilità decrescente:
▲ ┌───────────────────────────────┐
│ │ LEZIONE (Lesson) │ Peso: 3 | Emivita: 30 giorni
│ │ "Mai disabilitare TLS" │ (Regole d'oro, convenzioni permanenti)
│ ├───────────────────────────────┤
│ │ DECISIONE (Decision) │ Peso: 2 | Emivita: 7 giorni
│ │ "Usiamo Vitest, non Jest" │ (Scelte di architettura e librerie)
│ ├───────────────────────────────┤
DURABILITÀ │ FATTO (Fact) │ Peso: 1 | Emivita: 48 ore
│ │ "Config in src/config.ts" │ (Stato del sistema, snapshot ambiente)
│ ├───────────────────────────────┤
│ │ RUN (Run Note) │ Peso: 0 | Emivita: 2 ore
│ │ "Passo 3 fallito con timeout"│ (Log transitori, i primi ad essere rimossi)
▼ └───────────────────────────────┘
-
Protezione Quota Run: Quando la memoria è piena (
maxFacts = 200), i log di tiporunpossono occupare al massimo il 30% dello spazio disponibile durante l'eviction, impedendo che un flusso intenso di lavoro cancelli le lezioni preziose.
┌──────────────────────────────┐
│ 1. Scrittura & Deduplica │ Normalizzazione testo, auto-tagging, unione hits
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ 2. Ricerca & Recupero │ Ranking BM25 + stemming morfologico
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ 3. Invecchiamento & Eviction │ Decadimento esponenziale a emivita; touch ringiovanisce
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ 4. Iniezione nel Prompt │ Task-Aware (BM25) vs Generale (Retention Score)
└──────────────────────────────┘
Quando un agente chiama save_memory:
-
Sicurezza Atomica su Disco: Scrivere direttamente su
memory.jsonrischia di corrompere il file se il processo viene interrotto a metà. TSUKA scrive su un file temporaneo (memory.json.tmp) ed esegue una rinomina atomica a livello di filesystem. Se il file viene trovato corrotto, i byte vengono salvati inmemory.json.corrupt-<timestamp>prima di ripartire puliti. -
Deduplicazione Normalizzata: Prima di salvare, il sistema genera una chiave normalizzata:
key = `${scope} ${content.trim().replace(/\s+/g, ' ').toLowerCase()}`
-
Unione Intelligente (Smart Merge): Se il ricordo esiste già:
- Aggiorna il tipo di durabilità se il nuovo è superiore (es.
fatto$\to$ decisione). - Incrementa il contatore
hitsdel ricordo esistente (un concetto registrato più volte è un concetto che ha valore). - Aggiorna i timestamp e unisce i tag.
- Aggiorna il tipo di durabilità se il nuovo è superiore (es.
- Auto-Tagging: Se l'agente non specifica tag, il motore estrae fino a 5 parole chiave significative dal testo, ignorando le stop-words.
Quando gli agenti cercano nella memoria con recall_memory(query):
Le parole vengono ricondotte alla loro radice base (es. "funzioni" "funzion", "running" "runn"). Questo permette a ricerche in italiano e inglese di intercettare singolari, plurali e coniugazioni.
Invece di un banale controllo di sottostringa, BM25 applica tre principi matematici intuitivi:
-
Rarità dei Termini (IDF): Le parole comuni contano pochissimo; i termini rari e distintivi (es.
"OAuth","PostgreSQL","deadlock") dominano il punteggio. - Saturazione di Frequenza: Ripetere una parola 10 volte non decuplica il punteggio. BM25 applica rendimenti decrescenti, neutralizzando lo "spam" di parole chiave.
- Normalizzazione della Lunghezza: Una nota sintetica di 20 parole che contiene il termine cercato ottiene un punteggio superiore rispetto a un paragrafo di 500 parole in cui la parola compare per caso.
Quando la memoria supera la capienza massima (maxFacts = 200), il sistema elimina il ricordo non bloccato con il punteggio più basso:
FORMULA DEL PUNTEGGIO DI EVICTION
Score = (Peso_Durabilità × 100) + (Decadimento_Tempo × 10) + TieBreak_Recente + Bonus_Hits
▲ ▲
│ │
Fattore dominante: Erosione esponenziale
Lezione batte sempre in base all'emivita
i log di Run (2h, 48h, 7g, 30g)
- Quando un ricordo viene restituito da
recall_memory(query), il sistema lo tocca (touch):- Incrementa
hitsdi 1. - Aggiorna il timestamp
lastUseda ora.
- Incrementa
- Effetto: I ricordi consultati spesso rimangono sempre "giovani" e immuni all'eviction. I ricordi mai richiamati decadono naturalmente e vengono rimossi.
-
Ricordi Fissati (
pinned: true): I fatti contrassegnati come fissati sono permanentemente esenti da decadimento ed eliminazione.
Come arrivano i ricordi all'interno del prompt dell'agente?
È noto l'obiettivo/task specifico dell'utente?
│
├── SÌ ──► Iniezione Contestuale al Task (formatRelevant)
│ Cerca con BM25 i ricordi rilevanti per il compito attuale.
│ (Importante: questa ricerca NON altera gli hits, evitando falsa popolarità).
│
└── NO ──► Iniezione per Rilevanza Globale (formatForPrompt)
Inietta i ricordi più importanti e duraturi (Lezioni e Decisioni).
Ogni ricordo viene formattato con badge compatti leggibili immediatamente dai modelli leggeri:
- [2026-08-15][LESSON] (security_auditor) Mai disabilitare la verifica dei certificati TLS negli script di produzione.
- [2026-08-16][DECISION] (architect) Tutti i tool personalizzati devono restituire stringhe JSON strutturate.
Gli agenti interagiscono con la memoria persistente tramite 4 tool nativi:
Salva un nuovo fatto, decisione o lezione nella base di conoscenza.
{
"content": "Windows PowerShell richiede codifica UTF-8 esplicita per gestire caratteri non-ASCII nei pipe.",
"summary": "Regola codifica UTF-8 in PowerShell",
"kind": "lesson"
}Cerca nella memoria con algoritmo BM25 e rinfresca la giovinezza del ricordo.
{
"query": "PowerShell codifica pipe"
}Aggiorna o arricchisce un ricordo già salvato.
{
"id": "mem_j8x19",
"content": "Regola aggiornata: PowerShell 7 supporta UTF-8 nativamente; Windows PowerShell 5.1 necessita di chcp 65001.",
"kind": "lesson"
}Elimina definitivamente un ricordo obsoleto o errato specificandone l'ID.
{
"id": "mem_j8x19"
}In conformità con la Direttiva 8 (Modularity by Design), TSUKA disaccoppia la memorizzazione e il recupero dei dati dietro un'interfaccia esplicita:
// src/core/memory/types.ts
export interface MemoryBackend {
load(): Promise<void>;
save(): Promise<void>;
addFact(fact: Omit<MemoryFact, 'id' | 'createdAt' | 'lastUsed' | 'hits'>): MemoryFact;
updateFact(id: string, patch: Partial<MemoryFact>): boolean;
forgetFact(id: string): boolean;
search(query: string, scope?: string, options?: SearchOptions): ScoredFact[];
formatForPrompt(maxChars?: number, scope?: string): string;
formatRelevant(taskText: string, maxChars?: number, scope?: string): string;
// ...
}-
Backend di Default (
JsonMemoryBackend): Zero dipendenze esterne, salvataggio su file JSON in TypeScript puro organizzato in moduli focalizzati:-
codec.ts: normalizzazione e derivazione automatica del summary da pattern noti (goal/tracce/subagent), sanitizzazione dei fatti, deduplicazione per chiave/titolo, formattazione badge e rendering sezioni prompt entro il budget di caratteri (promptMaxChars). -
storage.ts: persistenza atomica tramite file temporaneo.tmperenameSync, pulizia file orfani al boot e backup automatico dei file corrotti (.corrupt-<timestamp>). -
bm25.ts: scoring testuale puro, rimozione stop-word e tagging semantico. -
retention.ts: pesatura per tipologia (pinned,lezione,decisione,fatto,run), emivita temporale e selezione della vittima di eviction.
-
-
Registro Pluggabile: Backend alternativi (es. SQLite con estensione FTS5, database vettoriali o store cloud remoti) possono essere registrati tramite
registerMemoryBackend(name, factory)e selezionati tramite la chiavememoryBackendintsuka.config.jsono la variabile d'ambienteTSUKA_MEMORY_BACKEND. -
Facade Unificata: L'intero harness continua ad accedere alla memoria tramite il singleton facade
MemoryStore, garantendo il 100% di compatibilità a ritroso.
La creazione di questo sistema ha fatto emergere diverse insidie pratiche:
-
Cosa accadeva: Nelle prime versioni, ogni risposta di un tool veniva salvata in
memory.json. - La conseguenza: La memoria si riempiva rapidamente di frammenti di codice e log di errore temporanei, spazzando via le vere decisioni architetturali dopo poche ore.
- La soluzione: La memoria deve essere curata. Solo le lezioni esplicite, le convenzioni e i fatti stabili appartengono alla memoria persistente.
-
Cosa accadeva: Ogni volta che un ricordo veniva inserito nel prompt iniziale, il suo contatore
hitsaumentava e la sua data veniva aggiornata. - La conseguenza: I primi 10 ricordi inseriti nel progetto diventavano eterni perché venivano inclusi all'avvio di ogni turno, impedendo a nuove lezioni di emergere.
-
La soluzione: L'iniezione nel prompt usa
touch: false. Solo le ricerche esplicite e consapevoli degli agenti (recall_memory) contano come reale utilizzo.
-
Cosa accadeva: Gli agenti tentavano di salvare interi file sorgente con
save_memory. - La conseguenza: Saturazione immediata del limite di caratteri e peggioramento delle capacità di ragionamento dell'LLM.
- La soluzione: Il filesystem del workspace è l'unica sorgente di verità per il codice; la memoria persistente serve esclusivamente per meta-conoscenza, regole e convenzioni.
TSUKA v0.8.1 · Repository · Issues · MIT License