# Memory Systems Research — memoripy vs OpenMemory vs Hexis vs mem0

## neo-cortex — Stato Attuale (v4.1.1)

### Cosa FA
- **Ingest**: noise filter (static + Groq) → embed (Jina 1024d) → classify (Groq: project/topic/activity/title/summary/facts/concepts/files) → store ChromaDB + SQLite FTS5
- **Recall basic**: embed query → vector search ChromaDB
- **Recall smart**: Groq analizza query → estrae filtri → refina query → vector search + filtri → fallback FTS5 se Jina giù → aggrega in MBEL
- **Dream**: energia-based clustering → boost cluster +0.1, decay resto -0.03
- **Timeline/Stats**: metadati ordinati per timestamp

### Buco Principale
Il classifier estrae `concepts`, `facts`, `files_touched` e li salva in SQLite ma **nessuno li usa**. Zero relazioni tra memorie. Zero grafo.

---

## 1. memoripy (Python, FAISS + NetworkX)

**Architettura**: FAISS vector search + NetworkX graph per concetti

### Come funziona
- **Ingest**: estrai concetti → crea nodi NetworkX → archi bidirezionali tra concetti co-occorrenti (peso = conteggio co-occorrenze)
- **Recall**: cosine similarity × time decay × access reinforcement + **spreading activation** dal grafo
- **Short-term → Long-term**: dopo 10 accessi, memoria promossa a long-term
- **Clustering**: KMeans su embeddings → semantic memory clusters → retrieval anche dal cluster più vicino

### Spreading Activation (algoritmo chiave)
```python
def spreading_activation(self, query_concepts):
    activated_nodes = {c: 1.0 for c in query_concepts}
    for step in range(2):  # 2 hop nel grafo
        new = {}
        for node in activated_nodes:
            for neighbor in graph.neighbors(node):
                weight = graph[node][neighbor]['weight']
                new[neighbor] = activated_nodes[node] * 0.5 * weight
        activated_nodes.update(new)
    return activated_nodes
```
Cerca "encoding" → attiva anche "BOM", "UTF8", ".NET stdout" tramite il grafo.

### Scoring finale
```
score = (cosine_similarity × decay_factor × log(access_count)) + spreading_activation_bonus
```

### Cosa prendere
- **Grafo concetti NetworkX** — collegare memorie via concetti già estratti
- **Spreading activation** — espandere recall con concetti associati
- **Access count + decay** — più sofisticato del nostro energy flat

---

## 2. OpenMemory HSG (Python, SQLite) — DEEP DIVE

**Architettura**: Hierarchical Segmented Graph con SQLite + multi-sector vector embeddings

### Ingest (`add_hsg_memory`)
1. **SimHash dedup**: calcola simhash (64-bit) → cerca in DB → se Hamming distance ≤ 3, boost salience +0.15, NON creare duplicato
2. **Classify content**: regex pattern matching per settori (semantic, emotional, procedural, episodic, reflective)
3. **Extract essence**: seleziona frasi più rilevanti (max N chars) come contenuto condensato
4. **Multi-sector embedding**: embed il contenuto per ogni settore assegnato (primary + additional)
5. **Mean vector**: calcola vettore medio cross-settore per ricerca rapida
6. **Salience init**: `0.4 + 0.1 × len(additional_sectors)` — più settori = più importante
7. **Waypoints**: crea connessioni con memorie simili (single + cross-sector + inter-memory + contextual)
8. **Segment rotation**: le memorie sono raggruppate in segmenti (come pagine), rotazione automatica

### Query (`hsg_query`) — Il Più Sofisticato
```python
# 1. Classifica query per settore
# 2. Embed query per OGNI settore con pesi adattivi:
#    semantic=1.2, emotional=1.5, procedural=1.3, temporal=1.4, reflective=1.1
# 3. Vector search per ogni settore (k*3 candidati)
# 4. Se confidence bassa → expand via waypoints (BFS pesato, decay 0.8 per hop)
# 5. Per ogni candidato, calcola hybrid score:
#    score = sigmoid(sim*w_sim + tok_overlap*w_ov + waypoint*w_wp + recency*w_rec + tag*w_tag)
# 6. Cross-sector resonance: matrice 5x5 di interdipendenza tra settori
# 7. Retrieval reinforcement: salience += η * (1 - salience)
# 8. Propagazione associativa: boost salience dei nodi collegati via waypoints
```

### Waypoints (Spreading Activation Style)
```python
# expand_via_waypoints — BFS pesato sul grafo waypoints
# Ogni hop: weight *= parent_weight * edge_weight * 0.8
# Stop quando weight < 0.1
# Risultato: lista di memorie "vicine" con pesi decrescenti
```

