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.

Which builder to use where

ContextBuilder to useProduces
Term CRUD featuresPlain JS values or Value.*Tagged ValueDto
Query featuresPlain JS values or Value.*Tagged ValueDto
Fuzzy operation featuresPlain JS values or Value.*Tagged ValueDto
Fuzzy membership shapesFuzzyShape.*FuzzyShapeDto
Inference rule/fact inputspsi(), plain valuesUntagged TermInputDto
Guard constraintsguard()Untagged TermInputDto
Constrained variablesconstrained()ConstrainedPlainVar
Sort creationSortBuilderCreateSortRequest
Psi-term shorthandpsi()PsiTermInput
Allen temporal constraintsallen()Tagged ConstraintInputDto
LP optimizationLP.*LinearProgramDefinition

Value

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": [...], ...}}

Complete variant reference

MethodReturn typeJSON "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

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)),
};

psi

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"}

Coercion rules

JavaScript valueCoerced to
stringRaw string
"?X" (starts with ?)Variable (auto-detected)
"!uuid" (starts with !)Term reference (auto-detected)
numberRaw number
booleanRaw boolean
nullUninstantiated
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

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)),
})

guard

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):

OpMeaning
"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

Combining guard with inference

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 });

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

MethodDescription
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

allen

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")

The 13 Allen relations

RelationInverseMeaning
beforeafterA ends before B starts
meetsmet_byA ends exactly when B starts
overlapsoverlapped_byA starts before B, ends during B
startsstarted_bySame start time, A ends first
duringcontainsA entirely within B
finishesfinished_bySame end time, A starts later
equalsequalsSame start and end

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

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}`);
}
}