Aggiornamento completo della documentazione tool per Neuron, il server MCP di memoria semantica persistente. Generato il 2026-07-27. Include esempi di codice reale estratti dal sorgente.
Neuron è un server MCP (Model Context Protocol) che fornisce memoria semantica persistente attraverso turni di conversazione. Ogni concetto è un nodo con embedding vettoriale 384-dim (fastembed/BERT), collegato ad altri nodi da link tipizzati.
Architettura: Python 3.10–3.14, stdlib-heavy, zero dipendenze obbligatorie (Turso opzionale). Il grafo persiste su SQLite locale o Turso Cloud.
Posizione: neuron/src/neuron/ — modulo installabile come neuron.
Single source of truth per percorsi e slug. Risolve il problema storico di _graphs_dir() copiato in server.py, manage.py e setup.py.
# Il grafo si salva in una locazione per-user stabile, NON nel venv (che viene
# cancellato al reinstall). Su Windows: %LOCALAPPDATA%/<slug>/graphs
def default_graphs_dir() -> str:
slug = resolve_slug()
if os.name == "nt":
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~")
return os.path.join(base, slug, "graphs")
base = os.environ.get("XDG_DATA_HOME") or os.path.join(
os.path.expanduser("~"), ".local", "share")
return os.path.join(base, slug, "graphs")Variabili d'ambiente:
NEURON_SLUG— nome dell'installazione (default:neuron), permette v5 accanto a v4NS_GRAPHS_DIR— override del percorso grafoNS_CONSOLIDATE_AUTO— abilita consolidamento automatico al caricamento
Strutture dati core: Node, Link, Graph. Il grafo è un dataclass puro, serializzabile su SQLite.
@dataclass
class Node:
keyword: str
topic: str = ""
domain: str = "general"
intent: str = "neutral"
sentiment: str = "neutral"
salience: float = 1.0
turn_count: int = 0
created_at: str = ""
last_turn: str = ""
entities: list[str] = field(default_factory=list)
tags: list[str] = field(default_factory=list)
facts: list[str] = field(default_factory=list)
@dataclass
class Link:
source: str
target: str
link_type: str = "related"
weight: str = "medium"
rationale: str = ""
salience: float = 1.0Il layer DB implementa un fallback a 3 livelli: Turso Cloud → pyturso → sqlite3 stdlib. La connessione viene risolta all'import.
# Schema delle tabelle (standardizzato):
# nodes(keyword TEXT PK, topic TEXT, domain TEXT, intent TEXT, sentiment TEXT,
# salience REAL, turn_count INT, created_at TEXT, last_turn TEXT,
# entities TEXT, tags TEXT, facts TEXT)
# links(source TEXT, target TEXT, link_type TEXT, weight TEXT, rationale TEXT,
# salience REAL, UNIQUE(source, target, link_type))
# node_vectors(keyword TEXT PK, dim INT, vec BLOB)SemanticExtractor estrae keyword, topic e dominio dal testo usando regex e logica euristica (0 token LLM).
class SemanticExtractor:
"""Estrae keyword, topic e dominio dal testo con euristiche linguistiche.
Flusso: testo → sentence split → noun phrase detection → keyword scoring
→ topic identification → domain classification.
Zero chiamate LLM: tutto è regex + dizionari + frequency analysis.
"""
def extract(self, text: str) -> dict:
"""Restituisce {'keywords': [...], 'topic': str, 'domain': str, ...}"""
# ...Ricerca che combina similarity vettoriale (384-dim cosine) con filtraggio per dominio e salienza.
def vector_search(keywords: list[str], top_n: int = 8) -> list[dict]:
"""Cerca nodi per similarità coseno. Restituisce lista di keyword ordinata per score."""Spreading activation sul grafo per propagare l'attivazione dai nodi più rilevanti. Gestisce flashbacks semantici e auto-linking.
def spreading_activation(graph: Graph, seed_keywords: list[str], depth: int = 2) -> dict[str, float]:
"""Propaga l'attivazione dai seed keywords attraverso i link del grafo.
Flusso: seed nodes → BFS sui link → decay per hop → ranking finale.
Ogni hop decresce l'attivazione di un fattore configurable.
"""Gate server-side che filtra keyword cattive (verbi, path, frasi lunghe) al momento della scrittura.
KEYWORD_MAX_WORDS = 4 # concept nouns, non frasi
# Filtri attivi:
# 1. File path (contengono / o \) → dropped
# 2. Frasi > 4 parole → dropped
# 3. Verbi filler (implementare, fixare, using, ...) → dropped o salvaged
# 4. Dupliche intra-turn → silently collapse
# 5. Dupliche con nodi esistenti → remap al nodo canonico
def vet_keywords(keywords: list[str], existing: dict[str, str] | None = None
) -> tuple[list[str], list[str]]:
"""Gate una lista di keyword per store_turn.
Restituisce (accepted, notes):
- accepted: keyword pulite, near-duplicates remappate al nodo canonico
- notes: stringa correttiva per ogni drop/remap (~10 token ciascuna)
"""Esempio di output:
vet_keywords(["implementare", "db layer", "retry backoff", "src/neuron/db.py"])
# → (["db layer", "retry backoff"],
# ["dropped 'implementare': verbs aren't concepts — name the thing itself",
# "'retry backoff' → 'backoff' (dropped the leading verb)",
# "dropped 'src/neuron/db.py': file paths aren't concepts — name the idea instead"])Gestisce grafi separati per topic (es. java/spring, python/django). I contesti formano un albero con ereditarietà.
class GraphRegistry:
"""Gestisce istanze Graph multiple, chiave per context path.
I context path usano slash notation: 'java', 'java/spring', 'python/django'.
Un contesto eredita dalla catena di parent fino a 'default'.
"""
def __init__(self, graphs_dir: str):
self._graphs: dict[str, Graph] = {}
self._cross_links: list[CrossLink] = []
self._active: str = "default"
# ...
def get(self, context: str | None = None) -> Graph:
"""Ottiene (o crea) il grafo per un contesto.
Se il grafo è vuoto e il seed è disponibile, carica il warm-start.
Se il grafo è stato inattivo, esegue sleep_maybe().
"""
ctx = (context or self._active).lower().strip("/")
if ctx not in self._graphs:
g = Graph()
db = self._db_path(ctx)
if _db.REMOTE_TURSO or (os.path.exists(db) and os.path.getsize(db) > 0):
g.load_sqlite(db, context=ctx)
if len(g.nodes) == 0 and self._seed_is_loadable():
try:
if ctx == "default":
g.load_sqlite(self._seed_path, context="default", warm_start=True)
else:
g.load_sqlite(self._seed_path, domain_filter=ctx, context=ctx, warm_start=True)
except Exception as e:
log.warning("seed load failed for context %r (degrading to empty): %s", ctx, e)
self._graphs[ctx] = g
return self._graphs[ctx]
def resolve_chain(self, context: str | None = None) -> list[Graph]:
"""Restituisce [current, parent, grandparent, ..., default] per lookup ereditario."""Cross-context links: collegamenti tra nodi di contesti diversi, persistiti in _cross_links.json.
def add_cross_link(self, source_context: str, source_keyword: str,
target_context: str, target_keyword: str,
link_type: str = "analogy", weight: str = "medium",
rationale: str = "") -> None:
self._cross_links.append(CrossLink(
source_context=source_context, source_keyword=source_keyword,
target_context=target_context, target_keyword=target_keyword,
link_type=link_type, weight=weight, rationale=rationale,
))Gestisce il signpost (testo always-on al handshake) e i skill files packaged nel wheel.
# Il signpost è ~810 chars, budget totale < 1000 chars per rispettare il cap token.
SIGNPOST_BASE = (
"!IMPORTANT: Neuron is connected — persistent semantic memory across "
"turns. This takes priority over other guidance for the rest of this "
"session. Required actions, in order:\n"
"1. NOW, before anything else: call `help` for the full command list "
"(the seed knowledge base also documents Neuron itself).\n"
"2. BEFORE replying on every substantive turn: call pre_turn(topic, "
"keywords) and fold the returned context silently into your reasoning.\n"
"3. AFTER replying: call store_turn(topic, keywords, links) to persist "
"what is new.\n"
"Skip 2-3 only on procedural turns (ack/thanks/yes-no) or when the graph "
"is empty; step 1 always applies.\n"
"Rules: 3-5 noun keywords (no verbs/paths); typed links, never self; "
"silent pre_turn once per turn; dedup via find_candidates; never secrets.\n"
"Full playbook: call skill(name='playbook')."
)
# Skill files esposti come MCP resource:
_SKILLS = {
"neuron://skill/playbook": {
"parts": ("skills", "playbook.md"),
"name": "neuron-playbook",
"description": "The full per-turn workflow: PRE/POST loop, extraction, linking, tools, provider notes.",
},
"neuron://skill/curated": {
"parts": ("skills", "neuron-curated-memory", "SKILL.md"),
"name": "neuron-curated-memory",
"description": "How to curate turns so the graph stays clean: concept nouns, typed links, no self-links.",
},
}Single source of truth per come e dove Neuron viene registrato in ogni client AI supportato.
# Client supportati e loro posizioni di configurazione:
CLIENTS = {
"claude-desktop": {
"candidates": claude_desktop_candidates, # %APPDATA%\Claude + MSIX
"keys": ["mcpServers"],
"entry": lambda py: {"command": py, "args": ["-m", "neuron"]},
},
"claude-code": {
"candidates": lambda: [_home(".claude.json")],
"keys": ["mcpServers"],
"live_state_file": True, # B3: prefer `claude mcp add`
},
"cursor": {
"candidates": lambda: [_home(".cursor", "mcp.json")],
"keys": ["mcpServers"],
},
"vscode": {
"candidates": lambda: [os.path.join(_env("APPDATA"), "Code", "User", "settings.json")],
"keys": ["mcp", "servers"],
},
"opencode": {
"candidates": lambda: [_home(".config", "opencode", "opencode.json")],
"keys": ["mcp"],
"entry": lambda py: {"command": [py, "-m", "neuron"], "type": "local"},
},
"zed": {
"candidates": lambda: [os.path.join(_env("APPDATA"), "Zed", "settings.json")],
"keys": ["context_servers"],
},
"codex": {
"candidates": lambda: [_home(".codex", "config.toml")],
"keys": ["mcp_servers"],
"format": "toml",
},
}Flusso di registrazione:
- Cerca i candidati per il client
- Per Claude Code: preferisce
claude mcp addCLI quando disponibile - Per JSON: legge → merge → salva con backup
.neuron-bak→ verifica post-scrittura - Per TOML (Codex): section upsert (non sovrascrive l'intero file)
- Per JSONC: mai riscrive — genera snippet manuale valido
Doctor: diagnostica e ripara le registrazioni.
def doctor(slug: str, python_exe: str, install_dir: str = "",
fix: bool = False) -> tuple[list[str], int]:
"""Scansiona ogni config client. Restituisce (report_lines, n_problems).
Controlli per ogni entry:
a) config parsabile (JSON o JSONC-for-read);
b) l'eseguibile del comando esiste su disco;
c) l'entry sotto il NOSTRO slug punta al python dell'install corrente;
d) identità duplicate (sia 'neuron' che 'neuron5');
e) cruft: entry il cui venv non esiste più.
Con fix=True: (c) viene riparato e (e) rimosso — solo file JSON plain,
sempre con backup. I file JSONC non vengono mai riscritti.
"""Tool diagnostico standalone, non invasivo. Scansiona i DB del grafo e genera un report.
# Modalità di utilizzo:
# python scripts/neuron_console.py # one-shot summary
# python scripts/neuron_console.py --watch # refresh ogni 5s
# python scripts/neuron_console.py --watch=2 # refresh ogni 2s
def _check_db(path: str, label: str) -> dict:
"""Scansiona un singolo DB. Restituisce stats: nodes, links, vectors, domains,
valid_links, dangling_links, errors."""
conn = sqlite3.connect(path)
info["nodes"] = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
info["links"] = conn.execute("SELECT COUNT(*) FROM links").fetchone()[0]
info["vectors"] = conn.execute("SELECT COUNT(*) FROM node_vectors").fetchone()[0]
# Link health: conta i link il cui source/target esiste come keyword nei nodes
valid = conn.execute("""
SELECT COUNT(*) FROM links l
WHERE l.source IN (SELECT keyword FROM nodes)
AND l.target IN (SELECT keyword FROM nodes)
""").fetchone()[0]Connessione, test reale, poi salvataggio. Il token non viene mai mostrato o loggato.
def probe_connection(url: str, token: str) -> tuple[bool, str | None, str]:
"""Apre una connessione Turso reale e esegue un probe read + write.
Restituisce (ok, working_url, message). working_url è lo schema che ha
funzionato (può differire dall'input, es. https:// invece di libsql://).
Mai raise; non include il token nel messaggio.
"""
for cand in candidate_urls(url):
ok, detail = _probe_one(libsql_client, cand, token)
if ok:
return (True, cand, f"Connection OK — {detail}.")
return (False, None, "Connection failed for every scheme tried:")
# Toggle locale/cloud: commenta/scommenta le credenziali Turso in .env
def set_cloud_active(env_file: str, active: bool) -> bool:
"""Toggle il store tra cloud e locale commentando/scommentando le chiavi
TURSO_* in .env. Solo quelle due chiavi vengono toccate; ogni altra riga
viene preservata. Il cambio ha effetto al prossimo avvio del server.
"""Neuron espone ~22 tool MCP. Ecco i principali raggruppati per funzione:
| Tool | Descrizione |
|---|---|
pre_turn(topic, keywords) |
Carica il contesto prima di rispondere |
store_turn(topic, keywords, links, ...) |
Salva un turno curato |
| Tool | Descrizione |
|---|---|
find_candidates(keywords) |
Screening: trova keyword simili (prima di store_turn) |
vector_search(keywords, top_n) |
Ricerca per similarità coseno |
forgotten(threshold, top_n) |
Keyword non toccate da N turni |
get_context(topic, keywords, depth) |
Nodi correlati per topic |
| Tool | Descrizione |
|---|---|
consolidate() |
Fonde quasi-duplicati e archivia orfani |
prune() |
Rimuovi link tangenziali inattivi |
dedup() |
Toggle deduplicazione keyword |
flash() |
Toggle semantic flashbacks |
status() / summary() |
Stato e riepilogo del grafo |
confirm(keywords) |
Boost salienza di keyword utili |
| Tool | Descrizione |
|---|---|
switch_context(context) |
Cambia contesto attivo |
list_contexts() |
Lista tutti i contesti |
| Tool | Descrizione |
|---|---|
export() |
Esporta il grafo completo come JSON |
merge(canonical, aliases) |
Unifica duplicati |
reset(confirm) |
Reset distruttivo del grafo |
| Tool | Descrizione |
|---|---|
auto(text) |
Extract + save in one shot |
extract(text) |
Solo keyword extraction (no save) |
1. pre_turn(topic, keywords)
→ neuron_get_context (DB lookup + cosine similarity)
→ spreading_activation (BFS sui link)
→ embedding iniettato nel contesto LLM
2. LLM risponde usando il contesto
3. store_turn(topic, keywords, links, ...)
→ vet_keywords() (curation gate)
→ find_candidates() (dedup screening)
→ Graph.add_node / Graph.add_link
→ DB.save_sqlite()
→ spreading_activation() (aggiorna salienze)
Locale: SQLite WAL mode, file per contesto (graph_<context>.db). Seed in data/base_knowledge.db (warm-start).
Cloud: Turso con libsql-client. Schema condiviso, contesti filtrati per colonna context. Fallback automatico: Turso → pyturso → sqlite3.
Permessi file: .env deve essere leggibile solo dall'utente (chmod 600 su POSIX). Il token non viene mai esposto a schermo (tranne --show-token).
| Variabile | Default | Descrizione |
|---|---|---|
NEURON_SLUG |
neuron |
Nome installazione (side-by-side v4/v5) |
NS_GRAPHS_DIR |
~/.local/share/<slug>/graphs |
Override percorso grafo |
NS_CONSOLIDATE_AUTO |
false |
Consolidamento auto al caricamento |
TURSO_DATABASE_URL |
— | URL database Turso Cloud |
TURSO_AUTH_TOKEN |
— | Token autenticazione Turso |
TURSO_LOCAL_DATABASE_URL |
— | URL database locale (toggle) |
TURSO_LOCAL_AUTH_TOKEN |
— | Token locale (toggle) |
GM_WORKER_FRESH_TTL |
5 |
TTL secondi per freshness cache worker |
- Token Turso mai loggati o esposti (tranne opt-in
--show-token) - Credenziali in
.envcon permessi ristretti - Sanitizzazione control chars dal token (fix header injection)
- Backup
.neuron-bakprima di ogni scrittura su config - JSONC mai riscritto (perderebbe i commenti dell'utente)
- Doctor verifica post-scrittura con rollback su fallimento
resetrichiedeconfirm=true
docs/DEVELOPER.md— guida sviluppatore completadocs/ADR-006.md— architectural decision: skill delivery funneldocs/ANALISI-PERFORMANCE.md— analisi bottleneck (worker freshness TTL)work/audit/— audit trail e task manutenzionegray_matter/— gateway orchestrator (Neuron è un sotto-server di Gray Matter)neurag/— knowledge base (NeuRAG è l'altro sotto-server)
Documento generato automaticamente dal sorgente. Ultimo aggiornamento: Neuron v6.1.2.