A Decision Flow is the brain of KaireonAI. Every time you need to decide which offer to show a customer, a Decision Flow runs behind the scenes. It loads the eligible inventory, pulls in customer context, filters out anything that should not be shown, scores what remains using ML models, and returns a ranked list of personalized recommendations — all in under 200ms.Think of it as a pipeline with a clear contract: a customer walks in one end, and the best offers come out the other.
Decision Flows are executed through the Recommend API. Pass a decisionFlowId in the request body and the engine does the rest.
Decision Flows use a composable pipeline with 16 node types organized across 3 phases. You assemble a pipeline by choosing which nodes to include and configuring each through a visual canvas editor.
Score each candidate, optimize across objectives, rank, group into placements
score, optimize, rank, group
3 — Output
Compute personalized values and format the response
compute, set_properties, response
Cross-phase
Can appear in any phase
call_flow, extension_point
For a full reference of all node types, phase rules, and configuration options, see the Composable Pipeline page.For an operator-facing reference of every configurable field on every node — including how each knob is consumed by the engine and how to verify it had an effect — see Node Configuration Reference. For the operator-facing decision guide on the Score node’s three methods (priority_weighted, propensity, formula) plus channel/strategy overrides and the propensity score floor, see Scoring Strategies.
Lifecycle & publication — draft vs active vs published
A flow has a status (draft, active, paused, archived) and a list of publishedVersions[]. The engine refuses to run a flow unless its status is active (or published) AND at least one published version exists. This is the publish-or-refuse gate — drift between “still building” and “live in production” is intentionally hard to cross by accident.
Edits land on draftConfig (live as you type).
POST /api/v1/decision-flows/publish snapshots draftConfig into publishedVersions[] as a new versioned entry with optional notes.
The engine reads the latest entry of publishedVersions[] — not draftConfig — when resolving the route. Save without publishing → engine still serves the old version.
paused and archived are explicit hold states. Recommend calls against them throw "Decision flow is not in a runnable state".
The Studio’s Recommendation Preview panel needs an escape hatch to test draft changes before publishing. It sets previewDraft: true in the engine context, which suspends the gate for that one call. This is studio-only — the public POST /api/v1/recommend endpoint never accepts the flag from the network.
If you’ve configured SchemaJoin entries between your customer schema and other tables (under Data → Schema Joins) with autoEnrich: true, the engine automatically loads those joined rows into every candidate’s enrichedDatabefore any Enrich node runs. The Enrich node in the flow is reserved for explicit overrides — schemas that aren’t auto-joined, or auto-joined schemas where you want a custom prefix / lookup key / field subset.In the Studio, the Enrich-node panel reflects this automatically: it opens with a green “Auto-enriched from data layer” card listing every schema covered by an active join, and the explicit-source picker tags any duplicate schema with ”· auto-joined” plus an amber notice. See the Schema Joins API for how to define joins.Per-flow exclusion (#168): auto-enrich is tenant-wide, but a single flow can opt out of specific joins by adding their id to EnrichNodeConfig.excludeJoinIds[]. The Enrich-node panel exposes this with an × button on each green auto-join chip — click it and the chip becomes struck-through, indicating that join is skipped for this flow only. Other flows are unaffected. Use this for flows that don’t need a specific join’s data (saves a request-time lookup), or where you want a manual Enrich source to fully replace an auto-join rather than just override its fields. The original behavior (zero exclusions) remains the default.
The engine auto-applies all active Contact Policy rules (DNC, frequency caps, cooldowns, suppression windows) at the end of Phase 1, even when the flow has no explicit contact_policy node wired in. This is intentional — Contact Policies are safety rails that protect against compliance breaches (e.g. forgetting to enforce a do-not-contact list). The opt-out is the flow-level skipContactPolicy: true flag, reserved for flows that must legitimately bypass these rails (transactional notifications, OTPs, system-of-record updates).How the engine decides which path to take:
Flow shape
Engine behavior
No contact_policy node, skipContactPolicy = false (default)
Engine injects an implicit contact_policy node with mode = "all" at the end of Phase 1.
Explicit contact_policy node present (any mode)
Engine respects the explicit node’s config. No additional implicit injection.
No contact_policy node, skipContactPolicy = true
Engine skips contact policy entirely. Use only when the flow must bypass safety rails.
If you want a flow to run some policies (e.g. only DNC, skip frequency caps), add an explicit contact_policy node with mode = "selected" and pick the specific contactPolicyIds[]. The implicit injection happens only when no explicit node is present.
The PRIE formula is KaireonAI’s multiplicative scoring model. It produces a single priority score from four dimensions:
Priority = P x R x I x E
The multiplicative structure means a zero in any dimension eliminates the candidate entirely. This prevents irrelevant high-value offers from surfacing and ensures all four dimensions must be satisfied.
Impact vs. Emphasis: Impact (businessValue) captures objective business value — how much is the offer worth if the customer converts. Emphasis (priority) is a subjective lever for marketers to boost or suppress offers based on campaign strategy. See Offers — Business Value for details.
Flow-level weights — Configured on the Score node, these control how much each factor matters relative to the others. Weights must sum to 1.0. They are global tuning knobs for your decisioning strategy.
Per-offer factor values — Each offer provides its own values for each dimension. These are the raw inputs that get weighted and multiplied together.
Two Starbucks offers scored for the same customer, with flow-level weights P=0.4, R=0.2, I=0.2, E=0.2:
Factor
BOGO Frappuccino
Earn 3x Stars
P (model score)
0.85
0.60
R (context)
0.70
0.90
I (businessValue=80)
0.80
0.40
E (priority=70)
0.70
0.90
PRIE score
0.85 x 0.70 x 0.80 x 0.70 = 0.333
0.60 x 0.90 x 0.40 x 0.90 = 0.194
BOGO Frappuccino wins despite lower relevance and emphasis, because its higher propensity and business value outweigh the other factors.
When explain=true is passed to the Recommend API, each decision in the response includes a rankingScores object with the individual PRIE factor values (propensity, relevance, impact, emphasis) and the final composite score. This makes it easy to debug why one offer outranked another without needing to reconstruct the math manually.
You can add overrides scoped to a specific offer_channel (per-(offer, channel) tuple), offer, category, subcategory, or channel. Each override can specify a different model AND custom PRIE weights.
Override Scope
Key format
Example use case
offer_channel
<offerId>:<channelId>
Use a specialized model only for this offer on this channel — per-(action, channel) propensity granularity, native (no composition required)
offer
<offerId>
Specialized model for a high-value offer regardless of channel
category
<categoryId>
Different PRIE weights for beverages vs. merchandise
subcategory
<subcategoryId>
Boost emphasis weight for seasonal items
channel
<channelId>
Lower propensity weight for email where model accuracy is lower
The default priority order is offer_channel → offer → category → subcategory → channel → default. The most-specific match wins; the resolver short-circuits on the first hit. Operators can customize via score.overridePriority: ["offer_channel", "offer", ..., "default"] — the order in the array is the order checked.
Extension Points let you inject custom logic at three critical moments in the pipeline without modifying the core flow. They appear as dashed-border placeholder nodes on the canvas and are no-op when unconfigured.
Hook
Phase
When It Fires
Use Cases
pre_score
Before scoring
After enrichment and filtering
External API enrichment, last-minute filters, real-time features
score_override
During scoring
After built-in scorer runs
Replace/adjust scores with an external model, champion-challenger
Extension points are the recommended way to integrate external ML models for score adjustment. Use score_override to call your model and blend or replace the built-in score.
Ranking Influencers create a feedback loop between past customer outcomes and future scoring. When a customer has positive interactions with offers in a category, other offers in that same category receive a score boost. Negative outcomes lead to a demotion.
KaireonAI supports an always-on control group for measuring the true incremental lift of your Decision Flows. A configurable percentage of Recommend API calls receive randomized scores instead of model-driven scores, creating a baseline for lift measurement.
Deterministic assignment — Hash of customerId + date decides group membership. Same customer, same day, same group.
Random scoring — Control group requests still run the full pipeline (enrichment, qualification, contact policies), but scores are randomized.
Response flag — The response includes controlGroup: true | false for downstream analytics segmentation.
Setting
Type
Range
Default
Description
controlGroupPercent
integer
0—10
2
Percentage of Recommend calls in the control group
A 2% default means roughly 2 out of every 100 calls get randomized scores — enough for statistically meaningful lift measurement without materially impacting business outcomes.
The NBA Kill Switch is a tenant-level safety control that instantly disables Decision Flow execution across your entire tenant.Setting:nbaEnabled (boolean, default true) in Settings > General.When nbaEnabled = false:
Recommend API bypasses all Decision Flow execution
Offers are returned ranked by priority only (simple ordering)
No enrichment, scoring, filtering, or contact policy evaluation occurs
Response includes "mode": "fallback" flag
The kill switch is for emergency use — for example, if a flow misconfiguration is causing production errors. It does not disable the Recommend API itself; it only simplifies ranking to a safe fallback.
KaireonAI reduces manual wiring by automatically creating FlowRoutes when you add channels and placements.
First flow created is automatically marked as the default
New channel — FlowRoute created to the default flow
New placement — FlowRoute created to the default flow
New channels and placements are immediately functional without manual routing setup. Override any auto-created route by editing it in the Decision Flow configuration or the Channel detail page.
Auto-routing only creates routes to the default flow. To route a channel to a different flow, create the route manually or change the default flow first.
When autoAssembly is enabled (the default), the flow’s inventory updates automatically as your catalog changes. Creating a new offer, activating a creative, or adding a qualification rule triggers reassembly.Assembly triggers: Offers (created/activated/archived), Creatives (created/linked), Channels, Sub-categories, Qualification rules, Contact policies.Each trigger is logged in the flow’s assemblyLog with a timestamp, source entity, and changes applied. Set autoAssembly: false for full manual control.
afterGuardrails is the real candidate count after the
guardrail stage
removes any hard-blocked candidates. It runs once per request at rank-node
entry (or the response node when a flow has no rank node), so ranking and the
result limit operate on the survivors. guardrailReasons[] lists each failed
guardrail evaluation as { ruleKey, ruleName, severity, passed, reason?, offerId? }.
Use 100% sample rate during development. In production, 1—5% keeps storage costs manageable while giving enough data to investigate issues.
The response includes per-customer recommendations plus an aggregate summary with avgOffersPerCustomer, topOffers, and categoryDistribution.
Batch decisioning runs the same pipeline as real-time Recommend — enrichment, qualification, contact policies, scoring, and ranking all apply. The only difference is iteration over a segment instead of a single customer.
When you need to balance competing business objectives (revenue vs. customer experience vs. margin), the Optimize node produces a single composite score:
Walk through a complete Recommend API call to see how each stage transforms the candidate list.Setup: 5 active offers (offer-A through offer-E), 1 Decision Flow, customer C-4821 with credit_score = 745, PRIE scoring with a scorecard model, ranking: topN, maxCandidates = 2.