Tiramemsu

Reasoning with N3 · Chapter 9 of 10

Chapter 9

Reference

Every surface, option, vocabulary term and error in one place, followed by what the design leaves out on purpose.

Status. Reasoning ships behind the opt-in Cargo feature n3, off by default (OpenSpec change add-n3-reasoning, built on add-derived-beliefs). The reasoner is eyeron by Jos De Roo (MIT). Until eyeron is on crates.io the feature is available only when building from the repository.

Enabling it

Reasoning lives in the crate tm-n3, behind the facade feature n3. The feature is off by default, does not turn on exec, and combines with every other feature. Without it, neither tm-n3 nor eyeron is in the dependency tree. The core tier can reason: --no-default-features --features n3 builds without the query front ends.

Cargo.toml

[dependencies]
tiramemsu = { git = "https://github.com/Volland/tiramemsu", features = ["n3"] }

eyeron comes from crates.io with its HTTP client left out (default-features = false).

Rust API

tiramemsu, feature n3

use tiramemsu::{ReasonOptions, ReasonScope, Rules, TimeMode, TxN3};

// Read-only reasoning on any view.
impl View<'_> {
    pub fn reason(&self, rules: Rules, opts: &ReasonOptions) -> Result<ReasonResult>;
    pub fn rule_set_info(&self, rule_set: &Value) -> Result<RuleSetInfo>;
}

// Storing rule sets, inside a transaction (trait TxN3). Returns the eid of R.
fn store_rules(&mut self, rule_set: Value, n3: &str) -> Result<Eid>;

// Writing conclusions as derived beliefs.
impl Db {
    pub fn materialize(&self, rule_set: Value, opts: &MaterializeOptions) -> Result<MaterializeReport>;
}

pub enum Rules {
    Text(String),     // N3 text, parsed for this run
    Stored(Value),    // the live n3:source of a rule set in the view
}

ReasonOptions

OptionDefaultMeaning
scopeReasonScope::AllAll, Graphs(graphs) for members of named graphs, or Seeds { nodes, hops } for statements within hops of the seeds
timeTimeMode::PointwisePointwise sweeps valid-time segments; Atemporal ignores valid time. A valid_at view always reasons over one snapshot.
windowValid::ALWAYSthe valid-time window of the sweep; conclusions are reported only inside it
max_segments1 024stop the sweep after this many segments and report Incomplete { limits: [Segments] }
proof_n3falsealso return eyeron’s N3 proof text
limitseyeron’s defaultsReasonLimits for each eyeron run: max_iterations, max_match_steps, max_backward_depth, max_term_bytes

ReasonResult

FieldMeaning
conclusionsconclusions not among the loaded statements, each with s, p, o, its interval and a proof { rule_set, rule, premises }; premises are loaded statements by eid or other conclusions by index
tthe view’s transaction
nowthe pinned clock: the view’s instant
loaded, prunedstatements loaded, and whether predicate pruning applied
segmentsnumber of valid-time segments reasoned over
statusComplete, or Incomplete { limits } naming each limit reached: Segments, Iterations, MatchSteps, BackwardDepth, BackwardSolutions, TermBytes, TermDepth; builtin failures also make a run incomplete and are listed in errors
contradictionthe first fired inference fuse: its rule, instance and valid time
rule_set, rule_set_statementthe stored rule set, if the rules came from one, and the eid of its live n3:source statement
proof_n3eyeron’s proof text, with proof_n3: true

MaterializeOptions

OptionDefaultMeaning
scope, time, window, max_segments, limitsas for reasonthe window defaults to unbounded
require_completefalsefail with Incomplete instead of committing a partial run
dry_runfalsereport what would be written, commit nothing
budgetnonea QueryBudget for loading and the commit

MaterializeReport carries reasoned_at, the commit’s TxReport, derived, existing, widened, stale_support and status (chapter 7). RuleSetInfo carries monotonicity (Monotonic, NonMonotonic { rule, builtin } or ClockDependent { rule, builtin }), the rule count rules, the body predicates, and whether a body has a variable predicate.

