Skip to content

Creating Sorts

Sorts are the type system of the Reasoning Layer. They form a lattice with multiple inheritance, feature declarations, and inter-feature constraints. Every Psi-term (data instance) belongs to a sort.

Use client.sorts.createSort() to create a sort. The method accepts a CreateSortRequest and returns the created SortDto.

import { ReasoningLayerClient } from '@kortexya/reasoninglayer';
const client = new ReasoningLayerClient({
baseUrl: 'https://platform.ovh.reasoninglayer.ai',
tenantId: 'my-tenant-uuid',
auth: { mode: 'cookie' },
});
const person = await client.sorts.createSort({
name: 'person',
description: 'A human being',
});
console.log(person.id); // UUID assigned by the server
console.log(person.name); // "person"

The SortBuilder provides a fluent API for constructing complex sort definitions with features, parents, and constraints.

import { SortBuilder } from '@kortexya/reasoninglayer';
const request = SortBuilder.create('employee')
.parent(personSortId)
.feature({
name: 'salary',
required: true,
constraint: { type: 'IntegerRange', value: { min: 0, max: 10_000_000 } },
})
.feature({
name: 'department',
required: true,
expectedSort: 'department',
})
.feature({
name: 'email',
required: false,
constraint: { type: 'StringPattern', value: '^[^@]+@[^@]+$' },
})
.feature({
name: 'bio',
required: false,
description: 'A short biography, written by the employee',
index: 'text_and_vector',
})
.boundConstraint({
constraintType: 'upper',
target: 'endDate',
sourcePath: 'company.dissolutionDate',
})
.description('An employee at a company')
.build();
const employee = await client.sorts.createSort(request);

A feature description is prose for semantic embeddings and documentation. A feature index states how the feature takes part in the retrieval indexes: not_indexed, text, vector or text_and_vector. When you create a sort that already exists, a feature that does not state description or index keeps its stored value.

You can assign a specific UUID to a sort, which is useful for homoiconic cross-references between sorts, rules, and facts.

const request = SortBuilder.create('sensor_reading')
.withId('550e8400-e29b-41d4-a716-446655440000')
.feature({ name: 'value', required: true })
.build();

Sorts support multiple parents. Call .parent() multiple times or use .parents() with an array.

const request = SortBuilder.create('teaching_assistant')
.parent(studentSortId)
.parent(staffSortId)
.feature({ name: 'courses_assisting', required: true })
.build();
// Equivalent:
const request2 = SortBuilder.create('teaching_assistant')
.parents([studentSortId, staffSortId])
.feature({ name: 'courses_assisting', required: true })
.build();

When defining an entire hierarchy at once, use bulkCreateSorts. Unlike createSort, parent references use sort names (not UUIDs), which are resolved server-side.

const result = await client.sorts.bulkCreateSorts({
sorts: [
{ name: 'animal', description: 'Base animal sort' },
{ name: 'mammal', parents: ['animal'] },
{ name: 'bird', parents: ['animal'] },
{ name: 'bat', parents: ['mammal'], description: 'A flying mammal' },
{
name: 'penguin',
parents: ['bird'],
features: [{ name: 'colony_size', required: false }],
},
],
});
console.log(result.createdCount); // 5
console.log(result.sortIds); // { animal: "uuid1", mammal: "uuid2", ... }
// Check for partial failures
if (result.errors && result.errors.length > 0) {
for (const err of result.errors) {
console.error(`Failed to create ${err.name}: ${err.error}`);
}
}

The Greatest Lower Bound (GLB) is the most specific common sort of two sorts. The Least Upper Bound (LUB) is the most general common supersort.

// Compute GLB
const glb = await client.sorts.computeGlb({
sort1Id: employeeSortId,
sort2Id: customerSortId,
});
if (glb.glb) {
console.log(`GLB sort: ${glb.glb}`);
} else {
console.log('No common lower bound exists');
}
// Compute LUB
const lub = await client.sorts.computeLub({
sort1Id: employeeSortId,
sort2Id: customerSortId,
});
if (lub.lub) {
console.log(`LUB sort: ${lub.lub}`);
}