### Decay Model
```python
# decayed = init_salience * exp(-lambda * days)
# lambda diverso per settore (emotional decade più lento di procedural)
# reinforcement = alpha * (1 - exp(-lambda * days))
# salience_finale = decayed + reinforcement
```

### Cosa prendere
- **SimHash deduplication** — evitiamo memorie ripetitive (zero costo API)
- **Waypoints come spreading activation** — connessioni pesate tra memorie, BFS per espansione
- **Retrieval reinforcement** — ogni recall rafforza la memoria + propaga ai vicini
- **Decay per settore** — diversi tassi di decadimento per tipo di contenuto
- **Hybrid scoring** — combinazione pesata di similarity, overlap, recency, waypoints

---

## 3. agi-memory / Hexis (Python, PostgreSQL + Apache AGE)

**Architettura**: PostgreSQL con estensione Apache AGE (graph DB nativo)

### Memory Types
- **Episodic**: cosa è successo (con emotional_valence, context)
- **Semantic**: fatti/conoscenza (con source_references, trust_level)
- **Procedural**: come fare le cose (con steps)
- **Strategic**: decisioni/ragionamenti (con supporting_evidence)

### Graph Features
```python
# Concetti come nodi grafo
await mem.link_concept(memory_id, "encoding", strength=1.0)
await mem.find_by_concept("encoding", limit=10)

# Relazioni tra memorie
await mem.connect_memories(from_id, to_id, "caused_by", confidence=0.8)
await mem.find_causes(memory_id, depth=3)        # catene causali
await mem.find_contradictions(memory_id)           # contraddizioni

# Cluster + partial activation ("punta della lingua")
await mem.explore_clusters(query, limit=3)
```

### Cognitive Features
- **Working memory**: items temporanei con TTL (scade dopo N secondi)
- **Spontaneous memories**: emergono senza query (alta importanza + basso accesso recente)
- **Hydration**: singola call → raccoglie memorie + partial activations + identity + worldview + emotional state + drives
- **Trust/provenance**: ogni memoria ha source_attribution + trust_level calcolato
- **Goals + Scheduled Tasks**: integrati nel sistema memoria
- **Identity/Worldview/Emotions**: stored nel grafo come nodi speciali

### Cosa prendere (idee, non implementazione)
- **Catene causali** — "cosa ha causato questo bug?" traversando relazioni
- **Contraddizioni** — detectare info nuove che contraddicono memorie vecchie
- **Memory types distinti** — episodic vs semantic vs procedural (noi abbiamo MemoryType ma non lo usiamo)
- **Hydration pattern** — un singolo call per raccogliere tutto il contesto
- **Spontaneous memories** — memorie che emergono per alta importanza

---

## 4. mem0 (Python, Vector + Graph DB multipli)

**Architettura**: Dual storage — Vector Store (Qdrant/ChromaDB/etc) + Graph Store (Neo4j/Memgraph/Neptune/Kuzu)

### Come funziona — Ingest (`Memory.add`)
1. **LLM estrae fatti**: dal testo, LLM genera lista di `facts` (frasi atomiche)
2. **Per ogni fatto, cerca memorie simili** via vector search (limit=5)
3. **LLM decide azione**: confronta nuovi fatti vs memorie esistenti → decide ADD/UPDATE/DELETE/NONE per ciascuno
4. **Graph parallelo**: se `enable_graph=True`, in parallelo:
   - LLM estrae **entità** (function calling): `[{entity: "encoding", entity_type: "concept"}, ...]`
   - LLM estrae **relazioni** tra entità: `[{source: "BOM", relationship: "corrupts", destination: "JSON_stdin"}, ...]`
   - Per ogni entità: embed nome → cerca nodo simile nel grafo (threshold 0.7) → MERGE se esiste, CREATE se nuovo
   - Crea archi tipizzati con Cypher: `MERGE (source)-[r:CORRUPTS]->(destination)`
   - Traccia `mentions` (conteggio accessi) su nodi e archi

### Come funziona — Search (`Memory.search`)
1. **Vector search**: embed query → search vector store → top-N risultati
2. **Graph search** (parallelo):
   - LLM estrae entità dalla query
   - Per ogni entità: embed → cerca nodo simile → traverse relazioni in/out via Cypher
   - BM25 reranking sui risultati del grafo
3. **Merge**: combina risultati vector + graph, opzionale reranking finale

