nodum¶
A DB-native knowledge graph: knowledge is a typed graph of nodes in one
SQLite file — not files with an index on top. Every mutation flows through a
deterministic, LLM-free service layer that validates, enforces a
proposed → active → archived state machine, and appends to an event log with
full before/after payloads — so every change is versioned, auditable, and
reversible.
Why one file¶
The whole graph — nodes, edges, versions, the event log, derived indexes, and even binary assets — lives in a single SQLite database. There is no server to run, no daemon, no external index to keep in sync, and no API key. Copying the file copies the knowledge base.
What makes it agent-native¶
nodum assumes both humans and agents write to it, and gives them different privileges enforced at the service layer, not by convention:
- A human write lands
activeimmediately. - An agent write lands
proposedwhen its grant on the space issuggest, andactivewhen its grant isedit. - Anything that retires or rewrites live state (
accept,reject,archive, everyreviewsubcommand, and the curative tier —merge-nodes,retype,supersede-edge,bulk-relink,consolidate) is limited to a human — or, in-space, to an agent holdingedit.undoandrollbackare human-only.
An agent edit does not overwrite: it stages a proposed version recording
which fields it named, so accepting it applies only those fields to the node
as it stands then. A human edit made while the proposal waited is not reverted.
Surfaces¶
The same service layer sits behind every surface, so they cannot drift:
| Surface | For | Entry point |
|---|---|---|
| CLI | humans, scripts | nodum … — one JSON object per command |
| HTTP API | the web UI | nodum serve |
| MCP server | external agents | nodum mcp serve — agent token in NODUM_AGENT_TOKEN |
| Web UI | humans | served by nodum serve |
The MCP server exposes the read and additive tool tiers and nothing else — an agent can propose, never dispose.
Search¶
Two derived indexes are projected from the event log, each with checkpoint and rebuild mechanics: an FTS5 full-text index and a sqlite-vec chunk embedding index (a local in-process model — no daemon, no API key). Hybrid search fuses BM25 and vector results by reciprocal rank fusion, then re-ranks by graph expansion.
The embedding model is an optional extra; without it, search falls back to
BM25 keyword ranking. An ingested document's full extracted text is joined onto
the asset_ref node that stands for its bytes, so BM25 reaches every word of a
long PDF while its per-page blocks keep their own precision.
Status¶
Phase 1 (core) landed: schema and migrations, the service layer, the event log with versions and undo, Markdown-as-truth content, wikilink materialization, and the JSON-emitting CLI.
Phase 2 (agent-native) landed: projectors and the two derived indexes,
hybrid search, principals, spaces, and per-(agent, space) grants (read/suggest/edit),
the review/accept API, proposed updates, the MCP server, and content-addressed
assets with lazily generated thumb/preview renditions (agents get
renditions, never originals).
Phase 3 (human UI) landed: nodum serve runs the HTTP API — the human
surface, where every write is attributed to the session's human and no request
field can say otherwise — and serves the web UI from the same process: a Markdown
editor, hybrid search, the review queue, a graph view, an asset browser, an
accounts-and-grants admin view, and per-node version history.
Phase 4 (ingestion) landed: nodum ingest turns a file, a folder, or a URL
into a reviewable subgraph — an asset_ref node for the bytes, a source node
holding the extracted text, and one block per page. Text extraction runs
through optional per-format handlers (PDF, image OCR, audio), and a missing one
is reported rather than fatal. Also: page:<n> PDF page rasters, and
short-lived, single-use capability URLs for agent hosts that share no
filesystem with the graph.
Phase 5a (the gardener's spine) landed: the graph maintains itself. A
consolidation cycle groups a run of writes under one id, so nodum rollback
can take the whole of it back — all of it, or none of it, and it refuses rather
than clobbers when the graph has moved on. The gardener is an internal agent
with ordinary grants that runs four deterministic jobs (duplicate candidates,
link pruning and inference, housekeeping, a neglect report) and files what it
infers in the review queue rather than asserting it. A curative tier
(merge-nodes, retype, supersede-edge, bulk-relink) changes structure
rather than adding to it, each operation inside a cycle. A dream journal
(nodum cycle-list / cycle-get, and a view in the web UI) says what ran, who
asked, what it measured and what it changed. Three verbs act on an entry and
they are not interchangeable: nodum cycle-stop <id> asks a run that is still
going to wind down, nodum cycle-abandon <id> is the way out of a run a crash
left open — since a cycle that never closed cannot be rolled back and its events
cannot be undone either — and nodum rollback <cycle-id> is the only one of the
three that reverses a write. Cycles run on demand, and
nightly when NODUM_CONSOLIDATE_AT says so — unset means off.
Still to come: claim proposals and the gardener's LLM half — everything that needs a judgement rather than arithmetic.