Skip to content

Quick Start

This guide walks through a complete workflow: defining a sort hierarchy, creating terms, adding inference rules and facts, and running backward chaining to answer a query.

1. Create the client

Every interaction starts by creating a ReasoningLayerClient. The two required options are baseUrl (where the Reasoning Layer backend is running) and tenantId (a UUID identifying your tenant).

import {
ReasoningLayerClient,
Value,
psi,
constrained,
} from '@kortexya/reasoninglayer';
const client = new ReasoningLayerClient({
baseUrl: 'https://platform.ovh.reasoninglayer.ai',
tenantId: '550e8400-e29b-41d4-a716-446655440000',
auth: { mode: 'cookie' },
});

2. Create a sort hierarchy

Sorts are types in a multiple-inheritance lattice. Create a person sort and an employee sort that inherits from it.

// Create the "person" sort with name and age features
const person = await client.sorts.createSort({
name: 'person',
features: [
{ name: 'name', required: true },
{ name: 'age', required: false },
],
});
// Create "employee" as a child of "person", adding a "department" feature
const employee = await client.sorts.createSort({
name: 'employee',
parents: [person.id],
features: [
{ name: 'department', required: true },
{ name: 'salary', required: false },
],
});

You can also create an entire hierarchy in a single request using bulkCreateSorts, which accepts sort names as parent references instead of UUIDs:

const bulk = await client.sorts.bulkCreateSorts({
sorts: [
{ name: 'person', features: [{ name: 'name', required: true }] },
{ name: 'employee', parents: ['person'], features: [{ name: 'department', required: true }] },
{ name: 'manager', parents: ['employee'], features: [{ name: 'reports', required: false }] },
],
});
console.log(bulk.sortIds);
// { person: "uuid-1", employee: "uuid-2", manager: "uuid-3" }

3. Create terms

Terms are instances of sorts. For simple string and integer values, pass plain literals. For complex values (references, fuzzy scalars, fuzzy numbers, sets), use the Value.* builders.

const alice = await client.terms.createTerm({
sortId: employee.id,
ownerId: '00000000-0000-0000-0000-000000000001',
features: {
name: 'Alice',
age: 30,
department: 'Engineering',
salary: 120000,
},
});
console.log(alice.term.id); // UUID of the created term
console.log(alice.term.features); // { name: {type: "String", value: "Alice"}, ... }
console.log(alice.state); // "complete" | "residuated" | "no_witnesses"

4. Add rules and facts for inference

Inference operates on a separate homoiconic representation using the untagged format. Use the psi() builder for terms and plain literals for feature values.

Add facts

Facts are ground terms — concrete data the inference engine can reason about.

// Add facts about employees
await client.inference.addFact({
term: psi('employee', {
name: 'Alice',
department: 'Engineering',
salary: 120000,
}),
});
await client.inference.addFact({
term: psi('employee', {
name: 'Bob',
department: 'Engineering',
salary: 95000,
}),
});
await client.inference.addFact({
term: psi('employee', {
name: 'Carol',
department: 'Sales',
salary: 110000,
}),
});

Add rules

Rules define logical relationships. A rule has a head (conclusion) and antecedents (conditions).

import { guard } from '@kortexya/reasoninglayer';
// Rule: A "senior_employee" is an employee with salary > 100000
await client.inference.addRule({
term: psi('senior_employee', {
name: '?Name',
department: '?Dept',
}),
antecedents: [
psi('employee', {
name: '?Name',
department: '?Dept',
salary: constrained('?Salary', guard('gt', 100000)),
}),
],
certainty: 1.0,
});

Variables (prefixed with ?) unify across the head and body of a rule. The constrained('?Salary', guard("gt", 100000)) constrains ?Salary to values greater than 100,000.

5. Run backward chaining

Backward chaining searches for matching data by working backwards through rules from your goal.

const result = await client.inference.backwardChain({
goal: psi('senior_employee', {
name: '?Name',
department: '?Dept',
}),
maxSolutions: 10,
});
console.log(`Found ${result.solutions.length} solutions in ${result.queryTimeMs}ms`);

6. Interpret solutions

Each solution contains variable bindings showing how the goal was satisfied.

for (const solution of result.solutions) {
console.log(`Certainty: ${solution.certainty}`);
for (const binding of solution.substitution.bindings) {
console.log(` ${binding.variableName} = ${binding.boundToDisplay}`);
}
// Output:
// ?Name = Alice
// ?Dept = Engineering
//
// ?Name = Carol
// ?Dept = Sales
// Optional: inspect the proof tree
if (solution.proof) {
console.log(` Proved via rule: ${solution.proof.ruleTermId}`);
}
}

Error handling

All SDK errors extend ReasoningLayerError, enabling structured error handling:

import {
ReasoningLayerError,
ApiError,
AuthenticationError,
ForbiddenError,
NotFoundError,
RateLimitError,
TimeoutError,
} from '@kortexya/reasoninglayer';
try {
const sort = await client.sorts.getSort('nonexistent-uuid');
} catch (error) {
if (error instanceof AuthenticationError) {
console.log('Invalid or missing credentials');
} else if (error instanceof ForbiddenError) {
console.log('Insufficient permissions');
} else if (error instanceof NotFoundError) {
console.log('Sort not found');
} else if (error instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfter}s`);
} else if (error instanceof TimeoutError) {
console.log(`Request timed out after ${error.timeoutMs}ms`);
} else if (error instanceof ApiError) {
console.log(`API error ${error.status}: ${error.message}`);
} else if (error instanceof ReasoningLayerError) {
console.log(`SDK error: ${error.message}`);
}
}

Next steps

  • Configuration — customize timeouts, retries, interceptors, and per-call overrides
  • API Reference — full reference for all resource clients, types, and builders