Skip to main content
A decision is one decide call for one user on one element: which arm was served, when, and under what context. Decisions are what rewards attribute to, so how they are created matters.

How an arm is chosen

Each decision is a draw from a contextual bandit over the live arms. Every arm keeps a probability of being served, so no arm is ever starved, and the probabilities tilt toward what is working for users like this one, using the stored attributes and the call’s context. While the experiment is initializing, every user sees the control; variants arrive without any change on your side.

Static and dynamic assignment

options.assignment decides whether a user keeps seeing the same arm.
  • "static", the default: the same user keeps the same arm for as long as that arm is still live. Repeated calls re-serve it without a new draw. When the arm is concluded, the user is assigned afresh on the next call, like a new user.
  • "dynamic": every call is chosen afresh. Two calls for the same user may serve different arms.
static fits content a user sees more than once in a way that would read as broken if it changed: a headline, a hero, a paywall, a button label. Choose dynamic where each exposure is a genuinely separate chance: a push notification, an email send.
The option updates in place, so switching it never restarts the experiment. Like every option, its value is resolved when the experiment is created and stored with it, so a later change to the default never alters a running experiment.

Every call is a decision

Stickiness governs the content, never the record: every decide call creates a decision, even when static assignment re-serves the same arm. When a new decision is created for a user and element while an earlier one’s attribution window is still open, the earlier window ends at that moment. Each event then lands in exactly one decision’s window, which is what makes attribution unambiguous. The practical rule: call decide once per exposure, when the content is about to be shown, and reuse the result for the duration of that exposure. A page that calls decide on every re-render is still correct, just noisy; the browser SDK caches a decision for sixty seconds so remounts cost nothing. Server-initiated channels call on each send, which has a natural cadence.

The holdout cohort

A share of users, options.holdout, always sees the control: the default is 0.05, the bounds are 0.02 to 0.2. Membership is a deterministic function of the user and the element, so a held-out user stays held out for the experiment’s life, and the share is fixed for the experiment’s life too: declaring a different value supersedes the experiment. The holdout is what keeps a clean baseline no matter how the bandit tilts: a held-out user’s decisions involve no draw, and they feed the model as control observations for as long as the experiment runs.

What a decision records

The response tells you what happened: content after variable substitution, arm_id and arm_type, the decision_id, the experiment_id and experiment_status. Repeated serves of the same arm carry different decision ids. Under idempotency_key, a replay returns the original decision verbatim with replayed: true and creates nothing new. See Serve a decision.