An experimental database kernel · OCaml · AGPLv3

Relations,
not tables.

RNT is a database kernel modeled on Windows NT. The kernel speaks no query language. It manages objects — relations, tuples, branches, sessions, evaluators — and exposes them through paths, handles and protocols. Languages live on top as environment subsystems.

The RNT mark: a red cone, a violet sphere and a yellow cube
Architecture

One kernel,
many personalities.

Win32, POSIX and OS/2 once sat side by side on the NT native API. RNT does the same for data: each subsystem translates its own language into kernel operations, and they all share the same objects.

Native API — paths · handles · protocols ≈ ntdll

Kernel

≈ executive

Object manager

One tree. Every relation, branch, session and evaluator has a path.

Protocols

Directory, Enumerable, Cursor, Evaluator — and any you declare.

Evaluators

\evaluator\fol
\evaluator\algebra
\evaluator\psm

Branches

Content-addressed tuples in prolly trees. Sessions pin snapshots.

Driver interface — narrow dispatch tables ≈ HAL

Drivers

≈ device drivers

Storage LMDB ships today

Where tuples live. One driver per dataset, several active at once.

LMDB · SQLite · etcd

Compute planned

Multi-way joins and Soroban's array lines on the GPU, handed off by an evaluator.

Vulkan compute

Network planned

Relations and evaluators served to the web.

HTTP hosting

Yours

Anything an evaluator should delegate. The kernel never asks what is behind the table.

the sky is the limit

Subsystems — 12 items

Bring your own language.

An evaluator is just another object that implements the Evaluator protocol. Each subsystem below is a different answer to the question “what should talking to relations feel like?”

Sakura

Purely relational

A semantic-relational algebra engine after Codd’s RM/T. Relations carry both intension and extension. Ephemeral relations remember their provenance and lineage, so a derived relation stays updatable.

(CreateRelation (name "scores")
  (schema ((player "string") (pts "integer"))))
(CreateBranch (name "v1"))
(InsertTuple (relation "scores")
  (attributes ((player (Str "Alice"))
               (pts (Int 10)))))
(Checkout "v1")
(Base scores)
Branches · merges · constraints More about Sakura →

pureSQL

SQL, executed relationally

ISO/IEC 9075 SQL, read purely. Whatever yields nulls or duplicates does not exist. A query becomes a tree of ephemeral views, each the base of the next, and only the outermost is ever enumerated.

SELECT SNO, COUNT(*) AS PARTS, SUM(QTY) AS TOTAL
  FROM SP
 GROUP BY SNO
HAVING SUM(QTY) > 500
 ORDER BY TOTAL DESC;
No NULL · no DISTINCT · SQL/PSM routines · TCP :7778 More about pureSQL →

Forth

A machine made of relations

The Core word set of Forth 2012, on a machine with no state but five relations: STACK, RSTACK, MEMORY, WORDS and REGISTERS. Every word yields a successor state, so a line is atomic and UNDO steps back.

> : SQUARE ( n -- n*n ) DUP * ;
ok
> 7 SQUARE .
49 ok
> 1 0 /
error -10: division by zero
> .S
<0> ok
Atomic lines · UNDO · CATCH / THROW · TCP :7779 More about Forth →

Karuta

Logic programming on the BEAM

A relational programming language for the BEAM, in the line of Prolog and miniKanren. State facts, write rules, ask questions. The compiler, written in OCaml, emits BEAM assembly, and a small runtime gives Erlang logic variables and backtracking.

append[[], [], []].
append[[], [H | T], [H | T]].
append[[H | T], [], [H | T]].
append[[H | Tail], [HS | TS], [H | Mid]] :-
    append[Tail, [HS | TS], Mid].

append[[1, 2], [3, 4], Out]?
Facts · rules · questions · compiles to BEAM More about Karuta →

Sol

Programs that prove they halt

A total relational language, closer to a proof assistant than to a scripting language. Sol admits a program only with a proof that it terminates. Keyword messages come from Smalltalk, rules and unification from Prolog, and the checker that judges the proofs is itself proven in Rocq.

