Terms and Features
Terms are the core data structures of the Reasoning Layer. A term is a typed feature structure: it belongs to a sort and carries named features with values.
What is a term?
Section titled “What is a term?”A term has:
- A sort (its type in the lattice)
- Named features with typed values
- An identity (UUID) assigned by the server
- A validation state indicating whether it satisfies its sort’s type witnesses
The key insight is that rules, facts, goals, and constraints are all represented as terms — data and logic share the same structure.
TermDto and TermResponse
Section titled “TermDto and TermResponse”Term CRUD endpoints return TermResponse, not raw TermDto. This is a critical distinction. TermResponse wraps the term with validation state:
interface TermResponse { term: TermDto; // The underlying term data state?: TermState; // Validation state witnessProofs?: WitnessProofDto[]; // Proofs of satisfied witnesses residualWitnesses?: ResidualWitnessDto[]; // Suspended witnesses}
interface TermDto { id: string; // Term UUID sortId: string; // Sort (type) UUID tenantId: string; // Owning tenant UUID ownerId: string; // Owner user UUID features: Record<string, ValueDto>; // Named features (tagged format)}Note that TermDto.features uses the tagged ValueDto format ({"type": "String", "value": "hello"}). For the untagged format used in inference, see Values and Formats.
Creating terms
Section titled “Creating terms”Use plain JS values to construct feature values — the SDK auto-converts them to the tagged format:
import { Value, ReasoningLayerClient } from '@kortexya/reasoninglayer';
const client = new ReasoningLayerClient({ baseUrl: 'https://platform.ovh.reasoninglayer.ai', tenantId: 'your-tenant-uuid', auth: { mode: 'cookie' },});
const response = await client.terms.createTerm({ sortId: personSortId, ownerId: 'user-uuid', features: { name: 'Alice', age: 30, active: true, score: 0.95, supervisor: Value.reference(supervisorTermId), skills: ['TypeScript', 'Rust'], pending_review: null, },});
// response is TermResponse, not TermDtoconsole.log(response.term.id); // Term UUIDconsole.log(response.state); // "complete" | "residuated" | "no_witnesses"console.log(response.term.features); // Features with tagged ValueDto valuesTerm state
Section titled “Term state”The TermState field on TermResponse indicates whether the term satisfies its sort’s type witnesses:
| State | Meaning |
|---|---|
"complete" |
All type witnesses are satisfied |
"residuated" |
Some witnesses are suspended, awaiting more information |
"no_witnesses" |
The sort has no witness requirements |
Witness proofs
Section titled “Witness proofs”When a term satisfies its type witnesses, TermResponse includes proof objects:
interface WitnessProofDto { witnessId: string; // ID of the satisfied witness bindings: Record<string, string>; // Variable bindings that satisfy it certainty: number; // Confidence (0.0 to 1.0)}Example:
const response = await client.terms.createTerm({ sortId: employeeSortId, ownerId: userId, features: { name: 'Bob', salary: 85000, },});
if (response.witnessProofs) { for (const proof of response.witnessProofs) { console.log(`Witness ${proof.witnessId} satisfied with certainty ${proof.certainty}`); console.log('Bindings:', proof.bindings); }}Residuation
Section titled “Residuation”Residuation is the suspension of computation when information is missing. It is not deferral, lazy evaluation, or an error. It is the system’s way of saying “I cannot determine this yet, but I will revisit it when the missing information arrives.”
When witnesses residuate, TermResponse includes residual witness objects:
interface ResidualWitnessDto { witnessId: string; // The witness that could not be satisfied partialBindings: Record<string, string>; // Bindings found so far trigger: string; // What would trigger re-evaluation}const response = await client.terms.getTerm(termId);
if (response.state === 'residuated' && response.residualWitnesses) { for (const residual of response.residualWitnesses) { console.log(`Witness ${residual.witnessId} is waiting for: ${residual.trigger}`); console.log('Partial bindings:', residual.partialBindings); }}Updating terms
Section titled “Updating terms”Only the provided features are updated; omitted features remain unchanged:
const updated = await client.terms.updateTerm(termId, { features: { salary: 95000, department: 'Engineering', },});Checking existence and deletion
Section titled “Checking existence and deletion”const exists = await client.terms.termExists(termId);// returns true or false
await client.terms.deleteTerm(termId);Bulk creation
Section titled “Bulk creation”Create multiple terms in a single request:
const result = await client.terms.bulkCreateTerms({ terms: [ { sortId: personSortId, ownerId: userId, features: { name: 'Alice', age: 30 }, }, { sortId: personSortId, ownerId: userId, features: { name: 'Bob', age: 25 }, }, ],});
// result.termIds: string[] — UUIDs in the same order as the request// result.processingTimeMs: numberFeature value types
Section titled “Feature value types”Features on TermDto use the tagged ValueDto format. The 10 variants are:
| Variant | Builder | JSON |
|---|---|---|
| String | "hello" (plain) |
{"type": "String", "value": "hello"} |
| Integer | 42 (plain) |
{"type": "Integer", "value": 42} |
| Real | 3.14 (plain) |
{"type": "Real", "value": 3.14} |
| Boolean | true (plain) |
{"type": "Boolean", "value": true} |
| Uninstantiated | null (plain) |
{"type": "Uninstantiated"} |
| Reference | Value.reference(uuid) |
{"type": "Reference", "value": "uuid"} |
| List | [...] (plain) |
{"type": "List", "value": [...]} |
| FuzzyScalar | Value.fuzzyScalar(0.5, 0.8) |
{"type": "FuzzyScalar", "value": {...}} |
| FuzzyNumber | Value.fuzzyNumber(shape) |
{"type": "FuzzyNumber", "value": {"shape": {...}}} |
| Set | Value.set(lower, upper) |
{"type": "Set", "value": {"lower": [...], ...}} |
For a detailed comparison of tagged vs. untagged formats, see Values and Formats.
Features on a listed rule or derived fact
Section titled “Features on a listed rule or derived fact”The inference surfaces (getRules(), forward and backward chaining, fact listings) return a
PsiTermDto whose features use the untagged FeatureValueDto read format, exactly as
the engine sends it. An object value is one of five shapes:
| Shape | Meaning |
|---|---|
{term_id, var_name} |
A logic variable (?N) |
{term_id, literal} |
An interned literal, such as a guard’s bound |
{term_id, sort_id, sort_name, features, ...} |
A whole term, inlined |
{sort_ref, sort_name?} |
A sort named as a value |
{term_id} |
A reference to another term |
A sort value used to arrive as {term_id}. It now arrives as {sort_ref, sort_name?};
sort_name is absent when your lattice does not hold the sort. Use isSortRefFeatureValue()
to tell a sort from a term, and discriminateFeatureValue() to narrow an unknown value.
The write format is different ({ sortRef }, { termId }), so convert a listed value
before you send it back — for example to rebuild a rule for replaceRule:
import { featureValueToInput } from '@kortexya/reasoninglayer';
const features = Object.fromEntries( Object.entries(rule.head.features).map(([name, v]) => [name, featureValueToInput(v)]),);A rule’s certainty is not a head feature; read RuleEntryDto.certainty. The display
text shows every data feature, sorted by name, in full.