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 + 1beta = 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 best | You DO know, based on a user attribute |
| Multiple variants are all plausibly good | One path is for paying users, another for free |
| You want continuous re-optimization | You want a fixed rule that doesn't drift |
| You have a clean goal event | You'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 badge —
cold-startuntil 200 subjects reached, thenthompson - 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.
Related
- Adaptive Journeys — what a branch lives inside
- Global Holdout — how incrementality is measured separately from arm-vs-arm
- Beliefs — where calibrated impact lives once it's computed