Request Lifecycle
Every call toPOST /api/v1/recommend follows this lifecycle.
Flow Resolution
When the request does not include adecisionFlowKey, the engine resolves the flow automatically using FlowRoute records. Resolution uses most-specific-match-wins:
- Channel + Placement — exact match on both
- Channel only — matches any placement for that channel
- Tenant default — fallback when no channel/placement match exists
Multi-Placement Requests
When the request body includes aplacements array, the engine resolves each placement independently. With deduplicate: true, placements are resolved sequentially and each subsequent placement excludes offers already selected by previous placements. Without deduplication, all placements resolve in parallel.
Composable Pipeline
The composable pipeline uses an ordered array of nodes. Each node has atype, id, and config object. The runner validates the pipeline structure before execution, then processes nodes sequentially.
Three Phases
Nodes are organized into three logical phases. The pipeline validator enforces that Phase 1 nodes appear before Phase 2, and Phase 2 before Phase 3.All 16 node types are fully functional — no stubs. The
call_flow node (shown in amber) can be inserted at any phase to invoke a sub-flow, with a max nesting depth of 2 and circular reference detection. The enrich node queries schema tables with Redis caching, the qualify node evaluates decisioning gates with AND/OR logic trees.16 Node Types
Execution Model
The pipeline runner maintains a mutablecandidates array and a groupResult map as shared pipeline state. Each node reads from and writes to these structures:
- Validation — Before any node runs, the runner checks that the pipeline has the required nodes and that phase ordering is respected. A bad pipeline is rejected up front rather than failing partway through execution.
- Sequential loop — Nodes execute in array order via a
for...ofloop with aswitchonnode.type. - Early return — The
responsenode returns the assembled result immediately, terminating the loop. - Trace accumulation —
traceSummarycounters are updated at key nodes (inventory,qualify,contact_policy,rank).
Formula Engine
The formula engine (lib/formula-engine.ts) provides safe expression evaluation with no dynamic code execution. Every formula goes through three stages:
Pipeline
+, -, *, /, %, >, <, >=, <=, ==, !=), punctuation ((, ), ,, ?, :), and end-of-input.
2. Parser — Recursive-descent parser that builds an AST respecting operator precedence:
3. Evaluator — Tree-walks the AST, resolving identifiers from a variable map. Null propagation: if any operand resolves to null/undefined, the result is null. Division by zero returns null.
Built-in Functions
The engine ships six built-in functions. See the Formula Reference for full signatures and worked examples.- min — minimum of two numbers
- max — maximum of two numbers
- round — round to nearest integer, or to N decimal places when a second argument is supplied
- abs — absolute value of a number
- coalesce — first non-null value (minimum two arguments)
- concat — string concatenation (null propagates)
Variable Namespaces
Formulas resolve identifiers from three namespaces:
Example formula:
Scoring
The engine supports multiple scoring methods, selected per flow configuration.Scoring Methods
Model Resolution Hierarchy
Whenmethod is propensity or formula, the engine resolves the scoring model through this hierarchy:
- Active experiment — If an experiment references the configured
modelKey, the engine uses experiment-aware traffic splitting to select champion vs. challenger models. Assignment is tracked via theexperimentAssignmentTotalcounter. - Direct model lookup — Falls back to
algorithmModel.findFirst({ key: modelKey, status: "active" }). - Pre-computed propensity scores — If no model is found, checks
attributes.propensityScores[modelKey]from the request body. - Priority-weighted fallback — Last resort: uses
priority / 100as the score.
Scoring Failure Fallback
The scoring stage never lets a model error break a decision. When a model raises an error (or an external scoring endpoint fails), the engine catches it and applies a fallback:- External endpoints: the candidate score becomes
0.5 * fitMultiplier(the0.5default is a constant in the scoring stage, not an environment variable). - Missing model: scoring falls through the model-resolution hierarchy to
priority_weighted(priority / 100). - Flag:
degradedScoringis set totrueon the response (and on each affected candidate) so callers can tell the decision ran in a degraded state.
When
explain=true is passed to the Recommend API, each decision includes a degraded boolean that surfaces whether that specific offer was scored using the fallback. The response also includes a rankingScores breakdown with propensity, relevance, impact, emphasis, composite so you can inspect exactly how the PRIE formula was evaluated per offer. See Recommend API — Decision Explanations for the full response shape.Portfolio Optimization
Multi-objective portfolio optimization computes a weighted composite score across five dimensions:Dimensions
Default Weights
By default, onlyconversion has a non-zero weight (1.0), making the optimized score equal to the conversion probability. Configure weights via Portfolio Optimization profiles in Studio or via the Optimize pipeline node to enable multi-objective optimization.
If the total weight sums to zero, the engine falls back to the raw conversion score.
Decision Traces
The engine produces atraceSummary on every decision for lightweight observability, plus an optional debugTrace with full diagnostic detail.
Trace Summary (always captured)
Debug Trace (when debug=true)
Includes everything in the trace summary, plus:
qualificationReasons— Per-offer disqualification details (rule ID, reason)contactPolicyReasons— Per-offer/creative suppression details (policy ID, reason)featureContributions— Per-result score explainability (model type, base score, top factors)afterConsent,afterGuardrails— Additional stage counters
Sampling and Storage
Decision traces are persisted to the decision-trace store and surfaced in the Runs and Decision Health views. The tenant settingsdecisionTraceEnabled and decisionTraceSampleRate control whether and how often traces are written.
Policy Snapshots
On every decision, the engine persists a policy snapshot (fire-and-forget) containing the current decisioning gates, contact policies, and guardrail rules along with apolicyVersionHash. This enables forensic replay: given a decision trace, you can reconstruct the exact policy state that was active when the decision was made.
Worked Example
This example traces a pipeline executing a Recommend request for a banking cross-sell use case. The flow is configured with 10 nodes.Pipeline Config
Execution Trace
Response Shape
Next Steps
Recommend API
Full request/response reference for the Recommend endpoint.
Decision Flows
Configure Decision Flows in Studio.
Composable Pipeline
Build flows from 16 modular node blocks.
Algorithms & Models
Scoring engines used in the Score stage.