pex

Adaptive Branches

An Adaptive Branch is a step in an Adaptive Journey that routes each user through one of N arms, learning over time which arm performs best.

It's the difference between an A/B test that you set up and tear down, and a self-optimizing experiment that just keeps going.

The math

Each arm has a posterior conversion rate, modeled as a Beta distribution:

posterior ~ Beta(alpha, beta)

where:

  • alpha = goalEvents + 1
  • beta = subjectsReached - goalEvents + 1

(The +1's are the priors. They start every arm at uniform Beta(1,1) before any data lands.)

When a user reaches the branch, the runtime draws one sample from each arm's posterior and routes the user to the arm with the highest sample. This is Thompson sampling. The expected behavior:

  • Early on, samples are noisy → arms get balanced traffic
  • As an arm accumulates wins, its posterior tightens around a high mean → it gets sampled more
  • Underperforming arms aren't pruned — they're just sampled less often

Cold-start

Thompson's regret bound assumes each arm has enough samples to escape its prior. Below minSampleFloor (default 200) draws per arm, the runtime falls back to deterministic round-robin based on a stable hash of the subject ID. Same user, same arm — every re-evaluation lands on the same arm during cold start.

Once every arm has ≥200 subjects reached, the sampler switches to Thompson automatically. No flag, no announcement.

Goal attribution

When an event matches a branch's optimizationGoal.eventName AND fires within the journey's attributionWindowDays (default 7) AND the user passed through this branch, the runtime credits the user's assigned arm with one goal event.

Stable assignment: the dispatcher records the chosen arm on the execution pointer the moment the branch routes the user. Goal attribution looks at this record, not the live sampler — so even if Thompson would route the user to arm B today, an event that fires 3 days after they were originally routed through arm A still credits arm A. No retroactive re-bucketing.

Censoring (Council Stage 2)

Per the council's review of estimand validity: stopped, failed, or timed-out executions are NOT counted as failures. They're censored — neither denominator (subjectsReached) nor numerator (goalEvents) bumps for them. This prevents a bias toward shorter arms (which are mechanically less likely to be cut short).

Concretely, only pointers with status: running or status: succeeded credit goal events. GDPR forget-end-user, runtime errors, and Step Functions timeouts all leave the arm's counters intact.

When to use Adaptive Branches vs. Conditional Branches

Use Adaptive when...Use Conditional when...
You don't know which message/path is bestYou DO know, based on a user attribute
Multiple variants are all plausibly goodOne path is for paying users, another for free
You want continuous re-optimizationYou want a fixed rule that doesn't drift
You have a clean goal eventYou're routing on user state, not outcome

A common pattern: route on a conditional first (e.g. "free vs. paid"), then put an adaptive branch inside the free path that experiments with reactivation copy.

Optimization goal

Every Adaptive Branch declares one goal:

optimizationGoal: {
  eventName: "subscription.created",
  windowDays: 7,
}

Pick a goal that's:

  • Binary — fired or not fired (Thompson assumes a Bernoulli outcome)
  • Reasonably fast — within days, not months (so posteriors update before the experiment goes stale)
  • Caused by the journey — the user wouldn't have done it absent the nudge

Attribution windows >30 days are usually a smell — the signal-to-noise drops because the user's behavior depends on too many things outside the journey.

Reading the Branch Performance panel

Each arm shows:

  • Posterior-mean rate — the dot on the bar; this is what the sampler "thinks" the arm's true rate is
  • 95% credible interval — the shaded band; narrows as samples accumulate
  • Phase badgecold-start until 200 subjects reached, then thompson
  • Leading lift vs. runner-up — only shown when samples are warm; this is the headline "how much better is the leader"

Cold-start arms have wide intervals and inscrutable rates. That's expected — give them samples.