Vocabulary

TermKindMeaning
n3:sourcestored(rule_set n3:source "…"), the one live statement R of a rule set
n3:derivedBystored label(D n3:derivedBy rule_set) on a materialized conclusion
n3:rulestored label(D n3:rule i), the index of the rule that produced it
n3:skolemstored, derivedthe key of an invented node, "<R eid>/<rule>/<label>"
sys:derivedFromstored, engine-owneda support link from a derived statement to a premise or to R
tm:contentvirtuale tm:content { s p o }, what statement e says
tm:validFrom, tm:validTovirtuala statement’s bounded valid-time ends, as xsd:dateTime
tm:run tm:nowvirtualthe pinned clock; same value as time:localTime
tm:run tm:segmentFrom, tm:segmentTovirtualthe bounded ends of the current sweep segment

Prefixes: n3: is urn:tiramemsu:n3:, tm: is urn:tiramemsu:tm:, sys: is urn:tiramemsu:sys:. Anonymous nodes, blank nodes and statements appear in N3 as urn:tiramemsu:node:N, urn:tiramemsu:bnode:N and urn:tiramemsu:stmt:N. Virtual triples are generated only when a rule body uses them and are never stored. tm: cannot be written.

Errors

ErrorWhen
N3Parse { line, message }rule text does not parse, in reason or store_rules; store_rules leaves no trace
UnknownRuleSetRules::Stored or materialize names a rule set with no live n3:source in the view
N3NotMaterializable { rule, builtin }materialize on a non-monotonic or clock-dependent rule set, before reasoning
N3Contradiction { rule }an inference fuse fired during materialize; nothing is written
Incompletematerialize with require_complete reached a limit; nothing is written

reason never fails for a fired fuse or a reached limit. It reports them in contradiction and status.

Python, Node.js and the browser

All three go through the JSON bridge, with a read operation reason, a transaction operation storeRules and a database operation materialize. The bridge takes the same options and returns the same result as Rust:

{"op": "reason", "rules": "{ ?x a <urn:tiramemsu:v:Human> } => { ?x a <urn:tiramemsu:v:Mortal> }."}

The bindings wrap the bridge operations:

Python

MORTALITY = "urn:tiramemsu:v:mortality"
with db.transact() as tx:
    tx.store_rules(MORTALITY, open("mortality.n3").read())

result = db.now().reason(rule_set=MORTALITY)
preview = db.materialize(MORTALITY, dry_run=True)

Node.js

const MORTALITY = "urn:tiramemsu:v:mortality";
db.transact((tx) => { tx.storeRules(MORTALITY, rulesText); });

const result = db.now().reason({ ruleSet: MORTALITY });
const preview = db.materialize(MORTALITY, { dryRun: true });

MCP

ToolKindDoes
reasonreadruns rule text or a stored rule set over a view; listed in read-only mode
store_ruleswritestores or supersedes a rule set; absent with --read-only
materializewritewrites conclusions as derived beliefs; absent with --read-only

An agent that may only read can still ask what follows from memory. Writing rules or conclusions needs a server started without --read-only.

Command line

tm reason <file> (--rules <path> | --rule-set <iri>) [--as-of <tx> | --as-of-instant <t>]
          [--valid-at <instant> | --valid-during <from>..<to>] [--graph <iri>]... [--atemporal] [--max-segments <n>] [--proof] [--json]
tm rules put  <file> <iri> <path>
tm rules show <file> <iri>
tm materialize <file> <iri> [--valid-during <from>..<to>] [--graph <iri>]...
               [--require-complete] [--dry-run]

--as-of takes a transaction number. Instants are epoch milliseconds or RFC 3339 dates and date-times, and either side of a window may be empty. --json prints the bridge result.

Limits and non-goals

Credits

Reasoning is done by eyeron, a native Rust N3 reasoner by Jos De Roo, released under the MIT license, from the family of the EYE reasoner. Tiramemsu adds the mapping to statements, the two clocks and grounded materialization around it.