Documentation Index
Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt
Use this file to discover all available pages before exploring further.
This page is the operator-facing reference for every configurable field on every Decision Flow node. For each knob you get:
- Purpose — what the field controls.
- Engine behavior — exactly what the engine does when this value changes.
- Verification — how to prove the field had an effect (test script, doc cross-link, or live API call).
Where a field is covered by an automated test we link the script. Where it’s only documented or only tested end-to-end through the Studio UI we say so honestly.
Phase 1 — Narrow
Inventory node
| Field | Purpose | Engine behavior | Verification |
|---|
scope | Constrain candidate set by category / subcategory / explicit offer list. | WHERE clause on the Offer query in pipeline-runner.ts case "inventory". | T1 baseline recommend. |
categoryIds[] | Restrict to specific categories. | category_id IN (...) predicate. | T1, T2. |
subCategoryIds[] | Restrict to specific sub-categories. | sub_category_id IN (...) predicate. | T1. |
offerIds[] | Whitelist specific offers (bypasses category scope). | offer.id IN (...) predicate. | UI-tested. |
includeStatuses[] | Default ["active"]; allow staged/test offers in development flows. | Status filter on the Offer query. | UI-tested. |
Match Creatives node
| Field | Purpose | Engine behavior | Verification |
|---|
requireCreative | Drop candidates with no creative. | Filter candidate.creative != null after Inventory. | T1. |
placementMatchMode | exact keeps only creatives whose placementId matches request; any keeps wildcard creatives too; none skips placement filtering. | Branches in case "match_creatives" (pipeline-runner.ts:780). | T1, T8 (atomic coupling). |
Enrich node
| Field | Purpose | Engine behavior | Verification |
|---|
| Auto-enrich (implicit) | All active SchemaJoins with autoEnrich=true merge their joined customer rows before any Enrich node runs. | autoEnrichFromJoins() runs at line 590 of pipeline-runner.ts before nodes execute. | T14. |
sources[].schemaId | Manually attach a schema that isn’t auto-joined, or override a join with custom fields. | Reads the schema’s table, queries by lookupKey, prefixes results. | T14. |
sources[].lookupKey | FK column to use (default customer_id). | WHERE "<lookupKey>" = $customerId. | UI-tested. |
sources[].fields[] | Restrict to subset of columns (perf optimization). | Generates a SELECT col1, col2 ... projection. | UI-tested. |
sources[].prefix | Namespace prefix in enrichedData (default customer). | Output keys become <prefix>.<field>. | UI-tested. |
sources[].cacheTtlSeconds | Per-source Redis cache TTL. | Cache key enrich:<tenantId>:<schemaId>:<customerId>:<fields> — TTL set per source. | Logged but not asserted in tests. |
sources[].optional | Soft-fail when the schema or row is missing. | try/catch around the lookup; missing data → field not set instead of throwing. | UI-tested. |
sources[].multiRow + aggregation | Roll up multiple rows (e.g. last-30-day transactions) into one summary. | Triggers GROUP BY + aggregation function per field. | UI-tested. |
transforms[] | Apply record-level transforms after lookup (hash, mask_pii, expression, etc.). | Each transform mutates the enrichedData map in-place. | UI-tested. |
Qualify node
| Field | Purpose | Engine behavior | Verification |
|---|
mode = "all" | Apply every globally-scoped active QualificationRule. | Filters rules.filter(isGlobalRule). | T2. |
mode = "selected" | Apply only the explicit rule IDs (any scope — operator chose them). | rules.filter(r => ruleIdSet.has(r.id)). | T2. |
mode = "none" | Skip qualification entirely. | Early break. | UI-tested. |
qualificationRuleIds[] | The set of rule IDs when mode = "selected". | Consumed at line 879 of pipeline-runner.ts. | T2. |
logic (AND/OR groups) | Combinator tree for selected rules. | Recursively evaluates groups; AND requires all-pass, OR requires any-pass. | Logged finding — not exercised in current proofs. |
| Field | Purpose | Engine behavior | Verification |
|---|
mode | Same shape as Qualify: all (every active policy), selected (explicit list), none (skip). | filterByContactPolicies() consumes the filtered policy set. | T3 (cooldown), T4 (frequency cap), T5 (category suppression), T6 (DNC), T7 (metric_condition). |
contactPolicyIds[] | Explicit policy list when mode = "selected". | Same dispatch as Qualify. | T3–T7. |
Filter node
| Field | Purpose | Engine behavior | Verification |
|---|
conditions[].field | Path into enrichedData or candidate attributes. | Resolved via dot-path lookup. | UI-tested. |
conditions[].operator | One of 13: eq, neq, gt, gte, lt, lte, in, not_in, contains, starts_with, regex, is_null, is_not_null. | Pure boolean evaluation per candidate. | UI-tested. |
conditions[].value | RHS of comparison. | Type-checked at evaluation. | UI-tested. |
combinator | AND requires all conditions true; OR requires any. | Boolean reduction. | UI-tested. |
Conditional (split) node
| Field | Purpose | Engine behavior | Verification |
|---|
conditions[] + combinator | Same syntax as Filter — the predicate that decides which branch to take. | Evaluated per candidate. | UI-tested. |
trueBranchFlowId | Sub-flow to execute for matching candidates. | Loaded + executed via executePipelineV2 (subject to depth/cycle guards). | UI-tested. |
falseBranchFlowId | Sub-flow for non-matching candidates. | Same dispatch. Optional. | UI-tested. |
keepNonMatching | When false (default), non-matching candidates are filtered out. When true, they pass through to the next node. | Branches the rejection path. | UI-tested. |
label | Operator-facing label rendered on the canvas. | Display-only. | n/a. |
Phase 2 — Score & Rank
Score node — the most-configurable node in the flow
See Scoring Strategies for the operator-facing decision guide. Reference of every field:
| Field | Purpose | Engine behavior | Verification |
|---|
method | The base strategy: priority_weighted, propensity, or formula (PRIE). | Routes through one of three branches in pipeline-runner.ts:1734-1995. | T15 (3 sub-cases). |
modelKey / defaultModel | Which AlgorithmModel the propensity and formula methods use when no adaptation data is present. | Resolved by resolveScoreConfig() then loaded via prisma.algorithmModel.findFirst. | T15 sub-case 2. |
formula | Inline PRIE weights {propensityWeight, relevanceWeight, impactWeight, emphasisWeight}. Must sum to 1.0. | Used in case "formula" line 1879. Profile mapping overrides if strategyProfileId set. | T15 sub-case 3. |
overrides[] (per-scope ML model) | Pick a different AlgorithmModel per (offer, channel), per offer, per category, per subcategory, or per channel. | resolveModelForCandidate() walks the priority chain (default: offer_channel → offer → category → subcategory → channel → default). | T15 sub-case 8. |
overridePriority[] | Override the default scope priority. | Drives the loop in scoring-resolver.ts:70. | UI-tested. |
championChallenger.enabled | Activate A/B model routing. | selectVariantPersistent() deterministically routes per customerId based on weights. | UI-tested (Critical path #147). |
championChallenger.champion.{modelKey, weight} | The currently-serving model. | Weight gets normalized against challengers. | UI-tested. |
championChallenger.challengers[] | Models being tested at fractional traffic. | Weighted choice via FNV-1a hash of customerId + ":cc". | UI-tested. |
channelOverrides[] (deprecated, kept for compat) | Per-channel different method/model/formula. | resolveScoreConfig() consults this list first when channelId matches. | T15 sub-case 6. |
strategyProfileId | Default RankingProfile whose weights map to PRIE (conversion → Wp, recency → Wr, margin → Wi, fairness → We). | Replaces formula field when set; resolution at line 1857. | T15 sub-cases 4–5. |
strategyOverrides[] | First-match-wins profile selector by productType / category / channel. | Loops at line 1842; first matching scope wins. | T15 sub-case 7. |
shadowModelKeys[] | Models that ALSO score every candidate in parallel — recorded only, NEVER affects ranking. | applyShadowScoring() runs after the active path. | UI-tested. |
Engine-wide settings that affect Score behavior
These live on Tenant.settings and apply to every flow’s Score node:
| Setting | Default | Purpose |
|---|
propensitySmoothingWeight | 10 | Blend weight when offer has 1–49 outcomes — blends learned rate with category/global prior. |
propensityScoreFloor | 0.05 | Minimum propensity component. Prevents starvation when an offer has 50+ negative-only outcomes (raw positiveRate = 0). Clamped to [0, 0.5]. Verified live in T20 Test C — Platinum scored 0.0 before the floor and 0.05 after. |
modelMaturityThreshold | 100 | Below this evidence count, scores are scaled down (maturity ramp). |
rankingInfluencersEnabled | true | When true, sibling-offer outcomes influence a candidate’s score via category propagation. |
Algorithm models (referenced from modelKey)
AlgorithmModel.modelType | Math | Engine entry-point | Verification |
|---|
scorecard | Weighted rules table → sigmoid normalization | scoreScorecard | T16. |
bayesian | Naive Bayes with Laplace smoothing | scoreBayesian | T16. |
logistic_regression | Weighted sum → sigmoid | scoreLogisticRegression | T16. |
gradient_boosted (operator alias: AGB) | Tree-ensemble margin → sigmoid | scoreGradientBoosted | T16. |
thompson_bandit | Beta-Bernoulli posterior sampling | scoreThompson | T16. |
epsilon_greedy | ε-greedy exploitation with decay | scoreEpsilonGreedy | T16. |
online_learner | Online SGD on numeric features | scoreOnline | T16. |
neural_cf | Two-tower embeddings → MLP → sigmoid | scoreNeuralCF | T16. |
external_endpoint | HTTP POST to operator-hosted service | scoreOfferSetExternal (async path) | UI-tested. |
onnx_imported | ONNX runtime inference | scoreOnnxImported (async path) | UI-tested. |
Optimize node (deprecated — folded into Score’s strategyProfileId)
The Optimize node is kept for back-compat and now acts as a passthrough. Use Score.strategyProfileId instead.
Rank node
| Field | Purpose | Engine behavior | Verification |
|---|
method = "topN" | Take the top N by score. | Sort by score descending, take first N. | T1, T2. |
method = "diversity" | Greedy diversity selector — penalizes consecutive same-category picks. | MMR-style selection at line 2202. | UI-tested. |
method = "round_robin" | Round-robin across categories until N reached. | Round-robin pointer scan. | UI-tested. |
method = "explore_exploit" | ε-greedy mix of top-N with random N′. | Uses explorationRate to decide per-slot. | UI-tested. |
maxCandidates | The N in top-N. | Result slice length. | T1. |
maxPerCategory | Diversity cap. | Hard ceiling — drop further candidates from a category once cap is hit. | UI-tested. |
maxPerChannel | Same idea per channel. | Same enforcement. | UI-tested. |
explorationRate | ε in explore_exploit. | Decides exploit-vs-explore per slot. | UI-tested. |
Group node
| Field | Purpose | Engine behavior | Verification |
|---|
allocationStrategy = "optimal" | Hungarian (Kuhn-Munkres) global assignment — per-offer uniqueness across placements. | allocateOptimal() builds the cost matrix, runs Munkres. | T1, T8 (atomic coupling cascade), T9 (vs greedy). |
allocationStrategy = "greedy" | Slot-by-slot best score per placement. Faster, can repeat the same offer across placements. | allocateGreedy(). | T9. |
allocationStrategy = "priority_fill" | Fill slots in priority order until done. | allocatePriorityFill(). | UI-tested. |
placements[] (optional override) | A/B-test slot counts without forking the flow. When absent, slots derive from request.placements[] × Placement.maxSlots. | At line 720, the override merges with placement catalog. | T1. |
allowPartial | Deprecated — channel coupling is the supported lever. | No-op. | n/a. |
Phase 3 — Output
Compute node — runs in any phase
The compute executor doesn’t care about phase. What changes is which candidate set the formulas evaluate against. See Computed Values for formula syntax.
| Field | Purpose | Engine behavior | Verification |
|---|
overrides[] | Per-candidate computed fields named to OVERRIDE an existing category-level computed field. | Evaluates formula then writes to candidate.personalization[name], replacing any prior value. | T13. |
extras[] | Per-candidate computed fields with new names (don’t collide with category-level fields). | Same as overrides but appends. | T13. |
transforms[] | Record-level transforms on personalization after computed fields fire — e.g. mask_pii, cast_type. | Applied to the merged personalization map. | UI-tested. |
Placement choice (T19):
- Phase 1 (after Enrich) → applies to every candidate that survived Qualify + Contact Policy. Use this when a downstream node (Score, Filter) needs to read the computed value.
- Phase 3 (after Rank/Group) → applies only to the final top-K. Use this for render-only personalization (greetings, computed credit limits, conditional CTA text).
Set Properties node
| Field | Purpose | Engine behavior | Verification |
|---|
properties[].key | Property name surfaced in response under properties.{key}. | Direct assignment. | UI-tested. |
properties[].value (literal) | Static value. | Written verbatim. | UI-tested. |
properties[].formula | Computed value via the same formula engine compute uses. | evaluateFormula() over the candidate’s context. | UI-tested. |
Response node
| Field | Purpose | Engine behavior | Verification |
|---|
includeDebugTrace | Include the full per-stage trace in the response body. | Adds trace to the response. | T2, T3, all trace-bearing tests. |
responseFormat = "standard" | Default flat shape. | One offer per response entry. | T1, T10. |
responseFormat = "grouped" | Group by placement. | Wrap entries under placements.{placementId}. | T1, T8. |
Cross-phase nodes
Call Flow node
| Field | Purpose | Engine behavior | Verification |
|---|
flowId | Target DecisionFlow.id. | Loads the target flow, runs via executePipelineV2. | UI-tested. |
passContext | Forward request attributes + enrichedData to sub-flow. | When true, sub-flow inherits parent context. | UI-tested. |
mergeMode = "replace" | Sub-flow result replaces parent candidates. | Standard. | UI-tested. |
mergeMode = "append" | Sub-flow result is unioned with parent candidates. | Concatenation. | UI-tested. |
optional | Soft-fail if sub-flow missing. | Try/catch around dispatch. | UI-tested. |
Extension Point node
| Field | Purpose | Engine behavior | Verification |
|---|
hookName | Lifecycle stage: pre_score / score_override / post_rank. Operator-facing label only — runtime treats them identically. | Recorded in trace; visible in flow editor. | T17. |
label, description | Operator-facing strings. | Display-only. | n/a. |
configured | Gate. When false, node is a no-op (safe placeholder in templates). | Early return at pipeline-runner.ts:2533. | T17 case 1.a. |
subFlowId | DecisionFlow.id of the linked sub-flow. | Loaded with status: "active" filter + V2 check. | T17 cases 2.a–3.b. |
| Implicit depth cap (2) | DoS hardening — sub-flows can nest at most 2 levels deep. | Skip at line 2542. | T17 case 2.c. |
| Implicit cycle break | A → B → A loops are detected via visitedFlowIds set. | Skip at line 2547. | T17 case 3.a. |
Verification index
Each test below is reproducible from a clean checkout. Scripts run via jiti so they don’t need the Next.js dev server.
| Test | Script | What it locks |
|---|
| T13 | platform/scripts/test-compute-personalization.mts | 22 compute-node personalization patterns through the live evaluateFormula. |
| T15 | platform/scripts/proof-scoring-e2e.mts | All three scoring methods + channelOverrides + strategyOverrides + resolveModelForCandidate routing — through live engine helpers. |
| T16 | platform/scripts/proof-algorithms-e2e.mts | Each of 10 algorithm types loads + scores against the same input. |
| T17 | platform/scripts/proof-extension-point.mts | All 7 gate/guard branches of the extension_point dispatcher. |
Coverage gaps tagged “UI-tested” above are exercised through the studio recommendation preview + decision-traces UI but don’t yet have committed scripts. Adding them is the operator-friendly way to close the audit — each row gets a script + a doc cross-link.