Skip to content

Working with Terms

Terms are the data instances of the Reasoning Layer. Each term belongs to a sort (type) and carries named features with typed values. Terms use the tagged ValueDto serialization format.

Use plain JavaScript values for feature values. The SDK handles serialization to the tagged wire format automatically.

import { ReasoningLayerClient } from '@kortexya/reasoninglayer';
const client = new ReasoningLayerClient({
baseUrl: 'https://platform.ovh.reasoninglayer.ai',
tenantId: 'my-tenant-uuid',
auth: { mode: 'cookie' },
});
const response = await client.terms.createTerm({
sortId: personSortId,
ownerId: 'user-uuid',
features: {
name: 'Alice',
age: 30,
height: 1.72,
active: true,
},
});

Term CRUD endpoints return TermResponse, not raw TermDto. The response includes validation state from the sort’s type witnesses.

const response = await client.terms.createTerm({
sortId: sortId,
ownerId: 'user-uuid',
features: { name: 'Bob' },
});
// The term data
const term = response.term;
console.log(term.id); // UUID
console.log(term.sortId); // sort UUID
console.log(term.features); // { name: { type: "String", value: "Bob" } }
// Validation state
console.log(response.state); // "complete" | "residuated" | "no_witnesses"
if (response.state === 'residuated') {
// Some witnesses could not be fully evaluated yet
for (const rw of response.residualWitnesses ?? []) {
console.log(`Witness ${rw.witnessId} waiting on: ${rw.trigger}`);
}
}
if (response.state === 'complete') {
// All witnesses satisfied
for (const wp of response.witnessProofs ?? []) {
console.log(`Witness ${wp.witnessId} proved with certainty ${wp.certainty}`);
}
}

The three states:

State Meaning
complete All type witnesses are satisfied
residuated Some witnesses are suspended (missing information)
no_witnesses The sort has no witness requirements

For primitive types, use plain JavaScript values directly: strings, numbers, booleans, null (for uninstantiated), and arrays. The Value namespace is only needed for special types that cannot be expressed as plain JS values.

import { Value, FuzzyShape } from '@kortexya/reasoninglayer';
// References to other terms
Value.reference(termId); // {"type": "Reference", "value": "uuid"}
// Fuzzy values
Value.fuzzyScalar(0.5, 0.8);
Value.fuzzyNumber(FuzzyShape.triangular(20, 22, 24));
// Set values (Smyth powerdomain)
Value.set(
['definite-member-uuid'], // lower bound
['definite-uuid', 'possible-uuid'], // upper bound
'sort-constraint-id', // optional sort constraint
);

For primitive types (strings, numbers, booleans, null, arrays), pass plain JavaScript values directly – no builder needed. The Value namespace is only required for reference, fuzzyScalar, fuzzyNumber, and set, which have no plain JS equivalent.

updateTerm applies a partial update. Only the features you provide are changed; omitted features remain unchanged.

const updated = await client.terms.updateTerm(termId, {
features: {
age: 31,
email: 'alice@example.com',
},
});
console.log(updated.term.features);
// age is now 31, email is added, name is unchanged
// Get a term by ID
const response = await client.terms.getTerm(termId);
const term = response.term;
// Check if a term exists
const exists = await client.terms.termExists(termId);
// Delete a term
await client.terms.deleteTerm(termId);
// List all terms for the tenant
const allTerms = await client.terms.listTerms();
// Clear all terms
await client.terms.clearTerms();

Create many terms at once with bulkCreateTerms.

const result = await client.terms.bulkCreateTerms({
terms: [
{
sortId: personSortId,
ownerId: 'user-uuid',
features: {
name: 'Alice',
age: 30,
},
},
{
sortId: personSortId,
ownerId: 'user-uuid',
features: {
name: 'Bob',
age: 25,
},
},
{
sortId: personSortId,
ownerId: 'user-uuid',
features: {
name: 'Charlie',
age: 40,
},
},
],
});
console.log(result.termIds); // ["uuid1", "uuid2", "uuid3"]
console.log(result.processingTimeMs); // 12

Terms can reference other terms using Value.reference().

// Create a department
const dept = await client.terms.createTerm({
sortId: departmentSortId,
ownerId: 'user-uuid',
features: {
name: 'Engineering',
},
});
// Create an employee referencing the department
const emp = await client.terms.createTerm({
sortId: employeeSortId,
ownerId: 'user-uuid',
features: {
name: 'Alice',
department: Value.reference(dept.term.id),
},
});

When a sort declares a feature’s range as an entity sort (department: department), that feature takes a reference. The engine refuses a scalar literal there with a 422, on every term write door (createTerm, updateTerm, bulkCreateTerms, OSFQL writes, actions.applyAction and inference.addFact):

Feature 'department' holds a string literal, which is incompatible with the declared range 'department' — write a reference to a 'department' term

Do not send the id as a string (department: dept.term.id). Send Value.reference(dept.term.id). A range at or below real_type admits an integer literal.

In an OSFQL write, a nested <sort>(__osf_term_id: "<id>") designates the existing term with that id. The engine stores a reference to it and mints nothing:

await client.osfql.execute(
`INSERT employee(name: "Alice", department: department(__osf_term_id: "${dept.term.id}"));`,
);

The engine refuses the write, before it mints anything, when the id is not a UUID, names no term, names a term the caller cannot see, or names a term outside the nested pattern (here, a term that is not a department). In a MATCH, the same nested pattern filters on that identity.

Use null for features whose value is not yet known. The system supports residuation – suspending computation until the value becomes available.

const partialTerm = await client.terms.createTerm({
sortId: personSortId,
ownerId: 'user-uuid',
features: {
name: 'Alice',
age: null, // unknown for now
},
});
// Later, fill in the missing value
await client.terms.updateTerm(partialTerm.term.id, {
features: {
age: 30,
},
});