velesdb-node

September 3, 2026 · View on GitHub

Local-first agent memory for Node.js: remember, recall, and explain why — in-process, no server.

npm Node License

Portability: ✅ a plain npm dependency — no MCP client, no server, no daemon, no API key · ✅ prebuilt binaries for macOS, Linux (glibc) and Windows · ⚠️ no musl prebuild, so Alpine-based images need a local build.

This crate is never published to crates.io. It compiles to a napi-rs cdylib that ships to npm as @wiscale/velesdb-memory-node, wrapping the exact same hardened Rust as the velesdb-memory MCP server and the Python binding — no logic is reimplemented here.

Objective

An agent that runs in Node forgets everything between processes, and a vector store only hands back text that looks like the question. Neither can answer "why is this value 7?", because the answer is usually a fact that shares no words with the question — the customer constraint behind the constant, the incident behind the config.

This addon gives a Node agent durable memory that never leaves the machine. It remembers facts, recalls them semantically, connects them with typed links, and walks those links to return the evidence trail behind an answer. It also carries the deterministic context compiler, which shrinks a prompt under a hard token budget with no model call at all.

recall() finds the booking but misses the reason; why() reaches it through typed links, across a session restart

The store is on disk, so memory survives process restarts: a new session reopens it and why() still walks the graph to context that shares no words with the question.

What you actually gain

Two problems cost you real money and real quality every day, and this addon fixes both — in your own Node process, with no service to run.

Your agent forgets. Close the process and everything it learned is gone. The store is a directory on disk, so a new process reopens it and the facts are still there.

Every turn re-sends the whole conversation. That is what you are billed for, and a context padded with repeated logs is also one where the model attends less to what matters. compileContext shrinks that payload before you send it — deterministically, and without calling any model itself.

What improvesMeasuredHow it was measured
Context sent to the model82.5 % smaller over a 12-turn coding session (80.8–87.4 % per turn as it grows)committed corpus, real cl100k tokenizer — every turn compiled twice, byte-identical
Compile cost0.7 ms stateless, 24.5 ms with source/event persistence onsame run
Storing a memoryzero AI calls — nothing leaves the processthe write path never calls a model
Prompt-cache prefixbyte-stable across all 12 turns (45 tokens reusable)same run

Those percentages come from our corpus. Every figure is pinned to its committed source by a contract the CI enforces: if one drifts from what the code produces, the build goes red.

How it works, in four steps

Everything below runs in-process. No server, no network, no API key.

1. It stores facts, not transcripts. remember takes one fact — "the API port is 6333 because 3000 collided with the web UI" — and writes it to a local directory. No model call.

2. It finds them by meaning. recall matches on sense, so "which port did we settle on" reaches that fact although the words differ.

3. It connects them — the part a search engine cannot do. Facts are linked to the topics they mention, and why walks those links: it returns the best match plus the facts that explain it, including ones sharing no words with your question. That is what the GIF above shows across a restart.

Those links have to exist. If you only ever call remember, the graph stays flat and why degrades to a search. rememberExtracted takes a paragraph, splits it into facts and wires the links for you.

4. It compresses what is too big. compileContext takes your accumulated context and a token budget, and returns a compiled view with one auditable decision per fragment — kept, abstracted, or dropped — plus a handle to fetch any original back. Nothing is destroyed; retrieveContextSource returns the exact bytes.

Use cases

  • A Node or TypeScript coding agent that must still know, three weeks and several processes later, why a timeout is 7 seconds — and can show the constraint it came from.
  • An Electron or CLI tool that needs memory without asking the user to install, run and secure a database service.
  • Regulated or air-gapped work where context cannot transit a third-party LLM API, and "show why it recalled that" has to be answerable.
  • A long agent session about to blow its context window: compile the prompt under a budget instead of summarizing and restarting.

Prerequisites

RequirementMinimum versionNote
Node.js18.17The package engines.node floor. CI builds and tests on Node 20.
A supported platformmacOS (arm64/x64), Linux glibc (x64/arm64), Windows x64 — see Compatibility.
Rust1.90Not needed to install. Only to build the addon yourself: building from source.
OllamaanyOptional. Only for embedder: "ollama" and rememberExtracted. The default embedder is offline and dependency-free.

Installation

npm install @wiscale/velesdb-memory-node

