Values and Formats
The Reasoning Layer backend uses two different serialization formats for values depending on the endpoint. Understanding when to use each format is the single most important thing to get right when working with this SDK.
The two formats
Section titled “The two formats”Tagged format (ValueDto)
Section titled “Tagged format (ValueDto)”Used by term CRUD, queries, and fuzzy operations. Values carry an explicit "type" discriminator:
{"type": "String", "value": "hello"}{"type": "Integer", "value": 42}{"type": "Real", "value": 3.14}{"type": "Boolean", "value": true}{"type": "Uninstantiated"}{"type": "Reference", "value": "550e8400-e29b-41d4-a716-446655440000"}{"type": "List", "value": [{"type": "Integer", "value": 1}, {"type": "Integer", "value": 2}]}For simple types, pass plain JS values directly — the SDK auto-converts them:
// Plain values (no builder needed — auto-converted by SDK):const features = { name: 'Alice', // -> {"type": "String", "value": "Alice"} age: 30, // -> {"type": "Integer", "value": 30} score: 0.95, // -> {"type": "Real", "value": 0.95} active: true, // -> {"type": "Boolean", "value": true} pending: null, // -> {"type": "Uninstantiated"}};For complex types that have no plain JS equivalent, use the Value namespace:
import { Value } from '@kortexya/reasoninglayer';
const complexFeatures = { mentor: Value.reference(mentorId), // {"type": "Reference", "value": "uuid"}};Untagged format (FeatureInputValueDto)
Section titled “Untagged format (FeatureInputValueDto)”Used by homoiconic inference endpoints (backward chaining, forward chaining, fuzzy proving, etc.). Values are raw JSON primitives with no discriminator:
"hello"423.14truenull["550e8400-...", "660f9500-..."]Build untagged inputs with psi() and plain JS values:
import { psi, constrained, guard } from '@kortexya/reasoninglayer';
// Plain values used directly inside psi():const fact = psi('person', { name: 'Alice', // "Alice" age: 30, // 30 score: 0.95, // 0.95 active: true, // true});
// Variables — strings starting with "?" are auto-detected:const query = psi('person', { name: '?Name', // {name: "?Name"} role: '?Role', // {name: "?Role"}});
// Term references — strings starting with "!" are auto-detected:const withRef = psi('team', { lead: '!550e8400-...', // {termId: "550e8400-..."}});The untagged format also supports structural variants:
// Constrained variableconstrained('?Salary', guard('gt', 50000));// {name: "?Salary", constraint: {sortName: "guard_constraint", features: {op: "gt", right: 50000}}}
// Inline term by sort name (nested psi-terms)psi('address', { city: 'Paris',})[ // {sortName: "address", features: {city: "Paris"}}
// List ('a', 'b')];// ["a", "b"]Which format for which endpoint
Section titled “Which format for which endpoint”| Endpoint domain | Format | Builder | Type |
|---|---|---|---|
| Term CRUD | Tagged | Plain values or Value.* |
ValueDto |
| Queries | Tagged | Plain values or Value.* |
ValueDto |
| Fuzzy operations | Tagged | Plain values or Value.* |
ValueDto |
| Backward chaining | Untagged | psi(), plain values |
FeatureInputValueDto |
| Forward chaining | Untagged | psi(), plain values |
FeatureInputValueDto |
| Add rule | Untagged | psi(), plain values |
FeatureInputValueDto |
| Add fact | Untagged | psi(), plain values |
FeatureInputValueDto |
| Fuzzy proving | Untagged | psi(), plain values |
FeatureInputValueDto |
| Bayesian prediction | Untagged | psi(), plain values |
FeatureInputValueDto |
| NAF proving | Untagged | psi(), plain values |
FeatureInputValueDto |
The rule of thumb: client.terms.* and client.query.* use plain values or Value.*. client.inference.* uses psi() with plain values.
FuzzyShapeDto uses “kind”, not “type”
Section titled “FuzzyShapeDto uses “kind”, not “type””Fuzzy membership function shapes use "kind" as their discriminator field, not "type". This is different from every other tagged union in the API:
{"kind": "Triangular", "a": 20, "b": 22, "c": 24}{"kind": "Trapezoidal", "a": 18, "b": 20, "c": 24, "d": 26}{"kind": "Gaussian", "mean": 100, "stdDev": 15}{"kind": "CyclicGaussian", "mean": 180, "stdDev": 30, "period": 360}Build fuzzy shapes with the FuzzyShape namespace and combine with Value.fuzzyNumber():
import { Value, FuzzyShape } from '@kortexya/reasoninglayer';
const temperature = Value.fuzzyNumber(FuzzyShape.triangular(20, 22, 24));// {"type": "FuzzyNumber", "value": {"shape": {"kind": "Triangular", "a": 20, "b": 22, "c": 24}}}
const iq = Value.fuzzyNumber(FuzzyShape.gaussian(100, 15));// {"type": "FuzzyNumber", "value": {"shape": {"kind": "Gaussian", "mean": 100, "stdDev": 15}}}Common mistakes
Section titled “Common mistakes”Mistake 1: Using Value builders in inference requests
Section titled “Mistake 1: Using Value builders in inference requests”// WRONG: Value.reference() produces tagged format, but inference expects untaggedawait client.inference.addFact({ term: psi('person', { name: Value.reference('some-uuid'), // This produces {"type": "Reference", "value": "uuid"} }), // The backend expects just {termId: "uuid"}});
// CORRECT: Use plain values or "!" prefix for inferenceawait client.inference.addFact({ term: psi('person', { name: 'Alice', // Plain string is used directly }),});Mistake 2: Using psi() in term CRUD
Section titled “Mistake 2: Using psi() in term CRUD”// WRONG: psi() is for inference, not term CRUDawait client.terms.createTerm({ sortId: personSortId, ownerId: userId, features: { name: psi('person'), // This is a PsiTermInput, not a ValueDto },});
// CORRECT: Use plain values for term CRUDawait client.terms.createTerm({ sortId: personSortId, ownerId: userId, features: { name: 'Alice', // Auto-converted to {"type": "String", "value": "Alice"} },});Mistake 3: Confusing “kind” with “type” on fuzzy shapes
Section titled “Mistake 3: Confusing “kind” with “type” on fuzzy shapes”// WRONG: Using "type" instead of "kind" for fuzzy shapesconst shape = { type: 'Triangular', a: 20, b: 22, c: 24 };
// CORRECT: FuzzyShapeDto uses "kind" discriminatorconst shape = FuzzyShape.triangular(20, 22, 24);// Produces: { kind: "Triangular", a: 20, b: 22, c: 24 }Value fallback stringification
Section titled “Value fallback stringification”The backend may convert domain-internal value types (BigInteger, DateTime, Geometry, Measurement, etc.) to ValueDto::String using Rust’s debug format. When reading term features, you may encounter strings like:
"DateTime(2024-01-15T10:30:00Z)""BigInteger(99999999999999999999)""Geometry(Point(1.0, 2.0))"These are not first-class ValueDto variants. They are the result of a fallback String(format!("{:?}", value)) conversion in the backend. If you encounter these patterns, the underlying value was a domain type that the SDK does not model with a dedicated variant.
All ValueDto variants
Section titled “All ValueDto variants”The tagged ValueDto union has 10 variants:
| Variant | Builder | JSON output |
|---|---|---|
| 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": {"value": 0.5, "membership": 0.8}} |
| FuzzyNumber | Value.fuzzyNumber(shape) |
{"type": "FuzzyNumber", "value": {"shape": {...}}} |
| Set | Value.set(lower, upper, sort?) |
{"type": "Set", "value": {"lower": [...], "upper": [...], "sortConstraint": ...}} |
All FeatureInputValueDto variants
Section titled “All FeatureInputValueDto variants”The untagged FeatureInputValueDto union has 10 structural variants. The ordering matches the Rust serde(untagged) deserialization order:
| # | Variant | Builder / value | JSON output |
|---|---|---|---|
| 1 | TermRef | "!uuid" in psi() or {termId: "uuid"} |
{"termId": "uuid"} |
| 2 | ConstrainedVariable | constrained("?X", constraint) |
{"name": "?X", "constraint": {...}} |
| 3 | Variable | "?X" in psi() |
{"name": "?X"} |
| 4 | InlineTerm | {sortId: "uuid", features: {...}} |
{"sortId": "uuid", "features": {...}} |
| 5 | InlineTermByName | psi("person", features) |
{"sortName": "person", "features": {...}} |
| 6 | List | [...] |
[...] |
| 7 | String | "hello" |
"hello" |
| 8 | Integer | 42 |
42 |
| 9 | Real | 3.14 |
3.14 |
| 10 | Boolean | true |
true |
The ConstrainedVariable variant must come before Variable in the deserialization order because both have a name field, but the constrained version also has constraint. The SDK builders ensure correct construction.