### Kuzu — Graph DB Embedded (chiave per noi)
```python
# Schema semplice
CREATE NODE TABLE Entity(id SERIAL PRIMARY KEY, user_id STRING, name STRING, mentions INT64, embedding FLOAT[])
CREATE REL TABLE CONNECTED_TO(FROM Entity TO Entity, name STRING, mentions INT64)

# Cerca nodo simile prima di creare
# → embed("encoding") → vector search → se score > 0.7 → MERGE, altrimenti CREATE
# → tutto embedded, zero server esterni, singolo file DB
```
Config: `KuzuConfig(db="/path/to/kuzu.db")` — un file, come SQLite.

### Architettura LLM-heavy
mem0 fa **3 chiamate LLM per ingest**:
1. Estrai fatti dal testo
2. Decidi ADD/UPDATE/DELETE per ogni fatto vs memorie esistenti
3. Estrai entità + relazioni per il grafo

### Cosa prendere
- **Kuzu come graph DB embedded** — zero dipendenze esterne, file singolo, Cypher nativo
- **Entity dedup via vector similarity** — embed nome entità → cerca simili → merge (threshold 0.7)
- **Mentions counting** — reinforcement naturale: nodi/archi più menzionati = più importanti
- **LLM-based memory management** — non solo estrai, ma decidi cosa fare (ADD/UPDATE/DELETE/NONE)
- **Parallel vector + graph search** — combina risultati per recall più ricco

### Cosa NON prendere
- **3 LLM calls per ingest** — troppo costoso/lento per noi (Groq ha rate limits)
- **Neo4j/Neptune/Memgraph** — server esterni, overkill per uso single-user
- **Complessità config** — mem0 ha 15+ backend combinations, noi abbiamo ChromaDB+Groq+Jina e basta

---

## 5. episodic-memory (Python, NetworkX + HNSWlib)

**Architettura**: NetworkX MultiDiGraph + HNSWlib per RL/agent memory

### Tre Layer di Memoria
- **IndexMemory** (HNSWlib): vettori di stato → nearest neighbor search (L2/cosine), `has_neighbor(threshold)` per dedup
- **TreeMemory** (NetworkX MultiDiGraph): transizioni stato→azione→stato, archi pesati con weight/oldness/stability
- **EpisodicMemory**: orchestratore che coordina i due

### Decay Model — Ebbinghaus-Based
```python
# weight = exp(-(oldness / stability))
# Ogni step: oldness += 1
# Se sequenza rivista: weight = 1, stability += 1 (consolidamento)
# Se weight < 0.01: arco rimosso (dimenticato)
# Nodi isolati: rimossi automaticamente
```

### Concetti Chiave
- **Stability vs Oldness**: stabilità aumenta con ripetizione, oldness cresce linearmente → memorie ripetute resistono al decay
- **MultiDiGraph**: più azioni possibili tra stessi stati (non solo una relazione)
- **Centrality**: calcola `degree_centrality` su tutti i nodi dopo ogni update → nodi hub importanti
- **Trajectory sampling**: `sample_trajectories(n, horizon)` — genera sequenze di azioni dal grafo

### Cosa prendere
- **Ebbinghaus decay** `exp(-(oldness/stability))` — più elegante del nostro flat energy boost/decay
- **Stability reinforcement** — ogni re-encounter aumenta stability → memorie consolidate durano di più
- **Auto-pruning** — archi sotto threshold rimossi, nodi isolati garbage-collected

---

## 6. Cognee (Python, Knowledge Graph + Vectors) — ANALIZZATO via lsai-dev

**Architettura**: Pipeline-based knowledge engine — `add()` → `cognify()` → `search()`

### Pipeline `cognify()` (5 step)
1. **classify_documents** — identifica tipo documento
2. **extract_chunks_from_documents** — chunking semantico (TextChunker o LangchainChunker)
3. **extract_graph_from_data** — LLM estrae knowledge graph (nodi + archi) da ogni chunk
4. **summarize_text** — genera riassunti gerarchici
5. **add_data_points** — deduplica nodi/archi, inserisce in graph DB + index vettoriale

### Graph Model customizzabile
```python
# Default: KnowledgeGraph generico
# Custom: schema Pydantic per dominio specifico
class ScientificPaper(DataPoint):
    title: str
    authors: List[str]
    methodology: str
    findings: List[str]

await cognee.cognify(graph_model=ScientificPaper)
```

### 14 tipi di Search
`CHUNKS`, `SUMMARIES`, `RAG_COMPLETION`, `GRAPH_COMPLETION`, `TRIPLET_COMPLETION`,
`GRAPH_COMPLETION_COT` (chain-of-thought iterativo), `GRAPH_COMPLETION_CONTEXT_EXTENSION`,
`CYPHER` (query dirette), `NATURAL_LANGUAGE`, `TEMPORAL`, `CODING_RULES`, `FEELING_LUCKY` (auto-select), ...

