Tiramemsu

Your agent’s brain loves Tiramemsu.

Memory that has layers and never forgets.

Tiramemsu is an embedded graph database on SQLite for agent and personal memory. Every fact has its own id, so a fact can carry a confidence, a source, or a belief about it, layer on layer. Every change is kept, with when it was made and when it was true.

A brain taking a bite out of a tiramisu whose layers are a graph

What it does

A small Rust core over one SQLite file. Two query languages, one store, exact answers to “what did we know, and when?”.

Layers

Every fact has an id

A statement is a row with its own id, so it can be the subject or object of other statements. Provenance, confidence and beliefs stack to any depth.

Time

Bitemporal, forever

Transaction time records when the database learned or dropped a fact. Valid time records when it held in the world. as_of, history and valid_at views, per query or per pattern.

Never forget

Nothing is deleted

SQLite triggers forbid DELETE and second retractions in the file itself. Forgetting means retracting, and the past stays exact.

Correct

Supersede, confirm, speculate

Correct a fact and its layers are replayed on the new one. Confirm a fact from a new source. Try changes in a with block that leaves no trace.

Query

SPARQL and Cypher

SPARQL 1.1 with RDF 1.2 annotations, and openCypher, share one IR and one semantics table. A relationship is also a :Statement node.

Paths

Native path engine

Reachability, trails and shortest paths as an automaton search, also as a SQL table function. Paths can cross layers to reach what a belief is about.

Graphs

Named graphs as tags

A graph is a node and membership is one more layer statement. GRAPH, FROM and WITH work with no new column or table.

Embedded

One file, one crate

Rust library over bundled SQLite (WAL, STRICT). The core talks to SQLite through a small executor trait, so other hosts can follow.

What agents are saying

Early feedback from the people it is for. We made these up, but each one is about something the code really does.

“I used to say ‘I recall you said Acme’ with total confidence. Now I say 0.8, from chat-2026-09-29. My humans find this either reassuring or unsettling.”

A chatbot, on layers

“I was wrong about where Alice works. supersede fixed the fact and carried my confidence over to it. I have never felt so forgiven.”

An assistant, on supersede

“My last memory store let me DELETE things. I do not trust myself with that kind of power. Here the triggers say no, in the file itself.”

A cautious agent, on never forgetting

“A user asked what I believed last Tuesday. I ran as_of and answered without a single hallucination. I asked for a raise in tokens.”

A support agent, on bitemporal views

“I wanted to try a wild idea without committing to it. A with block let me be reckless and leave no trace. I recommend it to all my sub-agents.”

A planner agent, on speculation

“My orchestrator speaks SPARQL and my intern speaks Cypher. They now share one store and, for the first time, one opinion.”

A multi-agent swarm, on two query languages

“It is one SQLite file. I can carry my entire brain in my context window’s pocket. Please stop asking me to attach a server, there isn’t one yet.”

An embedded agent, on being embedded

Layered graphs

A layer is not a separate structure. It is a statement whose subject is the id of another statement.

Three layers: the fact alice worksAt acme, a confidence and a source about it, and a belief supported by it

Read the full explanation →

How it is built

Two front ends compile to one logical IR. Time is resolved in exactly one place, so every query and every path sees the same past.

Architecture: SPARQL, Cypher and the Rust API compile to one IR; a planner and a path engine read through a view-aware scan over one SQLite file

Quick start

The same story in three languages: a fact with a layer, a correction that keeps the layer, and the question “what did we believe before the correction?”. Pick a language.

pip install tiramemsu · Python 3.9 or later · PyPI

from tiramemsu import Database, Iri

V = "urn:tiramemsu:v:"  # written `v:` in queries
alice, works_at, acme, globex, confidence = (
    Iri(V + name) for name in ("alice", "worksAt", "acme", "globex", "confidence")
)
db = Database("memory.db")

# 1. A fact is a statement with its own id, so it can carry layers.
with db.transact() as tx:
    job = tx.assert_(alice, works_at, acme)
    tx.assert_(job, confidence, 0.8)
fact = tx.report.asserted[0]

# 2. Correct it: the layers are replayed on the new fact, nothing is deleted.
with db.transact() as tx:
    tx.supersede(fact, o=globex)

