How the store is shaped, and how you insert, query, update, and delete — from Rust, the CLI, or HTTP. There are no tables, no schema, and no SQL. You write facts in plain language and ask for them back by meaning.
Two ideas, that's the whole model:
- A fact is a short sentence of plain text:
"the api key is zeta-9931". When you store it, neuron-db picks the surprising word as the retrievable value (zeta-9931) and indexes the rest as cues. - A scope (also called a neuron) is a named bag of facts, addressed by an id string
like
user:42orsession:abc. Scopes are isolated from each other. ANeuronDBis a file full of scopes.
If you come from SQL, the rough mental map is: a scope is like a row keyed by its id, and
its facts are the columns — except you never declare them. You just state things, and ask
questions. Retrieval is associative (cue overlap), not exact-key, so "what is the api key?" finds the fact above without you naming a column.
NeuronDB (one .db file)
├── scope "user:42" ── facts: "the plan is pro", "the region is us-west-2", ...
├── scope "user:43" ── facts: "the plan is free", ...
└── scope "team:ops" ── facts: "the on-call is Dana", ...
| intent | SQL-ish | neuron-db |
|---|---|---|
| insert | INSERT |
observe(scope, "the plan is pro") |
| read (one value) | SELECT … LIMIT 1 |
get(scope, "what plan?") -> "pro" |
| read (with metadata) | SELECT * |
recall(scope, "what plan?") -> {value, fact, coverage} |
| "update" | UPDATE |
just observe again — newest wins; or reinforce (plastic tier) |
| delete | DELETE |
forget(scope, "plan") (by substring) or forget(scope, None) (all) |
| converse | — | turn(scope, "my plan is pro") → stores or answers, one call |
| encrypted put/get | — | SecureNeuronDB.put/get(scope, secret, …) |
There is no UPDATE because facts aren't rows you mutate. To change an answer, state the new
fact — recall prefers the most recent matching fact. To make a fact "stick" so it wins over
newer ones, use the plastic tier's reinforce.
Add the crate (path or git) and enable the sqlite feature for the durable NeuronDB:
[dependencies]
neuron-core = { path = "rust/neuron-core", features = ["sqlite"] }use neuron_core::db::NeuronDB;
let db = NeuronDB::open("app.db", 500); // file path, max facts per scope
// INSERT
db.observe("user:42", "the plan is pro");
db.observe("user:42", "the region is us-west-2");
// READ — one value, or the full hit
let plan = db.get("user:42", "what plan?"); // Some("pro")
let hit = db.recall("user:42", "where is the region?"); // Some(Recall{ value, fact, coverage, .. })
// "UPDATE" — newest matching fact wins
db.observe("user:42", "the plan is enterprise");
assert_eq!(db.get("user:42", "what plan?").as_deref(), Some("enterprise"));
// DELETE — by substring, or everything in the scope
let (forgot, remaining) = db.forget("user:42", Some("region")); // drop region fact
db.forget("user:42", None); // wipe the scope
// CONVERSE — store or answer in one call
let t = db.turn("user:42", "my color is teal"); // t.kind == "ack"
let a = db.turn("user:42", "what is my color?"); // a.kind == "recall", a.reply == "teal."
// INTROSPECT
let s = db.stats("user:42"); // { facts, max_facts, created, updated, turns }
let ids = db.neurons(); // every scope id, most-recently-updated firstFor a cache or a unit test, skip SQLite entirely (default features, no deps):
use neuron_core::Neuron;
let mut n = Neuron::new(500);
n.observe("the wifi password is hunter2");
n.recall("what is the wifi password?").unwrap().value; // "hunter2"Facts gain strength on recall, fade when ignored, and wire together when recalled together.
use neuron_core::plastic::PlasticNeuron;
let mut n = PlasticNeuron::new(10_000_000, Some(200.0), 3); // max_facts, half_life, link_window
n.observe("the meeting is on monday");
n.observe("the meeting is on friday");
n.recall("when is the meeting?"); // "friday" (recency)
let id = n.base.episodes.iter().find(|e| e.t.contains("monday")).unwrap().id;
for _ in 0..4 { n.reinforce(id, 1.0); }
n.recall("when is the meeting?"); // "monday" (adapted to use)
n.recall_spreading("when is the meeting?", 2, 10, 0.6, 6); // neurotransmitter-style, multi-hopOne scope recalls best when it's small. To hold a lot, shard:
use neuron_core::router::NeuronRouter;
let mut r = NeuronRouter::new(128); // facts per shard
for f in facts { r.observe(&f); } // auto-spills into new shards
r.get("what is the north gate code?"); // fans out, returns the single best valueValues are AES-256-GCM ciphertext; the index is a keyed hash; the per-scope secret is
supplied per call and never stored. A stolen .db file is opaque.
use neuron_core::secure::SecureNeuronDB;
let v = SecureNeuronDB::open("vault.db");
v.put("alice", "alice-secret", "wifi password", "hunter2").unwrap();
v.get("alice", "alice-secret", "what is the wifi password?"); // Some("hunter2")
v.get("alice", "WRONG", "what is the wifi password?"); // NoneBuild with ./build.sh (or cargo build --release --features "sqlite secure server"), then:
neuron --db app.db observe user:42 "the plan is pro" # INSERT
neuron --db app.db get user:42 "what plan am i on?" # -> pro
neuron --db app.db recall user:42 "what plan?" # value + fact + coverage
neuron --db app.db turn user:42 "my color is teal" # store or answer
neuron --db app.db forget user:42 "plan" # DELETE facts containing "plan"
neuron --db app.db stats user:42
neuron --db app.db list # all scope ids
neuron --db app.db --json get user:42 "what plan?" # {"value":"pro"}
# encrypted tier (the secret is your "login" for that scope)
neuron --db vault.db --secret s3cr3t secure-put alice "wifi password" hunter2
neuron --db vault.db --secret s3cr3t secure-get alice "what is the wifi password?"The db path comes from --db or $NEURON_DB (default neurons.db). The secret comes from
--secret or $NEURON_SECRET.
Start the server (neuron serve 8088, the serve binary, or Docker). Set NEURON_DB_KEY
to require Authorization: Bearer <key> on every request.
GET / -> { service, endpoint }
GET /v1/{scope} -> stats (auth)
POST /v1/{scope} {message} -> { reply, kind, wrote, facts } (turn: store or answer)
POST /v1/{scope}/get {query} -> { value }
POST /v1/{scope}/recall {query} -> { value, fact, coverage } # for context gating
POST /v1/{scope}/recall_many {query,k} -> { hits:[{value,fact,coverage}] } # top-k memory block
POST /v1/{scope}/observe {facts:[...]} -> { wrote } # batch ingest (amortized save)
GET /healthz -> { ok }
GET /metrics -> { requests, uptime_s }
POST /v1/{scope}/forget {match} -> { forgot, remaining }
curl -d '{"message":"the api key is zeta-9931"}' localhost:8088/v1/user:42
curl -d '{"query":"what is the api key?"}' localhost:8088/v1/user:42/get
curl -H "authorization: Bearer $NEURON_DB_KEY" \
-d '{"match":"api key"}' localhost:8088/v1/user:42/forgetSQLite is embedded — a file, not a server, so there is nothing to log into. Control access at the layer that fits:
- Local file — OS filesystem permissions on the
.dbfile. - Over the network — run the HTTP server and set
NEURON_DB_KEY; clients send a bearer token. Without the env var, auth is off (fine behind your own gateway). - Per-secret encryption —
SecureNeuronDB: each scope's data is encrypted under a secret the caller supplies every time and that is never written to disk. Lose the secret, lose the data; that's the point.
NeuronDB is one SQLite file in WAL mode behind a process-wide lock, so concurrent threads
are safe (writes serialize). Each write persists the scope immediately. See docs/guide/DEPLOY.md
for running it as a service.