### `cognify_session` — Memify per sessioni
```python
# Ingest sessione Q&A in knowledge graph
await cognee.add(session_data, node_set=["user_sessions"])
await cognee.cognify(datasets=[dataset_id])
```

### Cosa prendere
- **Pipeline modulare** — task come step indipendenti, facili da aggiungere/rimuovere
- **Graph model customizzabile** — schema Pydantic → LLM estrae entità tipizzate
- **Triplet embeddings** — embed anche le triple (source, relation, target) per search semantico su relazioni
- **Session cognification** — automaticamente trasforma sessioni in knowledge graph

### Cosa NON prendere
- **Complessità** — 14 tipi di search, multi-backend (Kuzu/Neo4j/Neptune), ontologie OWL — overkill per noi
- **LLM-heavy** — ogni ingest richiede multiple LLM calls per extraction

## 7. Letta/MemGPT (Python, Self-Editing Memory) — non caricato in lsai-dev
**GitHub**: `letta-ai/letta` (repo troppo grande)
- Agenti che gestiscono la propria memoria (self-editing)
- Gerarchia: core memory (in-context, come RAM) ↔ archival memory (disk)
- L'agente decide cosa tenere in context e cosa archiviare
- Context Repositories: git-based versioning della memoria
- **Idea chiave**: l'agente ha tools per editare la propria memoria

## 8. MemOS (Python, Memory Operating System) — ANALIZZATO via lsai-dev

**Architettura**: Enterprise memory OS con tree-structured memory + graph DB

### Memory Types (via config)
- **NaiveTextMemory** — testo semplice + LLM extractor
- **GeneralTextMemory** — + vector DB + embedder
- **TreeTextMemory** — full: LLM extractor + dispatcher + embedder + reranker + graph DB + internet retriever
- **PreferenceTextMemory** — preferenze utente (implicite/esplicite)
- **MemFeedback** — feedback loop su memorie
- **KVCacheMemory** — key-value cache per LLM
- **LoRAMemory** — fine-tuning parametrico

### TreeTextMemory Features
- **Graph DB**: Neo4j, NebulaGraph, PolarDB, Postgres (no embedded!)
- **Reorganize**: consolidamento automatico delle memorie
- **Memory buckets**: `WorkingMemory: 20`, `LongTermMemory: 10000`, `UserMemory: 10000`
- **Search strategy**: BM25 + CoT (chain-of-thought)
- **Scheduler**: async ingestion con queue management

### Chat Flow (memory-augmented)
1. Search memorie rilevanti per query
2. Filtra per threshold di rilevanza
3. Build system prompt con memorie come contesto
4. LLM genera risposta con riferimenti alle memorie `[1]`, `[2]`
5. Post-processing: add conversazione a memoria, scheduler tasks, further suggestions

### Cosa prendere
- **Memory buckets con limiti** — WorkingMemory (recente, piccola) vs LongTermMemory (consolidata, grande)
- **Reorganize** — consolidamento periodico, concetto simile al nostro dream ma più strutturato
- **Feedback loop** — le memorie hanno feedback score, migliorano con l'uso

### Cosa NON prendere
- **Enterprise complexity** — Neo4j/Postgres required, multi-tenant, scheduler Redis
- **KVCache/LoRA memory** — irrilevante per il nostro caso (non facciamo fine-tuning)

---

## Piano Priorità per neo-cortex (Aggiornato)

### P1 — Grafo Concetti con NetworkX (da memoripy + mem0 + episodic-memory)
**Impatto: alto | Effort: basso**
- **Scelta**: NetworkX (zero dipendenze, persistenza JSON, Python puro)
  - Alternativa futura: Kuzu (da mem0) se serve Cypher e performance su grafi grandi
