# Enhanced Memory V2 - Analisi e Progettazione ## Stato: FASE 2 - Analisi e Proposta completata --- ## 1. DECISIONI PRESE | Domanda | Risposta | |---------|----------| | **Privacy** | Backend PUÒ processare plaintext in-memory per estrazione. NO persistenza plaintext | | **SaaS vs In-House** | **Solo in-house**. Nessuna dipendenza Supermemory API | | **Feature target** | Graph Memory, Contradiction Resolution, Forgetting/Decay, User Profiles, LLM Episode Summarization | | **Scala utenti** | < 100 (early stage) | | **Budget LLM** | **Minimizzare**. Preferire modelli piccoli/euristiche dove possibile | | **Semantic search** | NON richiesta in questa fase (keyword fallback accettato per ora) | --- ## 2. IMPLEMENTAZIONE ATTUALE (Adiuva) ### Architettura Memoria (MemGPT-style, 4 livelli) | Livello | Tabella | Stato | |---------|---------|-------| | **Core** | `memory_core` | Funzionante - key/value preferenze | | **Associativa** | `memory_associative` | Parziale - keyword fallback, no semantic | | **Episodica** | `memory_episodic` | Funzionante - troncamento 200 char | | **Proattiva** | `memory_proactive` | Schema vuoto, nessuna logica | ### Punti di Forza - E2E encryption per-user (Fernet) - 9 tool agente per memoria - Context injection automatica pre-LLM - Episodi auto-salvati post-conversazione ### Gap vs Supermemory (ciò che manca) 1. Nessuna estrazione automatica di fatti dalle conversazioni 2. Nessuna relazione tra memorie (graph UPDATE/EXTEND/DERIVE) 3. Nessun forgetting/decay temporale 4. Nessuna risoluzione contraddizioni 5. Nessun user profile auto-generato 6. Episodi troncati a 200 char senza summarization 7. Proactive memory non implementata ### IL PROBLEMA CENTRALE: context selection cieca Il metodo `_load_associative()` fa: ```python SELECT * FROM memory_associative WHERE user_id = ? ORDER BY updated_at DESC ← ordina per DATA, non per rilevanza LIMIT 5 ``` **Il messaggio dell'utente NON viene usato per filtrare.** Ritorna i 5 fatti più recenti, anche se totalmente irrilevanti alla domanda. Lo stesso per episodic (ultimi 10 per data). --- ## 3. COME SUPERMEMORY SALVA I DATI (non usa MD!) ### Chiarimento: Supermemory NON salva file MD Supermemory accetta MD come formato di input (insieme a PDF, JSON, codice, ecc.), ma i dati vengono trasformati e salvati in **PostgreSQL + vector embeddings (Cloudflare AI)**. ### Schema di storage Supermemory (da codice sorgente) ``` Documents table (Drizzle ORM + Postgres) ├── id, customId, orgId, userId ├── content ← raw content originale ├── title, summary ← generati da LLM ├── type ← 'text' | 'web' | 'pdf' | 'md' | ecc. ├── status ← 'queued' | 'extracting' | 'chunking' | 'embedding' | 'done' ├── metadata ← JSON key/value filtrabile ├── tokenCount, wordCount, chunkCount ├── summaryEmbedding (vector) ├── containerTags[] ← namespace isolation (user_id, project_id) └── createdAt, updatedAt MemoryEntries table (fatti estratti) ├── id, memory ← il fatto estratto ("Mario preferisce risposte concise") ├── version ← numero di versione del fatto ├── context │ ├── parents[] ← [{relation: 'updates'|'extends'|'derives', memory, version}] │ └── children[] ← [{relation, memory, version}] ├── similarity ← score di ricerca ├── metadata ├── sourceDocumentId ← da quale documento è stato estratto └── updatedAt ``` ### Come Adiuva salva i dati (confronto) ``` memory_core (PostgreSQL) ├── key, value_encrypted ← Fernet AES-128 └── user_id memory_associative (PostgreSQL + pgvector) ├── content_encrypted ← cifrato ├── embedding (1536-dim) ← colonna presente ma MAI usata per search ├── entity_type, entity_id └── user_id memory_episodic (PostgreSQL) ├── summary_encrypted ← "User: [200 char]\nAssistant: [200 char]" ├── session_id └── user_id memory_proactive (PostgreSQL) ├── pattern_encrypted, confidence ← schema vuoto, nessun dato └── user_id ``` ### Differenza chiave nel salvataggio | | Supermemory | Adiuva | |---|-----------|--------| | **Cosa salva** | Fatti strutturati estratti da LLM | Testo grezzo cifrato | | **Relazioni** | Graph con UPDATE/EXTEND/DERIVE + versioning | Nessuna relazione | | **Embeddings** | Generati e usati attivamente per search | Colonna presente ma inutilizzata | | **Encryption** | Nessuna (plaintext) | Fernet per-user | | **Processing** | Pipeline: Extract → Chunk → Embed → Index | Store diretto senza processing | --- ## 4. SUPERMEMORY - Cosa Prendere Come Ispirazione > **NON integriamo Supermemory SaaS.** Ci ispiriamo al design per implementare in-house. ### Concetti da adottare 1. **Relazioni tra memorie**: UPDATE (sostituisce), EXTEND (arricchisce), DERIVE (inferisce) 2. **isLatest flag**: tracciamento della versione corrente di un fatto 3. **Automatic forgetting**: fatti temporali con `expires_at`, episodi che decadono 4. **User Profile duale**: `static` (fatti stabili) + `dynamic` (attività recente) 5. **Fact extraction post-conversazione**: LLM estrae fatti strutturati dopo ogni chat ### Concetti da NON adottare (non rilevanti) - Connectors (Google Drive, Gmail, etc.) — Adiuva è un'app desktop, non un aggregatore - Multi-modal extraction (PDF, video) — fuori scope - Hybrid RAG+Memory search — non richiesto ora --- ## 4. ANALISI COSTI/BENEFICI - OPZIONI ### OPZIONE SCARTATA: Supermemory SaaS Integration | | Dettaglio | |---|----------| | **Costo** | $0-19/mo per <100 utenti (Free/Pro) | | **Pro** | Implementazione rapida, SOTA benchmarks | | **Contro Fatali** | Viola privacy (plaintext obbligatorio), vendor lock-in, latenza API esterna, architettura Cloudflare Workers non self-hostabile facilmente | | **Verdetto** | **SCARTATA** — incompatibile con zero-trust e preferenza in-house | ### OPZIONE SCELTA: Enhancement In-House Ispirato a Supermemory **Approccio**: evoluzione incrementale dell'architettura esistente in 4 fasi. --- ## 5. PIANO DI IMPLEMENTAZIONE PROPOSTO ### FASE 1 — Memory Graph + Contradiction Resolution **Effort**: ~3-5 giorni | **Costo LLM extra**: ~$0.002/conversazione (GPT-4o-mini) **Cosa cambia nel DB:** - Nuova tabella `memory_fact` (sostituisce progressivamente `memory_associative`) ``` memory_fact: id, user_id content_encrypted -- il fatto estratto, cifrato category -- 'preference' | 'fact' | 'episode' | 'goal' | 'relationship' entity_type -- a cosa si riferisce: 'user' | 'project' | 'task' | 'person' entity_id -- opzionale, FK is_latest -- boolean, come Supermemory superseded_by_id -- FK → memory_fact (relazione UPDATE) extends_id -- FK → memory_fact (relazione EXTEND) derived_from_ids -- JSON array di FK (relazione DERIVE) confidence -- 0.0-1.0 source -- 'extracted' | 'explicit' | 'inferred' expires_at -- nullable, per fatti temporali last_accessed_at -- per decay scoring created_at, updated_at ``` **Come funziona:** 1. Post-conversazione, GPT-4o-mini riceve transcript (ultimi 2000 char) + prompt strutturato 2. LLM estrae JSON array di fatti: `[{content, category, entity_type, is_temporal, expires_at}]` 3. Per ogni fatto estratto, sistema verifica con fatti esistenti (keyword match su decrypt in-memory) 4. Se contraddizione trovata → vecchio fatto `is_latest=false`, `superseded_by_id=nuovo` 5. Se arricchimento → nuovo fatto ha `extends_id=vecchio` 6. Tutto cifrato prima di persistenza **Stima costi LLM (GPT-4o-mini @ $0.15/1M input, $0.60/1M output):** - Input: ~2500 tokens/conversazione (transcript + system prompt) - Output: ~300 tokens (JSON fatti estratti) - Costo: ~$0.0006/conversazione → **~$0.06 per 100 conversazioni/giorno** --- ### FASE 2 — Automatic Forgetting + Decay **Effort**: ~1-2 giorni | **Costo LLM extra**: $0 (puro heuristico) **Meccanismi:** 1. **TTL-based expiry**: fatti con `expires_at` vengono ignorati dopo la data 2. **Access decay**: `last_accessed_at` + scoring formula: `score = confidence * (1 / (1 + days_since_access * 0.05))` 3. **Background cleanup** (cron/periodic): soft-delete fatti con score < 0.1 da >30 giorni 4. **Episodic consolidation**: dopo N episodi per sessione, consolida in un singolo summary **Nessun costo LLM** — pura logica temporale e scoring matematico. --- ### FASE 3 — User Profile Auto-Generato **Effort**: ~2-3 giorni | **Costo LLM extra**: ~$0.001/aggiornamento **Come funziona:** 1. Nuova mini-tabella `user_profile`: ``` user_profile: user_id (PK) static_encrypted -- JSON: fatti stabili (nome, ruolo, preferenze durature) dynamic_encrypted -- JSON: attività recente (ultimi 3-5 topic, task in corso) updated_at ``` 2. Dopo ogni estrazione fatti (Fase 1), profilo viene aggiornato: - `static`: fatti con `category IN ('preference', 'fact', 'relationship')` e `confidence > 0.7` - `dynamic`: ultimi 5 fatti con `category = 'goal'` o episodi recenti 3. Profilo iniettato nel system prompt a OGNI conversazione (prima del context attuale) 4. Aggiornamento trigger: post-extraction, batch di fatti nuovi → GPT-4o-mini "aggiorna profilo" **Stima costi:** - Input: ~1000 tokens (profilo attuale + nuovi fatti) - Output: ~500 tokens (profilo aggiornato) - Costo: ~$0.0005/aggiornamento → **trascurabile** **Alternativa zero-costo LLM:** il profilo `static` è calcolato come aggregazione diretta dei fatti con `is_latest=true` + alta confidence. Il `dynamic` è gli ultimi N episodi. Nessuna LLM call, solo query SQL. Meno elegante ma $0. --- ### FASE 4 — LLM Episode Summarization **Effort**: ~1-2 giorni | **Costo LLM extra**: ~$0.001/episodio **Cosa cambia:** - Il campo `summary_encrypted` in `memory_episodic` passa da troncamento 200 char a summary LLM - GPT-4o-mini genera un riassunto strutturato: `{topic, user_intent, outcome, key_facts_mentioned}` - Async: non blocca la risposta. Avviene dopo che la risposta è già stata inviata al client **Stima costi:** - Input: ~1500 tokens (conversazione completa) - Output: ~200 tokens (summary strutturato) - Costo: ~$0.0004/episodio → **~$0.04 per 100 conversazioni/giorno** --- ## 6. RIEPILOGO COSTI TOTALI ### Costi LLM aggiuntivi stimati (< 100 utenti, ~100 conversazioni/giorno) | Fase | Costo/conv | Costo/giorno | Costo/mese | |------|------------|--------------|------------| | Fase 1 (Fact Extraction) | $0.0006 | $0.06 | ~$1.80 | | Fase 2 (Forgetting) | $0 | $0 | $0 | | Fase 3 (User Profile) | $0-0.0005 | $0-0.05 | $0-1.50 | | Fase 4 (Episode Summary) | $0.0004 | $0.04 | ~$1.20 | | **TOTALE** | **~$0.001-0.002** | **~$0.10-0.15** | **~$3-4.50** | ### Confronto con Supermemory SaaS | | In-House | Supermemory Free | Supermemory Pro | |---|---------|------------------|-----------------| | Costo/mese | ~$3-4.50 (LLM) | $0 (ma limiti) | $19/mo | | Privacy | E2E mantenuta | Plaintext obbligatorio | Plaintext obbligatorio | | Limiti | Solo LLM rate limits | 1M tokens, 10K search | 3M tokens, 100K search | | Personalizzazione | Totale | Nessuna | Nessuna | | Vendor lock-in | Zero | Alto | Alto | **Verdetto**: l'implementazione in-house costa meno di $5/mese, mantiene la privacy, e offre personalizzazione totale. --- ## 7. MATRICE BENEFICI | Feature | Impatto UX | Effort | Priorità | |---------|-----------|--------|----------| | Fact Extraction + Graph | ALTO - l'AI ricorda tutto automaticamente | Medio | P0 | | Contradiction Resolution | ALTO - niente informazioni obsolete | Basso (incluso in P0) | P0 | | Automatic Forgetting | MEDIO - meno noise nel context | Basso | P1 | | User Profile | ALTO - personalizzazione immediata | Medio | P1 | | Episode Summarization | MEDIO - recall migliore | Basso | P2 | --- ## 8. ANALISI CRITICA: COME SUPERMEMORY INIETTA IL CONTESTO ### Il flusso Supermemory (Python SDK) ```python from supermemory import Supermemory client = Supermemory() # richiede SUPERMEMORY_API_KEY # ── PRE-LLM: recupera profilo + memorie rilevanti ── profile = client.profile( container_tag="user_123", # = user_id di Adiuva q="What sneakers should I buy?" # = il messaggio dell'utente ) # profile.profile.static → ["Senior engineer at Acme", "Prefers dark mode"] # profile.profile.dynamic → ["Working on auth migration"] # profile.search_results → memorie rilevanti per la query # ── Assemblaggio system prompt ── context = f"""Static profile: {chr(10).join(profile.profile.static)} Dynamic profile: {chr(10).join(profile.profile.dynamic)} Relevant memories: {chr(10).join(r.get("memory","") for r in profile.search_results.results)}""" messages = [{"role": "system", "content": f"User context:\n{context}"}, *conversation] # → passa al tuo LLM # ── POST-LLM: salva conversazione (Supermemory estrae fatti automaticamente) ── client.add( content="\n".join(f"{m['role']}: {m['content']}" for m in conversation), container_tag="user_123", ) ``` ### Cosa succede sotto: `client.add()` → HTTPS → Supermemory cloud 1. Supermemory riceve il **plaintext completo** della conversazione 2. Il **loro LLM** estrae fatti, preferenze, entità 3. Costruisce relazioni graph (UPDATE/EXTEND/DERIVE) con fatti esistenti 4. Aggiorna il profilo utente (static + dynamic) 5. Applica forgetting su fatti temporali scaduti ### Come si confronta con il tuo `enrich_context()`: | Aspetto | Adiuva (attuale) | Supermemory | |---------|-----------------|-------------| | **Dove vive la logica** | `memory_middleware.py` nel tuo backend | Cloud di terzi | | **Come recupera contesto** | 4 query SQL → decrypt in-memory | 1 HTTPS call `client.profile()` | | **Qualità contesto** | Raw key/value + troncamenti 200 char | Fatti strutturati + profilo curato | | **Chi estrae fatti** | Nessuno (o l'utente via tool) | LLM automatico su ogni `add()` | | **Latenza retrieval** | ~5-15ms (DB locale) | ~50-200ms (HTTPS) | | **Latenza storage** | ~2ms (INSERT SQL) | ~200-500ms (HTTPS + LLM extraction) | | **Privacy** | Plaintext solo in-memory, cifrato a riposo | **Plaintext permanente su server terzi** | --- ## 9. VALUTAZIONE CRITICA ONESTA ### BENEFICI REALI dell'integrazione Supermemory 1. **Extraction automatica** — Non dover fare nulla: `client.add(conversation)` e i fatti vengono estratti. Risparmi ~3-5 giorni dev della Fase 1. 2. **Contradiction resolution SOTA** — Il loro graph engine è #1 sui benchmark. Implementarlo in-house richiede un LLM prompt ben ingegnerizzato + logica di matching. 3. **User Profiles pronti** — `client.profile()` restituisce static+dynamic in ~50ms. In-house devi costruire la logica di aggregazione. 4. **Temporal forgetting** — Gestiscono scadenza e noise filtering. In-house è semplice (TTL + cron) ma loro lo fanno meglio con LLM. ### PROBLEMI CRITICI dell'integrazione 1. **PRIVACY DISTRUTTA** — Il punto più grave. Tutto il modello E2E di Adiuva si basa su: "il backend non persiste mai plaintext". Supermemory riceve e **tiene** tutti i dati utente in chiaro. Per un'app che vende privacy, è un dealbreaker. 2. **LATENZA AGGIUNTA** — Ogni conversazione aggiunge: - +50-200ms PRE-LLM (profile fetch via HTTPS) - +200-500ms POST-LLM (add + extraction) - vs. ~5-15ms totali con DB locale - Su connessione instabile: timeout → memoria persa 3. **SINGLE POINT OF FAILURE** — Se `supermemory.ai` è down, la tua app perde TUTTA la memoria. Non ha fallback locale. Le tue 4 tabelle PostgreSQL attuali sono resilienti. 4. **VENDOR LOCK-IN** — I fatti estratti vivono nel loro cloud. Se chiudono, cambi pricing, o limiti free tier → migrazione dolorosa. Con la soluzione in-house hai ownership totale. 5. **COSTI CHE SCALANO MALE** — Free tier: 1M tokens/mese = ~250 conversazioni medie. Con 100 utenti attivi: - ~30 conv/utente/mese = 3000 conv = ~12M tokens → **Scale plan $399/mo** - In-house: $3-5/mo per le stesse 3000 conv con GPT-4o-mini 6. **ARCHITETTURA OVERHAUL** — Devi: - Rimuovere/sostituire le 4 tabelle memory - Riscrivere i 9 tool dell'agente per usare l'SDK - Rimuovere la logica di encryption - Cambiare il contratto WebSocket (se i tool memory cambiano) - **Effort paradossale**: più lavoro per integrare che per migliorare in-house 7. **NON SELF-HOSTABILE** — Il repo GitHub è MIT ma il core è Cloudflare Workers + KV + Postgres. Self-hosting richiede Cloudflare infra o riscrittura significativa. ### BILANCIO FINALE | Pro | Peso | |-----|------| | Extraction + Graph + Forgetting gratis | ★★★★ | | User profiles automatici | ★★★ | | Zero dev effort per le feature memory | ★★★ | | **Totale Pro** | **10/15** | | Contro | Peso | |--------|------| | Privacy distrutta (dealbreaker per il brand) | ★★★★★ | | Vendor lock-in su funzionalità core | ★★★★ | | Costi $399/mo a regime vs $5/mo in-house | ★★★★ | | Latenza +200-700ms per conversazione | ★★★ | | Single point of failure | ★★★ | | **Totale Contro** | **19/25** | > **Verdetto: l'integrazione Supermemory SaaS è netta-negativa per Adiuva.** > I benefici (extraction, graph, profiles) sono replicabili in-house a costo inferiore, > senza sacrificare privacy, ownership e resilienza. --- ## 10. PROSSIMI PASSI - [ ] Approvazione piano di miglioramento in-house (4 fasi) - [ ] Design schema migration per `memory_fact` e `user_profile` - [ ] Implementazione Fase 1 (fact extraction + graph) - [ ] Test extraction con conversazioni reali - [ ] Implementazione Fasi 2-4 incrementalmente