17 KiB
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 (AdiuvAI)
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)
- Nessuna estrazione automatica di fatti dalle conversazioni
- Nessuna relazione tra memorie (graph UPDATE/EXTEND/DERIVE)
- Nessun forgetting/decay temporale
- Nessuna risoluzione contraddizioni
- Nessun user profile auto-generato
- Episodi troncati a 200 char senza summarization
- Proactive memory non implementata
IL PROBLEMA CENTRALE: context selection cieca
Il metodo _load_associative() fa:
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 AdiuvAI 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 | AdiuvAI | |
|---|---|---|
| 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
- Relazioni tra memorie: UPDATE (sostituisce), EXTEND (arricchisce), DERIVE (inferisce)
- isLatest flag: tracciamento della versione corrente di un fatto
- Automatic forgetting: fatti temporali con
expires_at, episodi che decadono - User Profile duale:
static(fatti stabili) +dynamic(attività recente) - Fact extraction post-conversazione: LLM estrae fatti strutturati dopo ogni chat
Concetti da NON adottare (non rilevanti)
- Connectors (Google Drive, Gmail, etc.) — AdiuvAI è 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 progressivamentememory_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:
- Post-conversazione, GPT-4o-mini riceve transcript (ultimi 2000 char) + prompt strutturato
- LLM estrae JSON array di fatti:
[{content, category, entity_type, is_temporal, expires_at}] - Per ogni fatto estratto, sistema verifica con fatti esistenti (keyword match su decrypt in-memory)
- Se contraddizione trovata → vecchio fatto
is_latest=false,superseded_by_id=nuovo - Se arricchimento → nuovo fatto ha
extends_id=vecchio - 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:
- TTL-based expiry: fatti con
expires_atvengono ignorati dopo la data - Access decay:
last_accessed_at+ scoring formula:score = confidence * (1 / (1 + days_since_access * 0.05)) - Background cleanup (cron/periodic): soft-delete fatti con score < 0.1 da >30 giorni
- 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:
- 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 - Dopo ogni estrazione fatti (Fase 1), profilo viene aggiornato:
static: fatti concategory IN ('preference', 'fact', 'relationship')econfidence > 0.7dynamic: ultimi 5 fatti concategory = 'goal'o episodi recenti
- Profilo iniettato nel system prompt a OGNI conversazione (prima del context attuale)
- 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_encryptedinmemory_episodicpassa 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)
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 AdiuvAI
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
- Supermemory riceve il plaintext completo della conversazione
- Il loro LLM estrae fatti, preferenze, entità
- Costruisce relazioni graph (UPDATE/EXTEND/DERIVE) con fatti esistenti
- Aggiorna il profilo utente (static + dynamic)
- Applica forgetting su fatti temporali scaduti
Come si confronta con il tuo enrich_context():
| Aspetto | AdiuvAI (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
-
Extraction automatica — Non dover fare nulla:
client.add(conversation)e i fatti vengono estratti. Risparmi ~3-5 giorni dev della Fase 1. -
Contradiction resolution SOTA — Il loro graph engine è #1 sui benchmark. Implementarlo in-house richiede un LLM prompt ben ingegnerizzato + logica di matching.
-
User Profiles pronti —
client.profile()restituisce static+dynamic in ~50ms. In-house devi costruire la logica di aggregazione. -
Temporal forgetting — Gestiscono scadenza e noise filtering. In-house è semplice (TTL + cron) ma loro lo fanno meglio con LLM.
PROBLEMI CRITICI dell'integrazione
-
PRIVACY DISTRUTTA — Il punto più grave. Tutto il modello E2E di AdiuvAI 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.
-
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
-
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. -
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.
-
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
-
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
-
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 AdiuvAI. 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_facteuser_profile - Implementazione Fase 1 (fact extraction + graph)
- Test extraction con conversazioni reali
- Implementazione Fasi 2-4 incrementalmente