When the GLB does not correspond to a single named sort, decodeGlb returns a human-readable disjunction.

const decoded = await client.sorts.decodeGlb({
sort1Id: sortA,
sort2Id: sortB,
});
console.log(decoded.formatted); // "int | real"
console.log(decoded.isBottom); // false
console.log(decoded.sortNames); // ["int", "real"]
console.log(decoded.sortIds); // ["uuid1", "uuid2"]

You can also use convenience aliases with simpler positional arguments:

const glb = await client.sorts.findCommonSubtype(employeeSortId, customerSortId);
const lub = await client.sorts.findCommonSupertype(employeeSortId, customerSortId);
const decoded = await client.sorts.explainCommonSubtype(sortA, sortB);

Compare two sorts using one of six comparison operators.

// Check if employee is a subtype of person
const result = await client.sorts.compareSorts({
operator: 'isa_le',
sortA: 'employee',
sortB: 'person',
});
console.log(result.result); // true

Available operators:

Operator Meaning
isa_le A is a subtype of or equal to B
isa_lt A is a strict subtype of B
isa_ge A is a supertype of or equal to B
isa_gt A is a strict supertype of B
isa_eq A and B are the same sort
isa_cmp General comparison (returns comparison result)

There is also a convenience method for subtype checking:

const isSub = await client.sorts.isSubtype(employeeSortId, personSortId);
console.log(isSub); // true

Set and retrieve fuzzy similarity between two sorts. Similarity is a symmetric value from 0.0 to 1.0.

// Set similarity between two sorts
await client.sorts.setSortSimilarity({
sort1Id: catSortId,
sort2Id: dogSortId,
degree: 0.7,
});
// Retrieve similarity
const sim = await client.sorts.getSortSimilarity({
sort1Id: catSortId,
sort2Id: dogSortId,
});
console.log(sim.degree); // 0.7
// Direct children
const children = await client.sorts.getChildren(animalSortId);
// Direct parents
const parents = await client.sorts.getParents(mammalSortId);
// All ancestors (transitive)
const ancestors = await client.sorts.getAncestors(batSortId);
// All descendants (transitive)
const descendants = await client.sorts.getDescendants(animalSortId);
// Compatible sorts
const compatible = await client.sorts.getCompatible(mammalSortId);
// List all sorts in the tenant
const allSorts = await client.sorts.listSorts();
// Delete a sort
await client.sorts.deleteSort(sortId);

A delete refuses by default when the sort holds data terms, when a rule names it, or when a child sort would be left with no parent. Each of the three has its own option, and each option defaults to refuse. Preview the delete first to see what it would remove and why it would be refused. The preview removes nothing.

import { sortDeleteRefusalOf } from '@kortexya/reasoninglayer';
const options = { terms: 'delete', descendants: 'delete' } as const;
const preview = await client.sorts.previewSortDelete(animalSortId, options);
if (preview.deletable) {
// Removes the sort and every sort below it, leaves first, with their terms.
const removed = await client.sorts.deleteSort(animalSortId, options);
console.log(removed?.sortsDeleted);
} else {
console.log(preview.refusal?.code, preview.refusal?.message);
}
// A refused delete throws an ApiError with a coded body.
try {
await client.sorts.deleteSort(mammalSortId);
} catch (error) {
console.log(sortDeleteRefusalOf(error)?.code); // e.g. 'sort_would_orphan_children'
}

reparentSort replaces the parent set of a sort. The sort keeps its id, its features and its subtree. The response reports the inherited features that each moved sort gained and lost.

const moved = await client.sorts.reparentSort(batSortId, {
parents: [mammalSortId, flyingSortId],
});
console.log(moved.previousParents, moved.parents, moved.inheritanceChanges);

The OSFQL form is ALTER SORT bat PARENTS (mammal, flying);.