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?
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
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
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
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
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
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
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
const exists = await client.terms.termExists(termId);// returns true or false
await client.terms.deleteTerm(termId);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
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.