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.
Which builder to use where
Section titled “Which builder to use where”| 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 valuesValue.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": [...], ...}}Complete variant reference
Section titled “Complete variant reference”| 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.
FuzzyShape
Section titled “FuzzyShape”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 cFuzzyShape.triangular(20, 22, 24);// {"kind": "Triangular", "a": 20, "b": 22, "c": 24}
// Trapezoidal: flat top between b and cFuzzyShape.trapezoidal(18, 20, 24, 26);// {"kind": "Trapezoidal", "a": 18, "b": 20, "c": 24, "d": 26}
// Gaussian: bell curve centered at meanFuzzyShape.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 directlypsi('person', { name: 'Alice', age: 30, active: true });// {"sortName": "person", "features": {"name": "Alice", "age": 30, "active": true}}
// Variables — strings starting with "?" are auto-detected as variablespsi('person', { name: '?Name', age: '?Age' });
// Nested psi-termspsi('employee', { person: psi('person', { name: 'Bob' }), salary: 100000,});
// With constrained variablepsi('employee', { salary: constrained('?Salary', guard('gt', 50000)),});
// Term reference — strings starting with "!" are auto-detected as referencespsi('department', { manager: '!550e8400-...' });
// Uninstantiated values — null is acceptedpsi('employee', { name: 'Alice', salary: null });
// No featurespsi('person');// {"sortName": "person"}Coercion rules
Section titled “Coercion rules”| 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) |
constrained
Section titled “constrained”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 guardconstrained('?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 constraintguard('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 comparisonguard('eq', 'active'); // string comparisonguard('ne', false); // boolean comparisonCombining guard with inference
Section titled “Combining guard with inference”import { psi, constrained, guard } from '@kortexya/reasoninglayer';
// Find high earners over 40const goal = psi('employee', { name: '?Name', salary: constrained('?Salary', guard('gt', 100000)), age: constrained('?Age', guard('gte', 40)),});
const result = await client.inference.backwardChain({ goal });SortBuilder
Section titled “SortBuilder”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);SortBuilder methods
Section titled “SortBuilder methods”| 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 Ballen('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 hoursallen('overlaps', 'meeting_time', 'work-hours-term-uuid');
// Lecture starts at the same time as the examallen('starts', 'lecture_time', 'exam-term-uuid');The 13 Allen relations
Section titled “The 13 Allen relations”| 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)],});Complete example: combining builders
Section titled “Complete example: combining builders”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 guardsawait 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}`); }}