That downloads a prebuilt binary; nothing is compiled on your machine, and no Rust toolchain is involved. Unsupported platform, or working on the binding itself? See building the Node addon from source.

First success in 60 seconds

Save this as first.mjs in a project with "type": "module", then run node first.mjs:

import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { MemoryService } from '@wiscale/velesdb-memory-node'

// Offline "hash" embedder by default: no model, no network, no API key.
const store = MemoryService.open(mkdtempSync(join(tmpdir(), 'velesdb-')))

// An earlier session recorded a decision and the human reason behind it.
const reason = await store.remember(
  'field crews work from remote mining sites over satellite links',
)
await store.remember('the default HTTP request timeout is 7 seconds', [
  { target: reason, relation: 'because' },
])
await store.remember('the vector index uses HNSW with M=16')

const question = 'why is the request timeout 7 seconds?'

console.log('recall — vector similarity only:')
for (const hit of await store.recall(question, 2)) {
  console.log(`  ${hit.content}`)
}

console.log('why  — vector seed + graph of typed links:')
const { nodes, edges, truncated } = await store.why(question)
for (const node of nodes) console.log(`  hop ${node.hop}  ${node.content}`)
console.log(`  ${edges.length} typed edge(s) walked`)
if (truncated) console.log('  explanation truncated')

Expected output, exactly:

recall — vector similarity only:
  the default HTTP request timeout is 7 seconds
  the vector index uses HNSW with M=16
why  — vector seed + graph of typed links:
  hop 0  the default HTTP request timeout is 7 seconds
  hop 1  field crews work from remote mining sites over satellite links
  1 typed edge(s) walked

That is the whole product in eight lines of output. recall never surfaces the satellite-link constraint — it shares no words with the question, so vector similarity ranks an unrelated HNSW note above it. why() follows the because edge and reaches it at hop 1, so your agent warns you before you "round 7 down" and cut off the customer.

Failure looks unmistakable: Failed to load native binding means no prebuilt binary matched your platform (Troubleshooting), and a why section with only hop 0 means the typed link was not stored — check that reason was passed as { target, relation }.

Configuration

There are no environment variables. Everything is an argument to the factory:

ArgumentDefaultEffect
pathDirectory of the on-disk store. Created if missing; reopened on the next run.
embedder"hash""hash" is offline and deterministic; "ollama" gives real semantic recall.
ollamaUrl"http://localhost:11434"Only with embedder: "ollama".
ollamaModel"all-minilm"Only with embedder: "ollama".
const store = MemoryService.open('./agent_mem', 'ollama')

A store is fixed to one embedder — the vector dimension is decided when the store is created — so use a separate directory when you switch.

Examples

examples/why_magic_constant.mjs is the runnable version of the demo above, scaled to 14 memories so the blindness of plain recall is unmistakable. From this directory, after a build:

node examples/why_magic_constant.mjs

The same wedge in the other bindings is listed in the velesdb-memory README.

API

21 methods on one class, in three families:

FamilyMethods
Durable memoryremember, recall, recallWhere, recallFused, recallFusedDated, relate, unrelate, forget, why, entity, feedback, rememberExtracted
Context compilercompileContext, compileTranscript, explainCompilation, contextSavings, retrieveContextSource, suggestBudget
Session resumptionsaveWorkingContext, loadWorkingContext, listWorkingContexts

loadWorkingContext resolves the {found, working, other_sessions} envelope the MCP tool serves — breaking in 0.12.0, where it used to resolve the bare working context or null; read .working for that value, and .other_sessions to tell a genuine fresh start from a typo in session.

Three contracts hold across all of them: every method returns a Promise and runs off the event-loop thread; every id crosses as a decimal string (a JS number loses precision above 2532^{53}); every rejection is an Error whose message starts with [INVALID_INPUT], [NOT_FOUND] or [INTERNAL].

Parameter types are generated into index.d.ts and shipped with the package — read them from your editor. Everything else (per-method semantics, the compiler surface, media fragments, working contexts, resource caps) is in the Node addon guide.

Bundled agent skills

Wiring the API gives your agent the methods; it does not tell it when to use them. Three skills ship inside the package for that — velesdb-memory (the recall → remember → relate → why → feedback loop), velesdb-context-optimizer (the compression workflow, including when not to compress) and velesdb-learning-loop (the discipline that makes those two compound: recall before designing, check recurrence before storing a fix, and never write to memory by reflex):

