420 lines
17 KiB
Markdown
420 lines
17 KiB
Markdown
# 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
|