Choosing an Algorithm

All algorithms share identical per-arm state. Switching is a single field at campaign creation.

AlgorithmValueHow it exploresWhen to use
LinUCB "linucb" (default) Deterministic UCB bonus: θ·x + α·√(x·A⁻¹·x) Predictable, tunable. Sweep alpha offline to calibrate exploration.
Linear Thompson Sampling "thompson_sampling" Samples θ̃ ~ N(θ, α²·A⁻¹), scores by θ̃·x Natural Bayesian exploration. alpha=1.0 is the principled default. Concurrent users automatically diversify arm coverage.
NeuralLinUCB {"neural_lin_ucb": {…}} Identical UCB formula to LinUCB, but applied to a learned embedding h(x; W) instead of raw context. MLP retrained in batch on its own cadence once retrain_every rewards accumulate. Non-linear or high-dimensional contexts where LinUCB has plateaued. Requires the neural feature flag at compile time.
NeuralThompsonSampling {"neural_thompson_sampling": {…}} Same MLP embedding and retrain path as NeuralLinUCB, but draws w ~ N(θ, σ²A⁻¹) and scores by w·h(x;W) instead of a UCB bound. Same non-linear/high-dimensional use case as NeuralLinUCB. Better long-run convergence, higher early regret. Requires the neural feature flag.
Progressive (Tournament) {"progressive": {"base": "linucb", "challenger": "neural_lin_ucb"}} Runs both models in parallel. Uses the better-performing model for 90% of traffic. The recommended default. Handles the cold-start with Linear and upgrades to Neural automatically when data justifies it.
# Progressive — the internal tournament handled by BanditDB
db.create_campaign("auto_agent", ["model_a", "model_b"], feature_dim=1536,
                   algorithm={"progressive": {"base": "linucb", "challenger": "neural_lin_ucb"}})

Normalise your context vectors. Every algorithm here scores exploration with a term proportional to the context's magnitude, and the regret bounds behind LinUCB assume ‖x‖ ≤ 1. Feed raw features on mixed scales — age next to income next to a 0/1 flag — and the model still converges, only much more slowly, with nothing in the output to indicate why. On the UCB shuttle benchmark, unit-L2 scaling alone moved cumulative regret from 2,026 to 709. The Python SDK ships normalize_context() for this.

Retraining runs on a background cadence (BANDITDB_RETRAIN_POLL_SECS), not as part of checkpointing. Before 2.0.0 the two were coupled, so a retrain held up the checkpoint behind it. Predictions read an immutable snapshot of the network weights and never wait on a retrain in progress.

The algorithm field is stored in both the WAL and checkpoint files. Old WAL records and checkpoints without an algorithm field recover as "linucb" automatically.