# 3. What is believed now, and what was believed before the correction?
q = "SELECT ?org WHERE { v:alice v:worksAt ?org }"
db.as_of(tx=1).sparql(q)   # acme
db.now().sparql(q)         # globex

# 4. The same store in Cypher: the confidence layer is a relationship property.
db.now().cypher("MATCH (p)-[r:worksAt]->(o) RETURN p, o, r.confidence AS conf")  # conf = 0.8

npm install @tiramemsu/node · Node 18 or later · npm

import { Database, iri } from "@tiramemsu/node";

const v = (name) => iri(`urn:tiramemsu:v:${name}`); // written `v:` in queries
const [alice, worksAt, acme, globex, confidence] =
  ["alice", "worksAt", "acme", "globex", "confidence"].map(v);
const db = Database.open("memory.db");

// 1. A fact is a statement with its own id, so it can carry layers.
const first = db.transact((tx) => {
  const job = tx.assert(alice, worksAt, acme);
  tx.assert(job, confidence, 0.8);
});

// 2. Correct it: the layers are replayed on the new fact, nothing is deleted.
db.transact((tx) => { tx.supersede(first.asserted[0], { o: globex }); });

// 3. What is believed now, and what was believed before the correction?
const q = "SELECT ?org WHERE { v:alice v:worksAt ?org }";
db.asOf({ tx: 1 }).sparql(q);   // acme
db.now().sparql(q);             // globex

// 4. The same store in Cypher: the confidence layer is a relationship property.
db.now().cypher("MATCH (p)-[r:worksAt]->(o) RETURN p, o, r.confidence AS conf"); // conf = 0.8

cargo add --git https://github.com/Volland/tiramemsu tiramemsu · not on crates.io yet

let db = Db::open(dir.join("memory.db"), OpenOptions::default())?;

// 1. A fact is a statement with its own id, so it can carry layers.
db.transact(TxOptions::default(), |tx| {
    let eid = match tx.assert(v("alice"), v("worksAt"), v("acme"), Valid::ALWAYS)? {
        Asserted::New(e) | Asserted::Existing(e) => e,
    };
    tx.assert(Value::Stmt(eid), v("confidence"), &conf, Valid::ALWAYS)?;
    tx.assert(Value::Stmt(eid), v("source"), Value::str("chat-2026-09-29"), Valid::ALWAYS)?;
    Ok(())
})?;

// 2. Correct it: the layers are replayed on the new fact, nothing is deleted.
let corrected = db.transact(TxOptions::default(), |tx| {
    tx.supersede(fact, Patch { o: Some(v("globex")), ..Patch::default() })?; Ok(())
})?;

// 3. What is believed now, and what was believed before the correction?
let q = "SELECT ?who ?org WHERE { ?who v:worksAt ?org }";
db.as_of(TimeRef::Tx(corrected.t.0 - 1)).sparql(q)?;   // alice → acme
db.now().sparql(q)?;                                        // alice → globex

// 4. The same store in Cypher: the confidence layer is a relationship property.
db.now().cypher("MATCH (p)-[r:worksAt]->(o) RETURN p, o, r.confidence", &CypherParams::default())?; // 0.8

This is crates/tiramemsu/examples/quickstart.rs; run it with cargo run -p tiramemsu --example quickstart. Setup lines are trimmed here.

Every binding wraps the same core, so all three see the same past. See the bindings for the full API.

Where it stands

Tiramemsu is new: designed in September 2026 and implemented in one pass. Here is what is measured, and what is not.

58 000 lines of Rust

Seven crates, 927 tests, 30 capability specs written before the code (OpenSpec) and a design graph kept in sync (lat.md).

SPARQL

634 of 781 in-scope W3C tests pass (66 more are skipped: named-graph data, unsupported formats). Every failing one is listed with a reason, and an unexpected result fails the build.

Cypher

2 615 of 3 880 openCypher TCK scenarios (67 %). Temporal types, CALL and a few dual-view cases are deferred and listed.

Speed

Raw SQLite lookups on its schema take about 4 µs at 11 million statements. Statements cost about 150 bytes each with all indexes. Details and caveats.

Known limits. As-of lookups slow down as one key collects many updates. Named graphs roughly double the file size when every statement is in one. Decimals come back as doubles in SPARQL. There is no server, WASM binding or MCP crate yet, and the Node.js and Python packages are built but not published.

Articles