- Concetti (GIÀ estratti dal classifier → `concepts[]`) diventano nodi
- Archi tra concetti co-occorrenti nella stessa memoria (peso = #co-occorrenze)
- Archi memoria↔concetto (bidirezionali)
- **Spreading activation** durante recall (da memoripy + OpenMemory waypoints)
- **Mentions counting** (da mem0): ogni volta che un concetto appare, incrementa mentions → importanza
- **Entity dedup via similarity** (da mem0): prima di creare nodo, controlla se esiste concetto simile
- **Stability-based decay** (da episodic-memory): `exp(-(oldness/stability))`, stability++ on re-encounter

### P2 — Deduplicazione SimHash (da OpenMemory — implementazione completa disponibile)
**Impatto: medio | Effort: basso**
- SimHash 64-bit del contenuto prima di ingestion (zero costo API, puro Python)
- Se Hamming distance ≤ 3 da memoria esistente → boost salience +0.15, NON creare duplicato
- L'algoritmo è 100% locale: tokenize → hash per token → somma bit-pesata → hex string
- Evita accumulo di memorie ripetitive (problema reale con ~300 memorie)

### P3 — Usare i dati già estratti dal Classifier
**Impatto: alto | Effort: basso**
- `facts[]`, `concepts[]`, `files_touched[]` vengono estratti ma MAI usati nel recall
- Connettere facts/concepts al grafo P1
- Usare files_touched come filtro aggiuntivo nel recall ("trova memorie che toccano store.py")
- Il classifier fa già il lavoro — dobbiamo solo collegare i risultati

### P4 — Catene Causali (da Hexis, su NetworkX)
**Impatto: medio | Effort: medio**
- Relazioni tipizzate: `caused_by`, `evolved_into`, `relates_to`, `contradicts`
- Estensione prompt classifier: "estrai anche relazioni causali tra concetti"
- `find_causes(memory_id, depth)` via graph traversal
- `find_contradictions(concept)` — detecta info nuove vs memorie vecchie (da Hexis)

### P5 — Retrieval Reinforcement (da OpenMemory)
**Impatto: medio | Effort: basso**
- Ogni recall rafforza la memoria: `salience += η * (1 - salience)`
- Propagazione ai vicini nel grafo: boost proporzionale a weight dell'arco
- Oggi il recall non modifica nulla — memorie vecchie ma utili non vengono mai rafforzate

### P6 — Memory Consolidation (Dream potenziato)
**Impatto: alto | Effort: medio**
- Dream cycle attuale: solo energy boost/decay piatto
- Nuovo: Ebbinghaus decay `exp(-(oldness/stability))` (da episodic-memory)
- Merge memorie simili in cluster → una sola memoria "consolidata"
- Come LLM-based memory management di mem0 (ma nel dream cycle, non in real-time)
- Auto-pruning: memorie con salience < threshold → rimosse

### P7 — Spontaneous Recall
**Impatto: medio | Effort: basso**
- Memorie ad alta importanza + basso accesso recente emergono proattivamente
- "Questo mi ricorda qualcosa di importante che non guardi da un po'"

---

## Confronto Sistemi Analizzati

| Sistema | Graph Tech | Embedded? | LLM calls/ingest | Dedup | Decay Model | Spreading |
|---------|-----------|-----------|-------------------|-------|-------------|-----------|
| **neo-cortex** | Nessuno | SI | 1 (classify) | No | Flat +0.1/-0.03 | No |
| memoripy | NetworkX | SI | 0 | No | time×access | SI (2-hop) |
| OpenMemory | SQLite waypoints | SI | 0 | SimHash | exp(-λ×days) per settore | SI (BFS) |
| Hexis | PostgreSQL+AGE | No | ~2 | No | Non chiaro | Catene causali |
| mem0 | Neo4j/Kuzu | Kuzu SI | 3 | Vector sim | mentions | No |
| episodic-memory | NetworkX | SI | 0 | HNSWlib neighbor | exp(-old/stab) | Centrality |
| Cognee | Kuzu/Neo4j | Kuzu SI | ~2 (extract+sum) | Dedup nodi | No | Triplet embed |
| MemOS | Neo4j/Postgres | No | ~2 | No | Reorganize | BM25+CoT |

## Confronto Tecnologie Grafo

| Feature | NetworkX | Kuzu | Neo4j/Memgraph |
|---------|----------|------|----------------|
| Dipendenze | Zero (stdlib-like) | `pip install kuzu` | Server esterno |
| Persistenza | JSON file | Single file DB | Server process |
| Query language | Python API | Cypher nativo | Cypher nativo |
| Performance grafi grandi | Medio | Alto | Alto |
| Complessità | Minima | Bassa | Alta |
| Adatto a noi ora | **SI** | Futuro | No |

**Decisione**: NetworkX per P1-P5. Se il grafo supera ~10k nodi, valutare migrazione a Kuzu.

## Pattern Universali (trovati in TUTTI i sistemi)
1. **Grafo di concetti/entità** — tutti lo hanno, noi no (P1)
2. **Decay esponenziale** — tutti usano exp(-x), noi flat ±0.03 (P6)
3. **Reinforcement on access** — recall rafforza la memoria (P5)
4. **Dedup prima di ingest** — evita bloat (P2)
5. **Spreading activation** — espandi recall via grafo (P1)
