Skip to content

Builders

The SDK provides builder namespaces that construct the correct serialization format for each API context. Builders are the primary developer ergonomics layer — they ensure type safety, prevent format mismatches, and provide IntelliSense guidance.

Context Builder to use Produces
Term CRUD features Plain JS values or Value.* Tagged ValueDto
Query features Plain JS values or Value.* Tagged ValueDto
Fuzzy operation features Plain JS values or Value.* Tagged ValueDto
Fuzzy membership shapes FuzzyShape.* FuzzyShapeDto
Inference rule/fact inputs psi(), plain values Untagged TermInputDto
Guard constraints guard() Untagged TermInputDto
Constrained variables constrained() ConstrainedPlainVar
Sort creation SortBuilder CreateSortRequest
Psi-term shorthand psi() PsiTermInput
Allen temporal constraints allen() Tagged ConstraintInputDto
LP optimization LP.* LinearProgramDefinition

The Value namespace builds complex tagged ValueDto values that have no plain JS equivalent. For simple types, pass plain JS values directly — the SDK auto-converts them.

Plain JS values (no builder needed):

// These are auto-converted to tagged ValueDto by the SDK:
"hello" // -> {"type": "String", "value": "hello"}
42 // -> {"type": "Integer", "value": 42}
3.14 // -> {"type": "Real", "value": 3.14}
true // -> {"type": "Boolean", "value": true}
null // -> {"type": "Uninstantiated"}
[...] // -> {"type": "List", "value": [...]}

Use with: client.terms.*, client.query.*, fuzzy operations.

import { Value, FuzzyShape } from '@kortexya/reasoninglayer';
// Plain values (no builder needed):
const features = { name: 'Alice', age: 30, active: true };
// Complex types (require Value.*):
Value.reference(termUuid); // {"type": "Reference", "value": "uuid"}
// Fuzzy values
Value.fuzzyScalar(0.5, 0.8); // {"type": "FuzzyScalar", "value": {"value": 0.5, "membership": 0.8}}
Value.fuzzyNumber(FuzzyShape.triangular(20, 22, 24)); // {"type": "FuzzyNumber", "value": {"shape": {"kind": "Triangular", ...}}}
// Sets (Smyth powerdomain)
Value.set(
['uuid1'], // lower bound (definite members)
['uuid1', 'uuid2', 'uuid3'], // upper bound (possible members)
'person_sort_id', // optional sort constraint
); // {"type": "Set", "value": {"lower": [...], "upper": [...], ...}}
Method Return type JSON "type" value
Value.reference(id) ReferenceValue "Reference"
Value.fuzzyScalar(v,m) FuzzyScalarValue "FuzzyScalar"
Value.fuzzyNumber(s) FuzzyNumberValue "FuzzyNumber"
Value.set(lo,hi,sort?) SetValue "Set"

Simple types (string, number, boolean, null, arrays) are passed as plain JS values and auto-converted by the SDK.

The FuzzyShape namespace builds fuzzy membership function shapes. These use "kind" as the discriminator field, not "type":

import { FuzzyShape } from '@kortexya/reasoninglayer';
// Triangular: peak at b, zero at a and c
FuzzyShape.triangular(20, 22, 24);
// {"kind": "Triangular", "a": 20, "b": 22, "c": 24}
// Trapezoidal: flat top between b and c
FuzzyShape.trapezoidal(18, 20, 24, 26);
// {"kind": "Trapezoidal", "a": 18, "b": 20, "c": 24, "d": 26}
// Gaussian: bell curve centered at mean
FuzzyShape.gaussian(100, 15);
// {"kind": "Gaussian", "mean": 100, "stdDev": 15}
// Cyclic Gaussian: wraps around a period (e.g., compass directions)
FuzzyShape.cyclicGaussian(180, 30, 360);
// {"kind": "CyclicGaussian", "mean": 180, "stdDev": 30, "period": 360}

Combine with Value.fuzzyNumber() for use in term features:

const features = {
temperature: Value.fuzzyNumber(FuzzyShape.triangular(20, 22, 24)),
iqScore: Value.fuzzyNumber(FuzzyShape.gaussian(100, 15)),
};

The psi() function creates format-agnostic psi-terms that the SDK converts to the correct wire format automatically. It is the primary way to build term inputs for inference.

Use with: client.inference.* for goals, rules, facts.

import { psi, constrained, guard } from '@kortexya/reasoninglayer';
// Simple term — plain JS values are used directly
psi('person', { name: 'Alice', age: 30, active: true });
// {"sortName": "person", "features": {"name": "Alice", "age": 30, "active": true}}
// Variables — strings starting with "?" are auto-detected as variables
psi('person', { name: '?Name', age: '?Age' });
// Nested psi-terms
psi('employee', {
person: psi('person', { name: 'Bob' }),
salary: 100000,
});
// With constrained variable
psi('employee', {
salary: constrained('?Salary', guard('gt', 50000)),
});
// Term reference — strings starting with "!" are auto-detected as references
psi('department', { manager: '!550e8400-...' });
// Uninstantiated values — null is accepted
psi('employee', { name: 'Alice', salary: null });
// No features
psi('person');
// {"sortName": "person"}
JavaScript value Coerced to
string Raw string
"?X" (starts with ?) Variable (auto-detected)
"!uuid" (starts with !) Term reference (auto-detected)
number Raw number
boolean Raw boolean
null Uninstantiated
psi("sort", features) Nested psi-term (recursive)
constrained("?X", guard) Constrained variable
{termId: "uuid"} Term reference
{sortName: "x", features} Inline term by name (recursive)
{sortId: "x", features} Inline term by sort ID (recursive)
{name: "?X", constraint} Constrained variable
{name: "?X"} Variable
[...] List (each element is coerced)

