Skip to main content
See also: Decision Flows REST API reference for request/response shapes, status codes, and error semantics.
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.

How It Works

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.
Two structural rules the validator enforces at save time:
  • The optimize node is deprecated — at runtime it passes scores through unchanged. Configure multi-objective weighting with strategyProfileId on the Score node instead.
  • rank and group are mutually exclusive — a flow may contain a rank node or a group node, not both (RANK_AND_GROUP_CONFLICT). Use rank for single-placement top-N, or group for multi-placement allocation.
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.

Keyboard Shortcuts

Undo/redo buttons are also available in the editor toolbar.

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, archived, and draft (or any status with no published version) are refused. Recommend calls against them throw Decision flow "<key>" is not in a runnable state (status="...", publishedVersions=...), unless previewDraft is set.
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.

The decision context (auto-assembled)

At the start of every Recommend call, the engine assembles one canonical, entity-namespaced decision context for the customer — automatically, before any node in the flow runs. Gates, compute formulas, and scoring predictors all read from this single flat record. Its namespaces: Assembly is resilient: a broken join, a failed metric load, or a journey-state error never fails the decision — that piece is omitted with a logged warning and the pipeline continues. Previewing the namespace: GET /api/v1/decision-context/preview returns the full key catalog available for authoring — every customer.* column, each active join’s rollup keys (and its raw <entity>[] collection), every active behavior.* metric, the journey.* keys, and the documented attributes.* entries, each with its type and source. Pass ?flowKey= to apply a specific flow’s excludeJoinIds[] opt-outs. This is what the Decisioning Gates attribute picker autocompletes from.
Enrichment is automatic — the Enrich node is an override. You do not need an Enrich node for customer.*, <entity>.*, behavior.*, or journey.* to be available. Add an Enrich node only to override or extend the assembled context: attach a schema that isn’t auto-joined, use a custom prefix / lookup key / field subset, or exclude specific joins. Enrich-node output wins over the auto-assembled context on key conflicts.
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-enrichment 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. When a flow has multiple Enrich nodes, their excludeJoinIds are unioned.
Deprecated: unscoped <rawschemaname>.* keys. Before the canonical context, auto-enriched fields were exposed under the raw (lowercased) schema name and base-customer fields under the raw schema name verbatim. These legacy alias keys are still emitted during the transition so existing gates and formulas keep resolving — but the canonical customer.* / <entity>.* form always wins on conflicts, each legacy-only alias logs a deprecation warning, and the aliases will be removed after the migration window. Re-author gates, compute formulas, and predictors against the canonical namespaces.

Implicit Contact Policy

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: 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.

Retail Rewards Example

A retail rewards Decision Flow might work like this:
  1. Inventory — Load all 10 retail rewards offers with their 60 creatives
  2. Enrich — Look up this customer’s purchase history, reward tier, and visit frequency
  3. Qualify — Drop “Buy 5 Get 1 Free” for customers who already redeemed it this month
  4. Contact Policy — Suppress email offers for customers who received 3 emails this week
  5. Score — Run a Bayesian model to predict which offers this customer is most likely to engage with
  6. Rank — Sort by PRIE score, return the top 3

PRIE Ranking Formula

The PRIE formula is KaireonAI’s multiplicative scoring model. It produces a single priority score from four dimensions:
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.

The Four Factors

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.

Two-Layer Architecture

  1. 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.
  2. Per-offer factor values — Each offer provides its own values for each dimension. These are the raw inputs that get weighted and multiplied together.

Worked Example

Two retail rewards offers scored for the same customer, with flow-level weights P=0.4, R=0.2, I=0.2, E=0.2: BOGO Iced Beverage 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.

Score Node Configuration

The Score node supports three methods — priority_weighted, propensity, and formula. PRIE is the formula method — the multiplicative model described above — and it exposes four weight sliders (P, R, I, E). The other two are simpler strategies; see Scoring Strategies for when to use each.

Overrides

You can add overrides scoped to a composite offer_channel, category_channel, or subcategory_channel tuple, or to a single axis — offer, category, subcategory, or channel. Each override can specify a different model AND custom PRIE weights.