Rule name: "an empty list has length zero"
     head: [Length list: #() result: 0]
     body: [True]

Rule name: "a cons adds one"
     head: [Length list: #(H | T) result: R]
     body: [Length list: T result: P] :and
           [Plus left: 1 right: P result: R]

Query expression: [Length list: #(1 2 3) result: R]
Total · termination proofs · checker proven in Rocq More about Sol →

Dandelion

A graph database over relations

A graph language where everything is a stem: a subject, a link and an object, ended by a period. Declarations, rules and questions are all bundles of stems, so a Dandelion program is itself a graph — one you can query with Dandelion.

Supply:
  S   key   SNO.
  P   key   PNO.
  SP  from  SNO.
  SP  to    PNO.

? who ships a part that S1 ships:
  'S1'  SP     _part.
  _s    SP     _part.
  _s    isnt   'S1'.
  _s    SNAME  ?NAME.
Stems · bundles · closures · programs are graphs More about Dandelion →

Aleph

Temporal logic over every commit

A temporal modal logic for asking about time and constraining it. A branch’s commits are the moments of a timeline that never has to end; Aleph asks what was true across them, refuses commits that would break the past, and keeps track of promises about the future until they are kept.

-- a shipped part must have existed when it shipped
require parts-exist:
  always (SP(sno: s, pno: p) -> once P(pno: p))

-- every order ships within ten commits of being placed
promise orders-ship:
  always (ORDER(id: o) -> eventually<=10 SHIPPED(id: o))

ask once S(sno: s, city: 'Paris') at main.
Once · since · always · eventually · promises are eggs More about Aleph →

Soroban

Whole columns at a time

An array language for the numeric work queries are bad at. Soroban reads a relation’s attributes as columns, applies every operation to a whole column at once, and hands the result back as a relation. Expressions read right to left, as in APL, and there is no loop to write.

-- how much each supplier ships, as a share of the total
qty:   SP.QTY
total: +/ qty
SHARE: (+/ qty by SP.SNO) % total

-- the shipments above average
big: SP where qty > avg qty
Columns · reduce and scan · history as an axis · fused on the GPU More about Soroban →

Shoji

Ask by filling in the blanks

Query by example for RNT, and the only subsystem with no syntax to learn. Shoji opens a relation as an empty skeleton, a window with its heading and a blank row, and you ask a question by filling the blanks in with what an answer would look like.

-- which suppliers in London ship part P2?
S  | SNO | SNAME | CITY
   | _s  | P.    | London

SP | SNO | PNO
   | _s  | P2

= | SNAME
  | Smith
  | Clark
Skeletons · example elements · P. I. U. D. · no grammar to learn More about Shoji →

Tangram

Find the tuples that fit

A solver. Every other subsystem asks which tuples are there; Tangram asks which tuples could be. You say what may be chosen, what is forbidden and what is preferred, and Tangram searches for a set of tuples that fits, then commits it to a branch, where you can look at it before anything is merged.

-- every shift gets two people from the staff
choose ROTA(shift: s, person: p)
  for each SHIFT(id: s)
  exactly 2 from STAFF(name: p).

-- nobody works two shifts on the same day
forbid ROTA(shift: a, person: p) and ROTA(shift: b, person: p)
   and SHIFT(id: a, day: d) and SHIFT(id: b, day: d) and a != b.

-- juniors take as few nights as possible
prefer fewest ROTA(shift: s, person: p) and NIGHT(id: s) and JUNIOR(name: p).

solve into rota-week-41.
Choose · forbid · prefer · every answer is a branch More about Tangram →

Kintsugi

Mend schemas across branches

A language for changing a schema without breaking what depends on it. A Kintsugi lens describes a change step by step, and every step runs both ways: forward, from the old shape to the new one, and back, carrying edits made in the new shape to the old one. Branches on different versions read each other through the lens, and merge through it.

lens names-split for S from v1 to v2:
  rename SNAME to NAME
  split NAME on ' ' into FIRST, LAST
  add COUNTRY default 'UK'
  drop STATUS into S_STATUS.
Lenses · forward and back · nothing lost · merges across versions More about Kintsugi →

Omikuji

Every tuple has its fortune

A probabilistic subsystem. Omikuji lets a tuple be likely instead of true: each uncertain tuple carries a chance, and a question returns its answers together with how likely each one is, across every world the uncertain tuples could make.

-- each link is up as often as it has been
weigh LINK(src: a, dst: b) by UPTIME(src: a, dst: b, ratio: r) as r.

-- how likely is the edge reachable from the core?
ask chance REACH(origin: 'core-1', dest: 'edge-9').

-- and if the link to core-2 is down?
ask chance REACH(origin: 'core-1', dest: 'edge-9')
  given not LINK(src: 'core-1', dst: 'core-2').
Weighted tuples · possible worlds · exact or drawn · given More about Omikuji →
Applications — 5 items

Many languages,
one program.

On NT, Notepad is a Win32 program and ls was a POSIX one: applications sit above the subsystems and borrow their personalities. RNT has the same top layer, with one difference. An NT program belongs to one subsystem; an RNT application can use several at once, because they all meet in the same object tree.

An application has no language of its own. It is a package of relations, routines and constraints under \application, installed by merging a branch and removed by reverting it. Four games and a package manager show the idea: in each one the state is relations, a change is a commit, and taking it back is checking out the commit before.

Chess

Moves in Karuta, memory in Aleph

Uses Karuta Aleph

The board is a relation, PIECE (SQUARE, KIND, SIDE). Karuta generates the legal moves, the way Prolog has always played chess, and Aleph remembers what a position cannot: castling rights, en passant and the fifty-move rule. Positions are content-addressed, so a threefold repetition is the same hash turning up for the third time.

knight[[F, R], [F2, R2]] :-
    jump[DF, DR],
    karuta:plus[F, DF, F2], karuta:plus[R, DR, R2],
    board[F2, R2].

jump[1, 2]. jump[2, 1]. jump[-1, 2]. jump[-2, 1].
jump[1, -2]. jump[2, -1]. jump[-1, -2]. jump[-2, -1].
\application\chess

Go

Groups are closures

Uses Dandelion Soroban

Every point is a tuple of POINT (AT, COLOUR), and the grid is a relation of neighbours. A group is a Dandelion closure over neighbours of the same colour, its liberties are the empty points it touches, and a group left without any is taken off in the same commit. Ko is a hash the game has seen before, and Soroban sums influence across the board for the score.

ally:
  ?A  NEXT    ?B.
  ?A  COLOUR  _c.
  ?B  COLOUR  _c.

? liberties of the group at D4:
  'D4'    ally*   _s.
  _s      NEXT    ?POINT.
  ?POINT  COLOUR  'empty'.
\application\go

Shogi

Captured pieces change sides

Uses Aleph Tangram

A capture moves a tuple from PIECE into the captor’s HAND, and a drop moves it back onto the board, on the other side. The rules that make shogi shogi are requirements the kernel checks on every move, and tsume problems, mate in n from a given position, are a search Tangram runs into one branch per solution.

-- nifu: never two unpromoted pawns of one side on a file
require nifu:
  always not (PIECE(at: a, kind: 'pawn', side: s, file: f)
          and PIECE(at: b, kind: 'pawn', side: s, file: f) and a != b)
\application\shogi

Mahjong

Four sessions, four hidden hands

Uses Omikuji Karuta

Four players are four sessions, and each sees its own hand and nobody else’s: hidden information is just a question of who may read which tuples. Karuta decides whether a hand is complete, four sets and a pair, and Omikuji says how likely it is that the tile you are waiting for is still in the wall.

-- an unseen tile is equally likely to be anywhere unseen
weigh WALL(tile: t) by UNSEEN(tile: t, copies: n, hidden: h) as n / h.

-- is my wait still live?
ask chance WALL(tile: '5-pin').
\application\mahjong

Tsumiki

Packages, the Nix and Guix way

Uses Sakura Dandelion Sol

A purely functional package manager, in the line of Nix and Guix. A package is a value: its inputs and its build recipe, hashed together, so the store is content-addressed exactly as RNT’s tuples already are. Recipes are written in Sakura’s Lisp, as Guix writes them in Scheme, and Sol proves that evaluating them ends. A runtime closure is a Dandelion DEPENDS*, a profile generation is a commit, rolling back is checking out the one before, and garbage collection removes whatever no root reaches.

(Package (name "hello") (version "2.12.1")
  (source (Fetch (url "mirror://gnu/hello/hello-2.12.1.tar.gz")
                 (hash "sha256-jZkUKv2S…")))
  (inputs (gcc make))
  (build gnu-build))

(Install (profile "default") (package hello))
\application\tsumiki
\ — object tree

Name · served by

  • ▾ \relation
    • S · P · SP pureSQL
    • DEE · DUM pureSQL
    • STACK · RSTACK Forth
    • MEMORY · WORDS Forth
    • REGISTERS Forth
    • EGG Aleph
    • PANE Shoji
    • SOLUTION Tangram
    • LENS Kintsugi
    • FORTUNE Omikuji
  • ▾ \evaluator
    • fol kernel
    • algebra · psm pureSQL
    • array Soroban
    • solve Tangram
  • ▸ \routine
  • ▸ \session
  • ▸ \branch
  • ▾ \application
    • chess · go application
    • shogi · mahjong application
    • tsumiki application

Subsystems meet in the tree, never in each other.

One host initializes RNT once and starts every subsystem on the same root. Forth binds its machine under \relation, so SQL can watch a running Forth — every line it runs is a new state of what SQL sees.

-- from pureSQL, reading the Forth data stack
SELECT DEPTH, CELL FROM STACK ORDER BY DEPTH DESC;
SELECT NAME FROM WORDS WHERE IMMEDIATE <> 0;
\ from Forth, reading pureSQL's shipments
S" SP" OPEN-RELATION THROW  VALUE SHIPMENTS
CREATE ROW 3 CELLS ALLOT
: TOTAL ( -- n )
  0 BEGIN ROW SHIPMENTS READ-TUPLE THROW
  WHILE ROW 1 CELLS + @ + REPEAT ;
Kernel — read me first

A small runtime you can read end to end.

Most of what a database hard-codes — what an object can do, which language queries it, where its bytes live — RNT lets you add from outside the kernel.

Everything has a path

Relations, branches, sessions and evaluators live in one object tree. If you can name it, you can reach it.

Path.lookup root ("session" @/ "client" @/ "branch" @/ this)

Teach objects new tricks

What an object can do is a list of protocols, and the set is open. Clients that only know Directory keep working; clients that know your protocol ask the handle for it.

Bring your own drivers

A driver is a narrow dispatch table, and the kernel never asks what is behind it. Storage drivers implement eight operations over content-addressed tuples; others take work an evaluator delegates — a multi-way join on the GPU, relations served over HTTP. The sky is the limit.

Nothing happens behind your back

All access goes through an explicit handle. Opening, closing, pinning, cleanup and contention follow documented lifecycle rules.

Status

Early. APIs will change. What works today, with tests:

  • Content-addressed tuple storage on LMDB
  • Prolly-tree indexes
  • Branches, and sessions that pin a snapshot
  • Path lookup across the object tree
  • Substantial and ephemeral relations with pull-based cursors
  • A FOL evaluator with scan and projection
AGPLv3 — GNU Affero General Public License, version 3GNU AFFERO GENERAL PUBLIC LICENSE · VERSION 3AGPLv3AGPLv3AGPLv3AGPLv3AGPLv3AGPLv3AGPLv3AGPLv3AGPLv3AGPLv3AGPLv3AGPLv3AGPLv3

GNU · Free software

Free as
in freedom.

RNT and Sakura are free software under the GNU Affero General Public License, version 3, and Sol under the GNU GPL, version 3. Run them for any purpose, study how they work, change them, and share them, with your changes or without.

The Affero clause is the part we care most about for a database kernel: run a modified RNT as a network service, and everyone who uses it is entitled to its source too. Relations should not be locked away, and neither should the software that keeps them.