Questions only layers can answer
None of tiramemsu's ingredients is new on its own. RDF-star annotates edges, Datomic makes transactions into entities, XTDB keeps two clocks, Graphiti gives edges validity windows. What is unusual is that they meet in one row. Every statement, and every layer on it, has an id, both clocks, a structural link to what it is about, and a row that is never deleted. This article collects the questions that combination answers in one query. Each one is a test in the repository (crates/tiramemsu/tests/recipes.rs and the feature suites), and the design notes are in lat.md/recipes.md.
The examples use one small memory: e1 = (alice worksAt acme), a confidence and a source on e1, a derivation from a document statement, and a belief belief9 supported by e1. If layers are new to you, start with Layered graphs, explained.
1. What did this belief rest on, back then?
A property path can start at a belief, land on a statement id, and keep walking through links between statements. Put it in a time scope and the walk sees the links as they were:
SELECT ?src WHERE {
SERVICE <urn:tiramemsu:tm:asOf/150> {
v:belief9 v:supportedBy/v:derivedFrom* ?src } }
If the derivation link was retracted after transaction 150, today's answer is e1 alone, and the answer as of 150 also includes the document statement. The virtual hop sys:subject steps from a statement to its subject, so v:belief9 v:supportedBy/sys:subject ?who gives the person the belief is ultimately about. A property graph cannot point an edge at an edge, an RDF-star store cannot run a property path through a quoted triple, and a datom has no id to walk to.
2. What breaks if I retract this?
Retracting a fact retracts everything about it, recursively. View::dependents(eid) lists that set without taking the write lock, on any view:
// what a retraction of e1 would take with it, and what depended on it back then
let at_risk = db.now().dependents(e1)?;
let then = db.as_of(TimeRef::Tx(150)).dependents(e1)?;
It is also a path query, because the inverse virtual hops step from a statement to the statements about it. A property test checks, over random layered graphs with cycles, that these three are the same set: the dependents, the list a dry-run retraction reports, and the ends of this path:
SELECT "end" FROM tm_path(:e1, '(^sys:subject|^sys:object)*', 'REACH')
3. Who wrote this, and why was that forgotten?
A transaction is a node, and its metadata is ordinary triples. The hop from a statement to its transaction is a virtual predicate, so provenance needs no annotation on each fact:
# every employment fact agent7 wrote
SELECT ?who ?c WHERE { ?who v:worksAt ?c ~ ?r . ?r tm:txAdded ?t . ?t sys:author v:agent7 }
# why facts were retracted
SELECT ?who ?why FROM <urn:tiramemsu:tm:history> WHERE {
?who v:worksAt ?c ~ ?r . ?r tm:txRetracted ?t . ?t sys:reason ?why }
4. How was this fact corrected over time?
Every correction writes sys:supersedes from the new version to the old one, and replays the fact's layers onto the new version. The whole lineage is one path:
SELECT ?old WHERE { v:alice v:age ?a ~ ?r . ?r sys:supersedes+ ?old }
5. Where do my sources disagree?
Two live facts with the same subject and predicate, different objects, overlapping valid time and different authors. A single-valued predicate would have refused the second one on write. For the others, this finds the conflict on read, and a later job that overlaps neither is not reported:
SELECT ?s ?o1 ?o2 ?a1 ?a2 WHERE {
?s v:worksAt ?o1 ~ ?r1 . ?s v:worksAt ?o2 ~ ?r2 . FILTER(STR(?o1) < STR(?o2))
?r1 tm:txAdded ?t1 . ?t1 sys:author ?a1 .
?r2 tm:txAdded ?t2 . ?t2 sys:author ?a2 . FILTER(?a1 != ?a2)
OPTIONAL { ?r1 tm:validFrom ?f1 } OPTIONAL { ?r1 tm:validTo ?u1 }
OPTIONAL { ?r2 tm:validFrom ?f2 } OPTIONAL { ?r2 tm:validTo ?u2 }
FILTER((!BOUND(?f1) || !BOUND(?u2) || ?f1 < ?u2) &&
(!BOUND(?f2) || !BOUND(?u1) || ?f2 < ?u1)) }
6. How late did we learn it?
tm:addedAt is the wall-clock instant a statement was recorded; tm:validFrom is when it became true. Their difference is how late memory learned the fact, and only a store with both clocks on every statement can compute it:
// Cypher: learned more than $days days after it became true
MATCH (a)-[r:worksAt]->(c)
WHERE r.addedAt.epochMillis - r.validFrom.epochMillis > $days * 86400000
RETURN a, c
# SPARQL: recorded after it had already stopped being true
SELECT ?s WHERE { ?s v:worksAt ?o ~ ?r . ?r tm:addedAt ?a ; tm:validTo ?to FILTER(?a > ?to) }
7. Which of my answers are stale?
With provenance switched on, every SPARQL row carries the ids of the stored statements that produced it: the matched patterns, the optional parts that matched, the union branch taken, annotations and graph memberships. An agent keeps them with its answer. Later, a cited id that is no longer live marks the answer as stale, and the history view says which transaction retracted it:
let rows = view.sparql_with(q, &SparqlOptions { provenance: true })?;
let cited = rows.solutions().unwrap().provenance(0).unwrap(); // sorted statement ids
SELECT ?e ?t FROM <urn:tiramemsu:tm:history> WHERE {
VALUES ?e { <urn:tiramemsu:stmt:12> <urn:tiramemsu:stmt:19> } ?e tm:txRetracted ?t }
Rows are exactly the rows of the same query without provenance; a corpus test runs every differential query both ways. Recursive paths do not cite their hops yet.
8. Can I hand this fact to another agent?
Tiramemsu is one SQLite file per agent. A bundle is one fact with everything about it and everything it rests on, ready to move to another file:
let bundle = agent_a.now().bundle(e1)?.to_json(); // "tiramemsu-bundle/1"
agent_b.transact(TxOptions::default(), |tx| {
tx.meta(Value::iri(vocab::SYS_SOURCE), v("agentA"))?;
tx.import_bundle(&Bundle::from_json(&bundle)?)?;
Ok(())
})?;
Import asserts, so importing twice changes nothing, and a fact the other agent already knows gains the new layers on its own id. Transaction ids and confirmations stay behind, because they only mean something in the file that wrote them. to_ntriples() writes the same bundle as RDF 1.2 with reifiers, for other RDF tools.
9. Can I stop layers from rotting?
A schema flag says what a layer predicate may annotate. With (v:confidence sys:subjectType sys:STMT), a confidence written on a node by mistake is rejected with SubjectTypeMismatch on every write path, API, SPARQL and Cypher alike, and a flag that live data already violates is refused with the list of violating ids.
10. What can I reach inside one session?
A graph is a tag on statements, so a recursive path under GRAPH only crosses statements tagged with it. A session's reasoning can be walked without the rest of memory leaking in, and under asOf the walk follows the graph as it was:
SELECT ?x WHERE { GRAPH v:session12 { v:belief9 v:supportedBy/(sys:subject|sys:object)+ ?x } }
SELECT ?g ?x WHERE { GRAPH ?g { v:alice v:knows+ ?x } } # per graph
11. Could this have reached B, and when?
A time-respecting path never goes back in valid time: each fact it crosses must still hold when the walk gets there. If A met B between days 1 and 5 and B met C between days 3 and 9, something could pass from A to C, arriving at day 3 at the earliest. If C had met D only between days 0 and 2, it could not have reached D, although an ordinary path says it could:
let args = PathArgs { time_respecting: Some(TimeRespecting { after: None }), ..PathArgs::default() };
let rows = view.path_with(a, "met+", &args)?; // each row carries its earliest arrival
It works with every path mode and with graphs, and tm_path takes it as a timeRespecting part of its view text. A property test compares all four modes with a brute-force enumeration of walks.
What is not new, and what is still missing
Each recipe could be built elsewhere with enough glue: a side table for statement ids, a history table per relation, a job that recomputes impact. The point is that here they are the same row and one query, and they stay exact under time travel because nothing is deleted.
Still open: merging two agents' files as a whole needs ids that mean the same thing in both. A proposal to reserve origin bits in every id is in openspec/changes/reserve-replica-id and waits for a decision. Ranking text and vector search results by how well a memory is supported is part of the planned retrieval milestone.