Override priority

The default priority order is offer_channel → category_channel → subcategory_channel → offer → category → subcategory → channel → default. Composite (two-axis) scopes rank above their single-axis components — a category_channel override beats a plain category override for candidates on the matching channel. The most-specific match wins; the resolver short-circuits on the first hit. Operators can customize via score.overridePriority: ["offer_channel", "category_channel", ..., "default"] — the order in the array is the order checked.
A matching override always wins over the node’s static defaultModel / modelKey (and over a legacy channelOverrides entry). Previously the overrides ladder only ran when no defaultModel was configured; overrides now take precedence exactly as the priority chain documents.

Channel Score Overrides

The Score node supports a channelOverrides array for entirely different scoring strategies per channel:
The score-config resolver checks the candidate’s channel against the overrides array. First match wins; no match falls back to the default config.

Extension Points

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. Each extension point can link to a sub-flow (a separate Decision Flow that runs inline):
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

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.

How It Works

  1. Load interaction history — Query the customer’s all-time interaction summaries (up to 50 most recent), map each offer to its category
  2. Compute category affinityboost = (positiveRate - negativeRate) * 0.1, clamped to -0.1 to +0.1 (max 10% adjustment)
  3. Apply to scoresadjustedScore = score * (1 + categoryBoost)

Example

A customer has interacted with 3 retail rewards beverage offers: 2 positive, 0 negative.
All other beverage offers for this customer get a 6.7% score boost.
Disable influencers during initial deployment or A/B testing if you want a clean baseline without feedback effects.

Control Groups

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.

How It Works

  1. Deterministic assignment — Hash of customerId + date decides group membership. Same customer, same day, same group.
  2. Random scoring — Control group requests still run the full pipeline (enrichment, qualification, contact policies), but scores are randomized.
  3. Response flag — The response includes controlGroup: true | false for downstream analytics segmentation.
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.

NBA Kill Switch

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.

Auto-Routing

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.

Auto-Assembly

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 decisioning gate triggers reassembly. Assembly triggers: Offers (created/activated/archived), Creatives (created/linked), Channels, Sub-categories, Decisioning gates, 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.

Decision Traces

Decision traces are forensic records that answer: “Why did customer X get Offer Y instead of Offer Z?”

Configuration

Configure in Settings > General > Retention > Decision Trace.

What a Trace Captures

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.

Outbound Batch Decisioning

For outbound campaigns (email, direct mail), process an entire customer segment in one API call:
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.

Portfolio Optimization

The standalone Optimize node is deprecated. At runtime it is a passthrough — case "optimize" in pipeline-runner.ts logs a deprecation notice and leaves every candidate’s score unchanged. Multi-objective weighting now lives on the Score node.
When you need to balance competing business objectives (revenue vs. customer experience vs. margin), attach a ranking (strategy) profile to the Score node via strategyProfileId — or a per-scope strategyOverrides[] entry keyed by category / productType / channel. The profile’s objective weights are mapped onto the PRIE factors (e.g. conversion → propensity, margin → impact), replacing the inline formula weights when set. For details on creating profiles, see Algorithms & Models.

Worked Example: Full Pipeline

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.

Trace Output


Field Reference

Create (POST /api/v1/decision-flows)

Update (PUT /api/v1/decision-flows)

Delete (DELETE /api/v1/decision-flows?id=…)

Soft-deletes the flow (retains record with deletedAt timestamp). Response: { "success": true, "cascaded": 0 }.

API Quick Reference

For complete request/response schemas, see the Decision Flows API Reference.

Composable Pipeline

Build flows from 16 modular node types across 3 phases.

Algorithms & Models

Scoring engines, experiments, and portfolio optimization profiles.

Decisioning Gates

Define eligibility gates that control which offers reach each customer.

Contact Policies

Frequency caps, cooldowns, and suppression rules.

Computed Values

Formula syntax, supported functions, and variable namespaces.

Glossary

Definitions for Offer, Creative, Decision Flow, and other key terms.