cp -r node_modules/@wiscale/velesdb-memory-node/skills/velesdb-memory ~/.claude/skills/
cp -r node_modules/@wiscale/velesdb-memory-node/skills/velesdb-context-optimizer ~/.claude/skills/
cp -r node_modules/@wiscale/velesdb-memory-node/skills/velesdb-learning-loop ~/.claude/skills/

That cp is a snapshot, not a live link: re-run it after every npm update. Details in the Node addon guide.

Need the full engine?

This addon is the memory wedge, by design and by license: memory semantics only. It exposes no raw VelesQL, no deep graph MATCH, no collection administration — a test pins the prototype allowlist and asserts query, upsert, createCollection and traverse are absent.

For the full engine from Node, run the REST server and talk to it with @wiscale/velesdb-sdk; the runnable two-step recipe is in the Node addon guide.

Known limits

  • Memory semantics only. No database-shaped API, ever — see above.
  • One process per store. The store takes a single-writer lock, so a second MemoryService.open on the same directory fails while the first is alive.
  • A store is fixed to one embedder. The dimension is set at creation.
  • No path fragment ingestion. The MCP server can read a file by reference under an allowlist; this binding has no such configuration surface. Read the file yourself and pass its content.
  • Bring-your-own-links by default. The graph comes from relate and links; automatic extraction needs rememberExtracted and a local model.
  • No musl prebuild, so Alpine images must build the addon themselves.
  • Not on crates.io. publish = false: the artifact is the npm package.

Compatibility

Prebuilt binaries, one per target declared in package.json napi.targets:

PlatformTarget tripleStatus
macOS, Apple siliconaarch64-apple-darwinPrebuilt, load-smoke-tested in CI
macOS, Intelx86_64-apple-darwinPrebuilt, cross-built (no native runner to smoke-test on)
Linux x64, glibcx86_64-unknown-linux-gnuPrebuilt, load-smoke-tested in CI
Linux arm64, glibcaarch64-unknown-linux-gnuPrebuilt, cross-built
Windows x64x86_64-pc-windows-msvcPrebuilt, load-smoke-tested in CI
Linux musl (Alpine)*-unknown-linux-muslNot shippedbuild from source
RuntimeStatus
Node.js 18.17+Supported (engines.node)
Node.js 20The version CI builds and tests on
Bun / DenoUntested — Node-API support exists in both, but nothing here verifies it

Troubleshooting

SymptomCauseFix
Failed to load native bindingNo prebuilt binary matches this platform — most often Alpine/musl. There is no source fallback.Build the addon from source, or use a glibc base image.
[INTERNAL] storage error: [VELES-031] Database is already opened by another process: <path>The single-writer lock is held — a second MemoryService on the same directory.Keep one instance per store, or give the second one its own path.
[NOT_FOUND] memory 999999999 does not exist on relate / feedbackThe id was rounded by JS number arithmetic, or came from a different store.Never convert an id with Number(); pass the decimal string through verbatim.
[INVALID_INPUT] invalid id 'not-an-id' (expected a decimal u64 string)A non-numeric value reached an id argument (often an object or undefined).Pass the exact string remember resolved to.
[INTERNAL] extraction error: ... ollama request failed: ... Connection refusedrememberExtracted needs a running Ollama, whatever embedder the store uses.Start Ollama and pull the model, or use remember with explicit links.
EPERM / EBUSY deleting a store directory on WindowsThe velesdb.lock file is still held; the release finalizer is not deterministic.Retry the delete, or drop the directory on the next run.

License

VelesDB Core License 1.0 (source-available, based on ELv2). See LICENSE — a local copy, so npm bundles it into the published package and each per-platform sub-package.

Running this addon inside your own application, where your users only ever receive results, is the license's expressly-permitted embedded, local-first use. What it forbids is re-hosting VelesDB as a multi-tenant service where third parties drive the database — which this package makes impossible by construction: memory semantics only, and it is a library, not a service. Questions: contact@wiscale.fr.


velesdb-node v0.14.2 (npm @wiscale/velesdb-memory-node@0.14.2) · Last updated: 2026-09-03 · Applies to: velesdb-core 6.0.0 · Report a docs error