TessariDB
The stack around the model, in one store: eleven engines — documents, graph, full-text, vectors, geometry, key-value, files, time-series, queues, topics and a vault — under one language, one transaction and one binary.
TessariDB is one store with eleven engines: documents, a graph, full-text search, vectors, geometry, a key-value space, file buckets, time-series, queues, topics and a vault. Not eleven services behind a façade — eleven access paths over one substrate, reached through one language and written in one transaction.
| engine | for | engine | for |
|---|---|---|---|
| Documents | records, nested and schemaless or declared | Key-value | one value under one key — expiring, counted, bounded |
| Graph | edges between records | Files | bytes, in chunks |
| Full-text | words inside text, scored | Time-series | events grouped by window, under a declared floor |
| Vectors | similarity between embeddings | Queues | work handed out one holder at a time |
| Geometry | shapes on the sphere, in metres | Topics | events every reader sees once, in commit order |
| Vault | secrets, sealed before they are stored |
The point is not that each exists — plenty of stores have one or two. It is that a question spanning them is one statement rather than a join written in your application against two systems that disagree about what time it is.
What it stands in for, and the trade
A project built around a model rarely needs one data system. It needs search, a database for everything else and a queue; then a cache, a lock, an event log, files, places, relations and somewhere for the secrets it must not leak. Each of those is usually one more service to stand up, upgrade, back up, secure and keep in step with the others — and most projects never load any one of them hard enough to need the specialist.
| a project would run | here |
|---|---|
| a database | records — declared or schemaless, joined, in transactions |
| a search engine | full-text — analysed in five languages, scored, prefix, infix and fuzzy; one search over several fields and tables, with synonyms |
| a vector index, and a step that re-ranks the two | vectors, and ORDER BY FUSE |
| a cache, a lock, a rate limit | a space — keys that expire, INCR, SET … IF ABSENT, a limit that evicts |
| a job queue | a queue — claims that lapse when a worker dies |
| an event log | a topic — each reader's position in the store |
| a geo database | geometry — radius reads, nearest-first, cells for a map |
| a graph database | the graph — edges as records, traversed |
| object storage | a bucket, in the same transaction as the records |
| somewhere for secrets | the vault — fields sealed before they are stored |
| a time-series store, an audit trail | series, VERSION and the change feed |
Not the fastest at any one of them; enough at all of them. A system built for one job will beat a general store at that job, and nothing here claims otherwise — there is no published benchmark. What the store removes is the cost of running all of them, and the seams between them. It grows with the project on the same binary — in memory, on disk, with followers, automatic failover, tables split by key range, two writers on one range, and nodes that speak only TLS to one another — and where a project outgrows one part of it, the data is one log behind a published protocol and five clients, so that part can move out when the numbers say so. The vault seals declared fields; it is not a general secrets service with leases and rotation.
Why this matters for AI applications
Storing a chunk of text with an embedding beside it is the first week of the problem. What an application asks for afterwards is words and meaning ranked together, and a filter and a traversal and what a record said before somebody edited it — then state for the tools the model calls, a cache for answers that cost money, a queue for the embedding pipeline and events the rest of the product reacts to. Every one of those in a separate system is a dual write nobody can make atomic.
Start with the chunk anyway, because it is the shortest way to see the shape. The embedding is an array of numbers in the record, beside the text it came from, so retrieval is one statement: similarity, a full-text match and an ordinary filter, together.
DEFINE ANALYZER simple FILTERS lowercase, ascii;
DEFINE COLLECTION chunks;
DEFINE FIELD body ON chunks TYPE string ANALYZER simple;
DEFINE INDEX by_body ON chunks FIELDS body SEARCH;
DEFINE INDEX by_at ON chunks FIELDS at VECTOR euclidean;
CREATE chunks = { body: 'Ada Lovelace wrote the first program', doc: 'notes', at: [0.1, 0.0] };
CREATE chunks = { body: 'Grace Hopper wrote the first compiler', doc: 'notes', at: [0.9, 0.1] };
CREATE chunks = { body: 'Ada Lovelace and the analytical engine', doc: 'other', at: [0.15, 0.02] };
SELECT body FROM chunks WHERE body MATCHES 'lovelace' AND doc = 'notes' ORDER BY vector::euclidean(at, [0.1, 0.0]) LIMIT 5;
One record back, reached through an index — a keyword match, a tenancy filter and a nearest-neighbour ordering, resolved together rather than intersected in your application.
What follows from the same shape:
| No dual write | the chunk, its embedding, its source document and its edges land in one transaction, or none of them do — there is no reconciliation job because there is nothing to reconcile |
| Exact unless you ask otherwise | a vector read is the exact answer unless the statement says APPROXIMATE, so the ground truth you measure recall against comes from the same store, not a second one |
| Provenance is a traversal | a citation is an edge, so which document did this answer come from is a query rather than a column convention |
| The originals live here too | source files go in a bucket, in the same transaction as the chunks derived from them |
| The store remembers what it said | VERSION reads it as it stood, so what did it know at the time is a statement — and below what reclamation has left, a read is refused rather than guessed |
| Catch up, not poll | a subscriber follows a table and is addressed by position in the log, so it resumes exactly where it stopped (protocol) |
| Secrets it cannot read back | a field declared to the vault is sealed before the record is encoded, so the index, the change feed, the replication log and every backup carry ciphertext |
| One binary | in memory for a test, on disk for a service, and the same process serves it — nothing to stand up before an experiment |
A worked example of all of that in one application is Agent Memory — a second product built on this engine, and the page says which parts of TessariDB it leans on for what.
The aspects worth knowing before you commit
| One language over everything | there is no separate schema language, no migration tool, no admin API — defining a table, reading records, declaring a user and taking a backup are all statements |
| An index changes the cost, not the answer | adding one never changes what a read returns, with a single exception the statement has to spell out: APPROXIMATE |
| Snapshot isolation, declared | named in the documentation and in the error taxonomy rather than left to be inferred from behaviour |
| A refusal has a name | every one is stable enough to match on, and carries the span in your script — the catalogue lists them |
| The log is the backup, and the state is too | a restore is a replay, a snapshot carries the current state when the log has been pruned, a script rebuilds the store in its own language, and --verify reads a backup without needing anywhere to put it |
| Closed by declaration | a store with no user is open; declaring the first closes it, for reads as well as writes |
Where to start
- Your first store — from an empty prompt to a record you can read back, in about two minutes.
- What it is — the shape of the store, in a page.
- The engines — all eleven, what each had to decide to be exact, and which one answers your question.
- TessariQL — the language, starting with records.
The rest of it
| Getting started | install it, run it, connect to it |
| TessariQL | records, documents, graphs, joins, search, vectors, geometry, transactions |
| Keys and files | the key-value space and the file bucket, in the same transaction as the records |
| Users and access | the open store, the three roles, what a grant narrows, and encryption in transit and at rest |
| Clustering | adding a node, what replicates where, failover, acknowledgement, reads and routing |
| Running a node | serving, draining, stopping, backing up, peers |
| Clients | the Rust client, the HTTP surface, the wire protocol |
| Reference | every statement, function and type, the command line, and the refusals by name |
What is true here
Every example on this site is run against the engine before the page ships — in order, against a scratch store, with a block the prose calls refused required to be refused. An example that appears here is an assertion that passes, not a sketch of an intended feature. Many of them are drawn from the engine's conformance corpus, which is the executable definition of the language; the rest are written for the page and held to the same bar. See The examples are executed.
Where a page describes something the engine does not do yet, it says so at the top. There is no third category.
This is 0.34.2-beta — what tessaridb --version answers, and what the
published image tessaridb/tessaridb:0.34.2-beta runs. The language and the wire
are still moving. The on-disk format is held: a store written by 0.22.0-beta or
any release since opens under a newer build and reads back the same.