Skip to main content
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

Match Creatives node

Enrich node

Qualify node

Contact Policy node

Implicit Contact Policy (#169 — engine-injected)

If a flow has no explicit contact_policy node and the flow’s skipContactPolicy flag is not true, the engine injects an implicit contact_policy node with mode: "all" at the end of Phase 1. This is a safety rail — frequency caps, DNC, and cooldowns must apply by default. The implicit injection is centralized in injectImplicitContactPolicy() and unit-tested at src/lib/__tests__/implicit-contact-policy.test.ts.

Filter node

Conditional (split) node


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:

Engine-wide settings that affect Score behavior

These live on Tenant.settings and apply to every flow’s Score node:

Algorithm models (referenced from modelKey)

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

At cold-start (no interaction data yet), propensity/formula scoring returns a flat 0.5 for every candidate. The rank node breaks these ties by descending Offer.priority, so results still order by business priority rather than arbitrarily. See How Scoring Works.

Group node

Rank and Group are mutually exclusive. A flow may contain a rank node or a group node, not both — the pipeline validator rejects a flow with both (RANK_AND_GROUP_CONFLICT, pipeline-validator.ts). Use rank for single-placement top-N; use group for multi-placement allocation across zones. (A rank node upstream of group throttles the candidate pool and starves placements.)

Channel coupling — the supported atomicity lever

Within-channel atomic coupling replaces the deprecated allowPartial. After the Group node allocates candidates to placements, applyChannelCoupling() (pipeline-runner.ts:503) runs a post-allocation pass: Cross-channel coupling is intentionally not supported — different channels are independent attention surfaces. Cross-channel suppression comes only from do_not_contact (unconditional), the consent stage, and cross_channel_cap (by volume).

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

Response node


Cross-phase nodes

Call Flow node

A call_flow node must sit in Phase 1 or Phase 2 — the validator rejects it in Phase 3 (CALL_FLOW_WRONG_PHASE). Sub-flow nesting is capped at depth 2 and circular references are rejected (CALL_FLOW_MAX_DEPTH / CALL_FLOW_CIRCULAR, validateCallFlowDepth).Where that is enforced: on POST and PUT to /api/v1/decision-flows, which return 422 naming the offending path (A → B → A). The check is transitive — a flow is refused when a cycle is reachable through anything it calls, not only when it closes the loop itself. Since a target must exist before another flow can point at it, a cycle is nearly always closed by an edit, so PUT is the path that matters most.
optional defaults to true, and it governs more than a missing sub-flow: with optional unset, a depth-exceeded or circular reference detected at run time is skipped with a log line, and the decision is returned without that branch. Set optional: false on a sub-flow whose contribution is required, so the run fails loudly instead of quietly producing a different answer.

Extension Point node


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