Part of Neuron — persistent semantic memory for AI. This guide covers the internals: architecture, the memory dynamics, the DB layer, per-client config and CI. For installing, see INSTALL.md; for the release story, see CHANGELOG.md.
- Architecture
- Project Structure
- Dependencies
- Vector Embeddings
- Fallback Chain
- Enabling Turso Cloud
- Key Behaviors
- MCP Client Configuration
- Interactive CLI Mode
- Development Setup
- CI/CD
- License
YOUR MCP CLIENT (OpenCode, Claude Desktop, Cursor, etc.)
│ calls MCP tools (stdin/stdout)
▼
┌──────────────────────────────────────────────────────────┐
│ server.py (Python) — MCP entry, ~22 tools │
│ ├── extraction.py — semantic extractor + lexicons │
│ ├── curation.py — quality gate (filler/dup/link fixup) │
│ ├── search.py / stimulus.py — retrieval + associative │
│ │ stimulus engine │
│ ├── funnel.py — signpost + skill registry │
│ └── vector embedding (384-dim, fastembed) · Turso │
│ vector_distance_cos() or Python fallback │
├──────────────────────────────────────────────────────────┤
│ models.py — Node, Link, Graph, episodes │
│ registry.py — GraphRegistry (multi-context, inheritance) │
│ clients.py — cross-platform client registration engine │
└────────────────────────────────┬─────────────────────────┘
▼
┌────────────────────────────────┬─────────────────────────┐
│ Turso Database (pyturso) — native vector search │
│ ├── <context>.db per context (nodes, links, vectors) │
│ ├── vector_distance_cos() inside Turso │
│ └── Python fallback (cosine similarity in memory) │
└───────────────────────────────────────────────────────────┘
The MCP server runs as a stdio subprocess of the MCP client. No HTTP layer, no daemon — every LLM tool call is processed inline.
Multi-context: each context (default, java/spring, python/django, ...) is a separate graph stored in its own .db file. Contexts form a hierarchy: java/spring inherits from java, which inherits from default. When a lookup finds no results in the active context, get_context and pre_turn automatically walk up the chain.
Neuron/
├── src/
│ └── neuron/
│ ├── __init__.py # Package init, version, .env autoload
│ ├── __main__.py # `python -m neuron` entry point (server / CLI dispatch)
│ ├── db.py # DB tier selector: Turso cloud > local pyturso > sqlite3
│ ├── models.py # Node, Link, Graph + episodes + SQLite persistence
│ ├── registry.py # GraphRegistry — multi-context, resolve_chain inheritance
│ ├── server.py # MCP server — PRODUCTION path (~22 tools, Turso/SQLite)
│ ├── extraction.py # SemanticExtractor + lexicons (split from server.py)
│ ├── curation.py # Quality gate: filler/dup drop, link canonicalization
│ ├── search.py # Vector + graph retrieval
│ ├── stimulus.py # Spreading activation / associative stimulus engine
│ ├── funnel.py # Signpost + skill registry (the "door")
│ ├── clients.py # Cross-platform client registration engine
│ ├── setup.py / manage.py / init.py # `neuron setup|manage|register|doctor` CLIs
│ └── engine.py # Standalone CLI engine for run_interactive.py — NOT production
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_core.py # Core/unit tests
│ └── test_server.py # MCP server smoke tests
├── scripts/
│ ├── run_interactive.py # Interactive CLI chat (6 LLM providers)
│ ├── run_mcp.bat # Windows MCP stdio launcher
│ ├── check.ps1 # Dependency check + repair
│ ├── neuron-summary.ps1 # Terminal graph summary
│ └── neuron_summary_query.py
├── skills/
│ ├── playbook.md # Full PRE+POST workflow skill (provider-agnostic)
│ ├── neuron-opener.md # Compact opener (OpenCode instructions)
│ └── neuron-curated-memory/ # Graph-hygiene curation skill
├── clients/ # MCP config examples per client
│ ├── claude-desktop.example.json
│ ├── claude-code.example.json
│ ├── cursor.example.json
│ ├── vscode.example.json
│ ├── zed.example.json
│ ├── cline-roocode.example.json
│ ├── opencode.example.json
│ └── generic-mcp.example.json
├── install.cmd # Windows entry point (double-click) → install.ps1
├── install.command # macOS entry point (double-click) → install.sh
├── install.sh # Linux entry point → installer engine (POSIX)
├── install.ps1 # installer engine (Windows)
├── pyproject.toml
├── README.md
├── DEVELOPER.md
├── LICENSE
├── .gitignore
└── .github/workflows/ci.yml
| Package | Required | Purpose |
|---|---|---|
mcp>=1.28.0,<2.0 |
yes | MCP SDK |
fastembed>=0.5.0,<1.0 |
yes | 384-dim semantic embedding |
pyturso==0.6.1 |
yes | Local Turso DB engine (vector_distance_cos) |
libsql-client>=0.3.1 |
no (neuron[cloud]) |
Real Turso cloud DB, used when TURSO_DATABASE_URL/TURSO_AUTH_TOKEN are set |
ollama |
no | LLM provider for chat mode |
openai |
no | LLM provider for chat mode |
anthropic |
no | LLM provider for chat mode |
google-generativeai |
no | LLM provider for chat mode |
All database access in the codebase goes through neuron.db (connect() /
connect_local()), which picks the engine tier: remote Turso cloud > local
pyturso engine > stdlib sqlite3. There is no other sqlite3-direct code path
left — models.py (graph persistence), server.py (vector search) and the
dev scripts under scripts/ all use it.
The MCP server runs with only the first 3. The LLM providers are only for run_interactive.py.
384-dim semantic embeddings via fastembed, ONNX runtime, downloaded on first import.
from fastembed import TextEmbedding
embedder = TextEmbedding()
vec = list(embedder.embed("database"))[0] # 384-dim float32The model is configurable via NS_EMBED_MODEL. Default:
sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 (384-dim, multilingual) — covers
EN and IT in one comparable space. English-only workload → set
NS_EMBED_MODEL=sentence-transformers/all-MiniLM-L6-v2 for a lighter model.
| model | dim | recall@3 | IT recall | ms/emb |
|---|---|---|---|---|
all-MiniLM-L6-v2 |
384 | 0.92 | 0.89 | 15.0 |
paraphrase-multilingual-MiniLM-L12-v2 (default) |
384 | 1.00 | 1.00 | 6.9 |
Benchmark: python scripts/bench_embed.py --k 3 (fixture tests/fixtures/bench_pairs_en_it.jsonl).
Vectors from different models are not comparable. Changing NS_EMBED_MODEL requires re-embedding
the store: run python scripts/reembed.py. save_sqlite records meta.embed_model/meta.embed_dim;
load_sqlite ignores stored vectors (and recomputes them) when they disagree with the active model,
so a model switch degrades gracefully instead of returning garbage cosine scores. For a non-384-dim
model also set NS_EMBED_DIM to match (a mismatch raises on first embed).
| Component | URL 1 | URL 2 | URL 3 | Final |
|---|---|---|---|---|
rustup-init.exe |
win.rustup.rs |
static.rust-lang.org |
GitHub raw | exit 1 |
| Windows SDK | fwlink/2120843 |
Microsoft mirror | — | skip |
| MSVC Build Tools | aka.ms/vs/17/release |
Microsoft mirror | — | 3 tries → GNU MinGW |
| GNU fallback | — | — | — | exit 1 if GNU fails |
| pip (mcp, fastembed, pyturso) | PyPI (3 retries) | — | — | exit 1 |
| Component | Primary | Secondary | Final fallback |
|---|---|---|---|
| Embedding | fastembed 384-dim | — | — |
| Database | Turso cloud (libsql-client, if TURSO_DATABASE_URL/TURSO_AUTH_TOKEN set) |
pyturso (local Turso engine) | sqlite3 (no vector search) |
| Vector search | vector_distance_cos() SQL (cloud or local engine) |
— | Python cosine in memory |
The cloud tier (RemoteTursoConnection in src/neuron/db.py) is implemented but ships
disabled: with no credentials, Neuron uses the local pyturso engine. The environment is
prepared so cloud can be switched on without code changes — connectivity against a real
Turso DB has not yet been validated end-to-end (tracked as a separate task).
To enable it:
-
Install the cloud extra first (the server imports
libsql_clientat startup once the credentials are set, so installing it first avoids an import crash):pip install -e .[cloud] # or: pip install "neuron[cloud]" -
Connect, test for real, and save — one command (recommended):
python scripts/connect_turso.py
It prompts for the database URL and auth token (token entry is hidden), then actually connects and runs a read + write probe against the real Turso DB. Only if the probe succeeds does it offer to save the two credentials into
.env. The token is never printed or logged. Flags:--check-only(test, never write),--url/--token/--yesfor non-interactive/installer use. This is the online counterpart to the offlinecheck_cloud_config.py.The URL saved may differ in scheme from what you typed:
libsql://uses a WebSocket transport that some Turso endpoints reject withWSServerHandshakeError: 400; the tool transparently falls back tohttps://(Hrana-over-HTTP, same SQL) and saves whichever scheme actually connected, so the server uses the working transport too.The server auto-loads
.envat startup (T16): on launch, Neuron searches up from its working directory for a.envand populates any unset variables from it (a real environment variable always wins). So onceconnect_turso.pyhas written.env, the cloud is used automatically — no need to also wire the vars into the clientenvblock. Override the file location withNEURON_ENV_FILE=/path/.env, or disable withNEURON_NO_DOTENV=1. Auto-loading is always skipped under pytest so the suite never hits the live cloud. To smoke-test the live path end-to-end:python scripts/smoke_cloud.py.Prefer to set the env vars by hand instead? Export both (or copy
.env.example→.env); both must be non-empty or Neuron silently stays on the local tier:export TURSO_DATABASE_URL="libsql://your-db-your-org.turso.io" export TURSO_AUTH_TOKEN="..."
-
Offline sanity check (optional) — reports the resolved tier and flags a half-configured state (credentials set but extra missing, or only one var set). Never connects, never prints the token:
python scripts/check_cloud_config.py
All members point at the same TURSO_DATABASE_URL; each pastes their own auth token
(create per-member tokens with the Turso CLI so they can be revoked individually). Everyone
then runs python scripts/connect_turso.py once. Because the store is keyed by a context
column (see save_sqlite/load_sqlite), several contexts coexist in the shared tables
without colliding, and each member's saves upsert only their delta — no save wipes another's
rows. A solo user and the team run the same code; the only difference is the connection
string, so nothing behaves differently when working alone.
Tier selection is automatic and ordered: Turso cloud (both env vars) → local pyturso
(installed) → stdlib sqlite3 (last resort, no native vector search). connect_local()
always stays local for file-scoped seed/vector operations even when cloud is active.
get_context and pre_turn always search the full resolution chain when the active graph returns no results:
active: java/spring → java → default
If "virtual threads" exists only in default, querying it from java/spring still returns it, annotated with (from:default). No client-side configuration needed.
neuron_pre_turn(topic, keywords) is a single-call alternative to status + get_context(compact). It returns a one-liner status followed by compact context:
[neuron] ctx=backend turn=14 nodes=42 links=31(active 18)
links:kotlin_flow-[s]->coroutines|spring_boot-[m]->di | nodes:kotlin_flow(22),spring_boot(18)
Designed for providers without automatic injection hooks (OpenCode, Cursor, etc.) — one call at turn start gives everything needed to answer with memory.
Extraction runs on the per-turn hot path (auto tool → _auto_extract). Two modes exist:
- Heuristic only:
SemanticExtractor— lexical analysis, token scoring, pattern matching. Zero tokens, deterministic, fast, fully unit-tested. Used byautoandextract. Server-side LLM extraction was removed — LLM-based extraction is the calling LLM's responsibility viastore_turn.
All keywords are normalized at ingestion (strip().lower()). add_node deduplicates on the normalized key and max-merges salience rather than creating duplicates. add_link normalizes source/target and deduplicates in both directions.
Semantic auto-links (generated from store_turn) use cosine similarity thresholds: ≥0.65 → strong, ≥0.45 → medium, ≥0.30 → tangential. Top 10 candidates per keyword are evaluated per call, with cross-call dedup to avoid re-adding existing links.
Nodes accumulate salience from store_turn (intent weight) and confirm (explicit feedback boost). Nodes not referenced in the last 5 turns lose 1 salience point. Tangential links expire after 5 inactive turns via prune_tangential().
Neuron is a local stdio MCP server — it has no HTTP layer and is launched as a
subprocess by the client (python -m neuron, or run_mcp.bat on Windows). How you mount it
depends on what transport the client speaks:
- Local-stdio clients (Claude Desktop, Claude Code, Cursor, OpenCode, Cline/Roocode, VS Code Copilot, Windsurf, Zed, Continue.dev, Cody, Amazon Q, and the Perplexity macOS app) accept a launch command directly. Register the command below and restart the client.
- Remote-only clients (ChatGPT / OpenAI Apps & connectors) connect to an HTTPS MCP endpoint, not a local process. To use Neuron there, wrap the stdio server with a stdio→HTTP bridge (MCP SDK Streamable HTTP, in-process), expose it over a public HTTPS tunnel, and register that URL as a custom connector — see ChatGPT / OpenAI below.
The launch command itself is the same everywhere:
| Platform | Command | Args |
|---|---|---|
| Windows (active install) | cmd |
/c %LOCALAPPDATA%\Programs\neuron\scripts\run_mcp.bat |
| Windows (dev repo) / Linux / macOS | python3 |
-m neuron (inside the project venv) |
On Linux/macOS, run from the project venv so mcp, fastembed and pyturso are importable
(source .venv/bin/activate or point the client at .venv/bin/python3).
{
"mcp": {
"neuron": {
"command": ["cmd", "/c", "%LOCALAPPDATA%\\Programs\\neuron5\\scripts\\run_mcp.bat"],
"type": "local"
}
}
}{
"mcpServers": {
"neuron": {
"command": "cmd",
"args": ["/c", "%LOCALAPPDATA%\\Programs\\neuron5\\scripts\\run_mcp.bat"]
}
}
}{
"mcpServers": {
"neuron": {
"command": "python3",
"args": ["-m", "neuron"]
}
}
}{
"mcpServers": {
"neuron": {
"command": "python3",
"args": ["-m", "neuron"]
}
}
}{
"mcpServers": {
"neuron": {
"command": "python3",
"args": ["-m", "neuron"]
}
}
}In .vscode/settings.json:
{
"github.copilot.mcpServers": {
"neuron": {
"command": "python3",
"args": ["-m", "neuron"]
}
}
}{
"mcpServers": {
"neuron": {
"command": "python3",
"args": ["-m", "neuron"]
}
}
}{
"mcp_servers": {
"neuron": {
"command": "python3",
"args": ["-m", "neuron"]
}
}
}{
"experimental": {
"mcpServers": {
"neuron": {
"command": "python3",
"args": ["-m", "neuron"]
}
}
}
}{
"mcpServers": {
"neuron": {
"command": "python3",
"args": ["-m", "neuron"]
}
}
}{
"mcpServers": {
"neuron": {
"command": "python3",
"args": ["-m", "neuron"]
}
}
}The Perplexity Mac app can run local MCP servers through its PerplexityXPC helper
(Perplexity → Settings → Connectors → Add local MCP). There is no JSON config file: you fill
in the command and args in the UI.
| Field | Value |
|---|---|
| Command | /path/to/Neuron/.venv/bin/python3 (or python3 if the venv is active) |
| Arguments | -m neuron |
Install the PerplexityXPC helper when prompted, then add Neuron as a local connector and
enable it. Local MCP is currently macOS-only; on Windows use one of the stdio clients above,
or the bridge route below. Remote custom connectors are a paid-tier Perplexity feature.
ChatGPT (Developer Mode connectors / Apps SDK) only talks to remote HTTPS MCP endpoints —
it cannot launch a local stdio process. Neuron is a stdio server, so you put a stdio→HTTP
bridge in front of it and give ChatGPT the bridge's public URL. Transport and storage are
independent: the bridge only changes how the client reaches Neuron; the wrapped
python -m neuron still resolves its storage tier from the environment exactly as usual
(local files, or the shared Turso Cloud if .env / the env provide the credentials).
Use a bridge that runs a stdio server and exposes it over HTTP/SSE — the right tool is
the MCP SDK's own Streamable HTTP transport. (Do not use
mcp-remote: that goes the other direction — it lets a stdio client reach a remote HTTP
server, which is the opposite of what we need.)
# 1. Wrap Neuron's stdio server and serve it over HTTP/SSE on localhost.
python3 -m neuron.bridge --port 8000
# → local endpoints: http://127.0.0.1:8000/mcp (Streamable HTTP) and /sse (legacy)
# 2. Expose that local port over PUBLIC HTTPS — ChatGPT connectors are remote and
# can't reach localhost. A quick tunnel:
cloudflared tunnel --url http://127.0.0.1:8000
# → gives a https://<random>.trycloudflare.com URL
# 3. In your client (Perplexity, or ChatGPT → Settings → Connectors (Developer Mode))
# add the public HTTPS URL with the /mcp path, e.g.
# https://<random>.trycloudflare.com/mcp (NOT /sse — Cloudflare buffers the SSE
# handshake so the legacy endpoint times out behind a tunnel).Notes:
- MCP connectors in ChatGPT require Developer Mode (beta) and are limited to Plus / Pro / Business / Enterprise / Education plans on the web.
- Security: the tunnel exposes a network endpoint and Neuron ships with no auth layer. Keep the tunnel private/short-lived, or put access control in front of it (e.g. Cloudflare Access), and revoke it when unused.
- A native HTTP transport (Neuron serving Streamable HTTP directly, no bridge) is a
possible future addition — see T15 in
TASKLIST.md. It would remove the bridge hop but still needs a public HTTPS endpoint for ChatGPT, so the bridge is the simplest path today.
| Client | Config file | Server key | Restart |
|---|---|---|---|
| OpenCode | ~/.config/opencode/opencode.json |
mcp |
/mcp reload |
| Claude Code | .mcp.json or ~/.claude.json |
mcpServers |
/mcp or restart |
| Claude Desktop | claude_desktop_config.json |
mcpServers |
app restart |
| Cursor | ~/.cursor/mcp.json |
mcpServers |
app restart |
| Cline / Roocode | VS Code global storage | mcpServers |
app restart |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
mcpServers |
app restart |
| VS Code (Copilot) | .vscode/settings.json |
github.copilot.mcpServers |
app restart |
| Zed | ~/.config/zed/settings.json |
mcp_servers |
project restart |
| Continue.dev | ~/.continue/config.json |
experimental.mcpServers |
IDE restart |
| Cody | ~/.cody/mcp.json |
mcpServers |
IDE restart |
| Amazon Q | ~/.aws/amazon-q/mcp.json |
mcpServers |
IDE restart |
| Perplexity (macOS) | in-app UI (no file) | Settings → Connectors → local MCP | re-enable connector |
| ChatGPT / OpenAI | in-app UI (no file) | Developer Mode → Connectors (HTTPS URL) | reconnect connector |
On Linux/macOS, replace cmd /c %LOCALAPPDATA%... with python3 -m neuron. ChatGPT requires a
stdio→HTTP bridge; Perplexity local MCP is macOS-only (see the subsections above).
Neuron includes a standalone chat mode (run_interactive.py) that connects directly to an LLM.
This is separate from the MCP server — it exists for testing and terminal use.
Not the production path.
run_interactive.pyis powered bysrc/neuron/engine.py, a self-contained engine that reimplements extraction/linking/flash independently ofserver.py. The two do not share code and are not kept in functional parity: the engine is versioned on its own (historically "v3.1") and may lag the server (v3.3). Editengine.pyonly for the interactive CLI; for production MCP behaviour editserver.py.
| Provider | Package | Flag | Default model | Fast model |
|---|---|---|---|---|
| Ollama (local) | ollama |
--provider ollama |
qwen2.5:14b |
qwen2.5:3b |
| OpenAI | openai |
--provider openai |
gpt-4o |
gpt-4o-mini |
| Azure OpenAI | openai |
--provider azure |
gpt-4o |
gpt-4o-mini |
| Anthropic | anthropic |
--provider anthropic |
claude-sonnet-4-5 |
claude-haiku-3-5 |
| Gemini | google-generativeai |
--provider gemini |
gemini-2.5-pro |
gemini-2.0-flash-lite |
| Compatible | openai |
--provider compatible --base-url ... |
mistral |
same as main |
# Ollama (locale, nessuna API key)
python scripts/run_interactive.py --provider ollama
# OpenAI
python scripts/run_interactive.py --provider openai --api-key sk-...
# Anthropic Claude
python scripts/run_interactive.py --provider anthropic --api-key sk-ant-...
# Compatible (LM Studio, Groq, Perplexity, DeepSeek, vLLM, LiteLLM...)
python scripts/run_interactive.py --provider compatible --base-url http://localhost:1234/v1Keys are resolved in this order (see resolve_key() in run_interactive.py):
--api-keyCLI argument- Environment variable (
OPENAI_API_KEY,ANTHROPIC_API_KEY,GEMINI_API_KEY,AZURE_OPENAI_API_KEY) ~/.neuron/config.json(saved with--save-config)
| Command | Action |
|---|---|
/neuron status |
Graph state |
/neuron summary |
Graph summary |
/neuron prune |
Prune tangential links |
/neuron flash |
Toggle semantic flashbacks |
/neuron export |
Export graph as JSON |
/neuron reset |
Clear graph |
/exit or Ctrl+C |
Exit |
git clone https://github.com/<your-user>/Neuron.git
cd Neuron
python3 -m venv .venv && source .venv/bin/activate
pip install -e .[dev]python -m neuron # Start MCP server (stdio)
python scripts/run_interactive.py # Start interactive chat
python scripts/run_interactive.py --provider ollama # Chat with local LLMFor a clean Windows machine, distribute the repository files — especially
install.cmd, install.ps1 and vendor/. The user double-clicks install.cmd,
which forwards %* to install.ps1 and propagates its exit code. Python is not a
prerequisite: the installer fetches the newest 3.14.x from python.org (per-user,
no admin) when none in 3.10–3.14 is present.
There is no compiled bootstrapper. NeuronInstaller.exe and its C# source
(installer/) were removed on 2026-07-29: the in-tree binary had gone stale
(pre-unification build, flagged in work/history/RELEASE-CHECKLIST.md), and
shipping a binary that duplicates what a 15-line .cmd already does was cost
without benefit. The four launchers — .cmd / .ps1 / .sh / .command — are
the entire distribution surface, and gray_matter/tests/test_installer_parity.py
asserts all three projects ship all four.
After installation, the supported human entry point is the Gray Matter control
center — one GUI for the whole suite (neuron gui opens it). It is not a
separate Neuron GUI: Neuron ships GM's wheel in src/neuron/_gm_vendor/ and
installs it offline on first launch, so the control center works even when Gray
Matter was never installed as a peer. scripts/run_mcp.bat remains only as a
compatibility launcher for legacy MCP registrations.
python -m compileall src/Two workflows:
.github/workflows/ci.yml(on push / PR): atestjob onwindows-latest(caches the fastembed ONNX model,pip install -e .[dev], byte-compilessrc/, runspytest tests/ -v) and abuildjob onubuntu-latestthat runspython -m buildand verifies the resulting wheel importsneuron..github/workflows/release.yml(on tagv*): builds the Windowspytursowheels (matrix 3.10–3.14), builds the Neuron wheel + sdist, and publishes a GitHub Release with all assets attached. See INSTALL.md for how those assets are consumed.
- Bump
__version__insrc/neuron/__init__.py(the single source of truth;pyproject.tomlreads it dynamically and the MCP server reports it). Follow semver: PATCH for fixes, MINOR for features, MAJOR for breaking/behavior changes. - Move the
[Unreleased]notes inCHANGELOG.mdunder a new## [X.Y.Z] - <date>heading and fill in Added/Changed/Fixed/Removed. - Run the full test suite (
scripts/run_tests.ps1) and do a clean-machine install smoke test via the Neuron Control Center → Setup / Manage. If you build the wheel/sdist locally to test, deletebuild/,dist/, andsrc/*.egg-infofirst — a stalebuild/staging dir or a cachedegg-info/SOURCES.txtcan bundle old files (CI builds from a fresh checkout, so the tagged Release is unaffected). Sanity-check the artifacts: the wheel should contain only theneuronpackage, and the sdist should not shipCLAUDE.mdor any.dbseed. - (Optional) Ship a seed: build one with
python scripts/import_vault.py(setNEURON_VAULTor pass--vault), then copy the resulting DB intosrc/neuron/data/base_knowledge.db— only a real, populated SQLite file. - Tag and push:
git tag vX.Y.Z && git push --tags.release.ymlbuilds the prebuilt PyTurso wheels and publishes the GitHub Release. - To bump
pyturso, change the pin inpyproject.tomland the version inrelease.yml'sbuild-pyturso-winjob together.
The MCP server actually used by clients runs from a separate install dir
(%LOCALAPPDATA%\Programs\neuron5), populated by install.ps1. That installer also sets up
the toolchain (Rust/MSVC), creates the venv and installs deps — heavy, and not what you want
for a quick "push my code edits to the install" loop. Use scripts/deploy.ps1 for that:
# preview the plan (source vs installed version, target venv) — no writes:
powershell -ExecutionPolicy Bypass -File scripts\deploy.ps1 -DryRun
# reinstall the current source into the install venv, then import-check it:
powershell -ExecutionPolicy Bypass -File scripts\deploy.ps1
# also run the test suite via the install venv (if it has pytest):
powershell -ExecutionPolicy Bypass -File scripts\deploy.ps1 -RunTestsIt reinstalls the package into the install's venv (pip install --force-reinstall --no-deps),
which is what python -m neuron actually imports (site-packages) — not a loose copy of src\
that nothing loads. --force-reinstall also beats the "same __version__ → pip skips" trap, so
identical-version code still lands. It never touches the install's .venv interpreter, graphs\
or logs\, verifies by importing neuron.server from the venv, and confirms the installed
__version__ matches source. This replaces hand-copying and keeps source ↔ install from drifting
between releases. (-Prune is now a no-op — a wheel reinstall already replaces stale files.)
Neuron ships as two active lines: v4.x (the stable line, master) and v5.x "Synapse"
(this branch — associative-memory engine: Hebbian links, spreading activation, drift, sleep-mode).
They are designed to coexist on one machine as separate MCP servers with isolated memory, because
v5 changed the DB schema and the default embedding model — a shared graph store would corrupt each
other's vectors. Nothing in the package needs renaming; the two lines differ only in install
identity:
| Axis | v4 | v5 "Synapse" |
|---|---|---|
| Version | 4.x |
5.x (current: 5.3.1) |
MCP server name (Server(...)) |
neuron |
neuron5 |
| Default graph store | %LOCALAPPDATA%\neuron\graphs |
%LOCALAPPDATA%\neuron5\graphs |
| Install dir (target) | %LOCALAPPDATA%\Programs\neuron |
%LOCALAPPDATA%\Programs\neuron5 |
| MCP client config key | neuron |
neuron5 |
Each install is a self-contained directory with its own venv, so import neuron never clashes — the
isolation is purely at the install/registration layer plus the distinct default store dir (baked into
_default_graphs_dir() on this branch). Register both in the client's MCP config under different keys
(neuron → v4's run_mcp.bat, neuron5 → v5's) and they appear as two independent memories. Override
either store with NS_GRAPHS_DIR if you want a custom location. See TASKLIST.md T39 for the
remaining install-layer wiring (deploy target dir + installer registration name).
v5 turns the graph from a tagged store into an associative memory. All of the below
live in src/neuron/models.py (pure graph logic) and src/neuron/server.py (wiring).
Hebbian reinforcement (E2.1). When two keywords co-occur in a turn, the link between
them accrues co_activation_count — at most once per HEBBIAN_COOLDOWN (2) turns — and its
weight is promoted tangential→medium→strong at 3 and 8. Promotion is monotone (a stale
concurrent writer can only raise it). Graph.reinforce_coactivation(), called from
store_turn/auto. Schema: links.co_activation_count.
Composite retrieval ranking (E2.2). get_context ranks nodes by
RANK_WEIGHTS = sim·0.5 + salience·0.3 + recency·0.2 (tunable) instead of weight→recency, so a
salient neighbour surfaces even without a direct vector match. Auto-consolidation protects
nodes with salience ≥ CONSOLIDATE_PROTECT_SALIENCE (8) from being merged.
Spreading activation (E2.3). Graph.spreading_activation(seeds, k=2) propagates activation
along links, each hop weighted by link_strength × (1 + salience/max) × decay. Hebbian-strong
links carry more; decay<1 and small k prevent flooding. It's the engine behind the flashes
and the pre-staged stimulus.
Unified flashes (E2.4). The three heuristics — 💤 dormant pulse, 🔗 cross-domain spark,
⚡ creative leap — now feed one selector: spreading activation scores the in-graph candidates and
only the top-2 are emitted (_build_context_window). Cross-domain stays a distinct signal
(the engine is single-graph). A future "Option B" (engine as the primary generator) is noted in
the code.
Piggyback stimulus (E2.5). store_turn and pre_turn append a compact one-line stimulus
(top spreading-activation node), capped to ~40 tokens and suppressed below
STIMULUS_MIN_ACTIVATION (no noise on a cold graph). _stimulus_block().
Cross-context drift (E3.1/E3.2). When a node from another visited context surfaces
alongside the current keywords (via the cross-domain spark), Neuron forms an implicit drift
link: born tangential, DRIFT_COOLDOWN (5), pruned after DRIFT_EXPIRY_TURNS (3) idle turns
(faster than intra-context tangentials), reinforced via the Hebbian counter. Drift stays out of
the normal views and surfaces only on get_context(depth≥3), rendered target@context. Schema:
links.target_context. Graph.form_drift_link().
Sleep-mode + pre-staging (E3.3/E3.4). On load, if a context was idle >SLEEP_IDLE_SECONDS
(30 min), Graph.sleep_maybe() (called from registry.get) consolidates (when
NS_CONSOLIDATE_AUTO) and pre-computes the top stimulus into meta.staged_stimulus. pre_turn
serves it once via take_staged_stimulus() if fresher than STAGE_FRESH_SECONDS (6h) — a warm
start that works around MCP's lack of push. Degrades to "consolidate-at-startup-if-idle" with no
external scheduler. Schema: meta.last_active_timestamp / staged_stimulus / staged_ts.
Testing each (all real-dep-free except where noted): tests/test_hebbian.py,
test_composite_ranking.py (needs mcp/fastembed), test_spreading.py, test_core.py
(flash cap), test_stimulus_piggyback.py (needs mcp/fastembed), test_drift.py, test_sleep.py.
Tuning knobs are the module constants above — calibrate on real data.
PolyForm Noncommercial License 1.0.0. See LICENSE.
Neuron keeps the graph small so search stays fast, rather than reaching for an approximate-nearest-neighbour index too early.
Consolidation. neuron consolidate (or the consolidate MCP tool) merges
near-duplicate concepts (cosine > 0.85, configurable) and archives low-salience
orphans to a recoverable _graveyard table. Enable the automatic pass by setting
NS_CONSOLIDATE_AUTO=1 — it then runs every 20 turns after a save. Merges are
salience-aware (protect_salience) so important nodes are never absorbed, and
everything is recoverable from _graveyard.
Search cost today. Vector search is a linear scan: vector_distance_cos in
SQL on the Turso tiers, a Python cosine loop on the plain-sqlite tier. Cost is
O(N) per query in the active context (search is context-scoped, so N is the
nodes of one context, not the whole store). Missing vectors are computed once and
cached/persisted (never re-embedded per search).
When to introduce an ANN index. Stay on the linear scan until a single
context regularly exceeds ~10–20k nodes and query latency becomes noticeable.
At that point add an approximate index on node_vectors (libSQL/Turso
libsql_vector_idx / DiskANN) to move from O(N) to sub-linear search. Before then
it is over-engineering: pruning + consolidation + context scoping keep N well
under that range for normal use.