The constrained() function creates a constrained variable for use in psi() features. It pairs a variable name with a guard or type constraint.

import { psi, constrained, guard } from '@kortexya/reasoninglayer';
// Constrained variable with a guard
constrained('?Salary', guard('gt', 100000));
// Use within psi():
psi('employee', {
name: '?Name',
salary: constrained('?Salary', guard('gt', 100000)),
age: constrained('?Age', guard('gte', 40)),
});

The guard() function creates a TermInputDto representing a guard constraint. It is the primary way to create value constraints for variables.

import { guard } from '@kortexya/reasoninglayer';
// Create a guard constraint
guard('gt', 100000);
// {"sortName": "guard_constraint", "features": {"op": "gt", "right": 100000}}

Available operators (GuardOp):

Op Meaning
"lt" Less than
"lte" Less than or equal
"gt" Greater than
"gte" Greater than or equal
"eq" Equal
"ne" Not equal

The right argument accepts number, string, or boolean:

guard('gt', 100000); // numeric comparison
guard('eq', 'active'); // string comparison
guard('ne', false); // boolean comparison
import { psi, constrained, guard } from '@kortexya/reasoninglayer';
// Find high earners over 40
const goal = psi('employee', {
name: '?Name',
salary: constrained('?Salary', guard('gt', 100000)),
age: constrained('?Age', guard('gte', 40)),
});
const result = await client.inference.backwardChain({ goal });

The SortBuilder class provides a fluent API for constructing CreateSortRequest objects.

import { SortBuilder } from '@kortexya/reasoninglayer';
const request = SortBuilder.create('employee')
.withId('550e8400-e29b-41d4-a716-446655440000') // optional deterministic UUID
.parent(personSortId) // add a parent sort
.parents([organizationMemberSortId]) // add multiple parents
.feature({
name: 'salary',
required: true,
constraint: { type: 'IntegerRange', value: { min: 0, max: 10000000 } },
})
.feature({ name: 'department', required: true })
.boundConstraint({
constraintType: 'upper',
target: 'end_date',
sourcePath: 'company.dissolution_date',
})
.description('An employee at a company')
.build();
const sort = await client.sorts.createSort(request);
Method Description
SortBuilder.create(name) Start building a sort with the given name
.withId(uuid) Set a deterministic UUID
.parent(parentId) Add a single parent sort UUID
.parents(parentIds) Add multiple parent sort UUIDs
.feature(descriptor) Add a feature descriptor
.boundConstraint(constraint) Add an inter-feature bound constraint
.description(text) Set a human-readable description
.build() Build the CreateSortRequest

The allen() function creates Allen temporal constraint inputs (ConstraintInputDto of type "Allen").

import { allen } from '@kortexya/reasoninglayer';
// Event A must happen before Event B
allen('before', 'event_a_time', 'event-b-term-uuid');
// {"type": "Allen", "intervalA": "event_a_time", "intervalBTermId": "event-b-term-uuid", "relation": "before"}
// Meeting overlaps with work hours
allen('overlaps', 'meeting_time', 'work-hours-term-uuid');
// Lecture starts at the same time as the exam
allen('starts', 'lecture_time', 'exam-term-uuid');
Relation Inverse Meaning
before after A ends before B starts
meets met_by A ends exactly when B starts
overlaps overlapped_by A starts before B, ends during B
starts started_by Same start time, A ends first
during contains A entirely within B
finishes finished_by Same end time, A starts later
equals equals Same start and end

Using Allen constraints in backward chaining

Section titled “Using Allen constraints in backward chaining”

Allen constraints can be passed in the constraints field of a BackwardChainRequest:

const result = await client.inference.backwardChain({
goal: psi('scheduled_event', {
name: '?Event',
time: '?Time',
}),
constraints: [allen('before', '?Time', meetingTermId), allen('after', '?Time', lunchTermId)],
});

Here is a complete example that uses multiple builder namespaces together:

import {
ReasoningLayerClient,
SortBuilder,
Value,
FuzzyShape,
psi,
constrained,
guard,
} from '@kortexya/reasoninglayer';
const client = new ReasoningLayerClient({
baseUrl: 'https://platform.ovh.reasoninglayer.ai',
tenantId: 'your-tenant-uuid',
auth: { mode: 'cookie' },
});
// 1. Create sorts (using SortBuilder)
const personSort = await client.sorts.createSort(
SortBuilder.create('person')
.feature({ name: 'name', required: true })
.feature({ name: 'age', required: false, constraint: { type: 'Integer' } })
.build(),
);
const employeeSort = await client.sorts.createSort(
SortBuilder.create('employee')
.parent(personSort.id)
.feature({
name: 'salary',
required: true,
constraint: { type: 'IntegerRange', value: { min: 0, max: 10000000 } },
})
.build(),
);
// 2. Create terms (using plain values — tagged format auto-converted)
await client.terms.createTerm({
sortId: personSort.id,
ownerId: 'user-uuid',
features: {
name: 'Alice',
age: 30,
},
});
// 3. Add inference facts (using psi() — untagged format)
await client.inference.addFact({
term: psi('employee', {
name: 'Alice',
salary: 120000,
}),
});
// 4. Add rules with guards
await client.inference.addRule({
term: psi('high_earner', {
name: '?Name',
}),
antecedents: [
psi('employee', {
name: '?Name',
salary: constrained('?Salary', guard('gt', 100000)),
}),
],
});
// 5. Query (using backward chaining)
const result = await client.inference.backwardChain({
goal: psi('high_earner', {
name: '?Name',
}),
});
for (const solution of result.solutions) {
for (const binding of solution.substitution.bindings) {
console.log(`${binding.variableName} = ${binding.boundToDisplay}`);
}
}