Running Inference
The inference engine operates on rules and facts. Inference endpoints use psi() for term construction and plain JS values for features.
Adding rules
Section titled “Adding rules”A rule has a head (conclusion) and antecedents (body/conditions). When all antecedents are satisfied, the head can be derived.
import { ReasoningLayerClient, psi } from '@kortexya/reasoninglayer';
const client = new ReasoningLayerClient({ baseUrl: 'https://platform.ovh.reasoninglayer.ai', tenantId: 'my-tenant-uuid', auth: { mode: 'cookie' },});
// Rule: grandparent(X, Z) :- parent(X, Y), parent(Y, Z)const rule = await client.inference.addRule({ term: psi('grandparent', { person: '?X', grandchild: '?Z', }), antecedents: [ psi('parent', { person: '?X', child: '?Y', }), psi('parent', { person: '?Y', child: '?Z', }), ], certainty: 1.0,});
console.log(rule.term.termId); // UUID of the created ruleAdding facts
Section titled “Adding facts”Facts are ground terms (no variables) that serve as the knowledge base.
// Add individual factsawait client.inference.addFact({ term: psi('parent', { person: 'Alice', child: 'Bob', }),});
await client.inference.addFact({ term: psi('parent', { person: 'Bob', child: 'Charlie', }),});Bulk operations
Section titled “Bulk operations”// Bulk-add factsconst factsResult = await client.inference.bulkAddFacts({ facts: [ psi('parent', { person: 'Alice', child: 'Bob', }), psi('parent', { person: 'Bob', child: 'Charlie', }), ],});console.log(factsResult.factsAdded); // 2
// Bulk-add rules — recursive definition of `ancestor`// ancestor(X, Y) :- parent(X, Y). -- base case// ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y). -- recursive stepconst rulesResult = await client.inference.bulkAddRules({ rules: [ { term: psi('ancestor', { person: '?X', descendant: '?Y', }), antecedents: [ psi('parent', { person: '?X', child: '?Y', }), ], }, { term: psi('ancestor', { person: '?X', descendant: '?Y', }), antecedents: [ psi('parent', { person: '?X', child: '?Z', }), psi('ancestor', { person: '?Z', descendant: '?Y', }), ], }, ],});console.log(rulesResult.rulesAdded); // 2See Recursive Rules for the base-case / recursive-step pattern, tabling, fixpoint semantics, and how to bound recursion safely.
Backward chaining
Section titled “Backward chaining”Backward chaining searches for matching data by working backwards through rules from your goal.
Using an inline goal
Section titled “Using an inline goal”const result = await client.inference.backwardChain({ goal: psi('grandparent', { person: '?Who', grandchild: 'Charlie', }), maxSolutions: 10, maxDepth: 50,});
for (const solution of result.solutions) { console.log(`Certainty: ${solution.certainty}`); for (const binding of solution.substitution.bindings) { console.log(` ${binding.variableName} = ${binding.boundToDisplay}`); }}
console.log(`Query time: ${result.queryTimeMs}ms`);Using a saved goal ID
Section titled “Using a saved goal ID”You can save a goal for reuse and reference it by ID. The engine stores saved goals outside the fact store: a saved goal does not appear in a MATCH or in getFacts().
// Create and save a goalconst goalResult = await client.inference.createGoal({ clauses: [ psi('grandparent', { person: '?Who', grandchild: 'Charlie', }), ],});
// Use the saved goalconst result = await client.inference.backwardChain({ goalId: goalResult.goalId, maxSolutions: 5,});Open-world reasoning
Section titled “Open-world reasoning”By default, backward chaining uses closed-world semantics. Enable openWorld for evidence-based reasoning that tolerates missing information.
const result = await client.inference.backwardChain({ goal: psi('diagnosis', { condition: '?Condition', }), openWorld: true, minCertainty: 0.3,});
for (const solution of result.solutions) { // Open-world solutions include evidence metrics console.log(`Evidence ratio: ${solution.evidenceRatio}`); console.log(`Matched: ${solution.evidenceMatched}`); console.log(`Residuated sorts: ${solution.residuatedSorts}`);}Timeouts
Section titled “Timeouts”The timeoutMs field sets a wall-clock timeout for proof search. When it fires, the engine returns whatever solutions have been found so far.
const result = await client.inference.backwardChain({ goal: psi('complex_query', { result: '?R', }), timeoutMs: 5000, // 5 second timeout});// result.solutions contains partial results if timeout occurredForward chaining
Section titled “Forward chaining”Forward chaining is data-driven materialization. It applies rules to existing facts to derive new facts.
const result = await client.inference.forwardChain({ maxIterations: 100, maxFacts: 1000, keepDerived: true, enableProvenanceTags: true,});
console.log(`Derived ${result.derivedCount} new facts`);console.log(`Total facts: ${result.totalFacts}`);console.log(`Iterations: ${result.iterations}`);console.log(`Time: ${result.materializationTimeMs}ms`);
// Examine derived factsfor (const fact of result.derivedFacts) { console.log(`${fact.sortName}: ${fact.display}`);}
// Provenance tags (when enabled)if (result.provenanceTags) { for (const tag of result.provenanceTags) { console.log(`Fact[${tag.factIndex}] confidence: ${tag.confidence}`); }}Seeding with initial facts
Section titled “Seeding with initial facts”const result = await client.inference.forwardChain({ initialFacts: [ psi('temperature', { location: 'server-room', value: 45, }), ], keepDerived: false,});Interpreting solutions
Section titled “Interpreting solutions”A SolutionDto contains variable bindings, an optional proof tree, and certainty information.
const result = await client.inference.backwardChain({ goal: psi('ancestor', { person: '?Ancestor', descendant: 'Charlie', }),});
for (const solution of result.solutions) { // Variable bindings for (const binding of solution.substitution.bindings) { console.log(`${binding.variableName} -> ${binding.boundToDisplay}`); // binding.variableTermId -- UUID of the variable term // binding.boundToTermId -- UUID of the bound value term }
// Proof tree (if available) if (solution.proof) { printProof(solution.proof, 0); }
// Certainty console.log(`Certainty: ${solution.certainty}`);}
function printProof(proof, depth) { const indent = ' '.repeat(depth); // `goalTermId` is the id of the conclusion this step proved — absent when // no conclusion was materialised, so fall back to the readable rendering. const goal = proof.goalTermId ?? proof.goalDisplay ?? '(unmaterialised)'; console.log(`${indent}Goal: ${goal} (certainty: ${proof.certainty})`); if (proof.ruleTermId) { console.log(`${indent} via rule: ${proof.ruleTermId}`); } for (const sub of proof.subproofs ?? []) { printProof(sub, depth + 1); }}Managing facts and goals
Section titled “Managing facts and goals”// List all factsconst facts = await client.inference.getFacts();
// Clear all factsconst cleared = await client.inference.clearFacts();console.log(`Cleared ${cleared.factsCleared} facts`);
// List saved goalsconst goals = await client.inference.listGoals();
// Get a specific goalconst goal = await client.inference.getGoal(goalId);
// Delete a saved goal with the terms it owns (never a fact it references)const deleteResult = await client.inference.deleteGoal(goalId);Meta-sorts
Section titled “Meta-sorts”The inference engine uses built-in meta-sorts for constraint handling. You can inspect them:
const metaSorts = await client.inference.getMetaSorts();// Record<string, string> mapping meta-sort names to UUIDsconsole.log(metaSorts.guardConstraint); // UUID of the guard meta-sort