Quick Start
A complete predict→reward cycle for a sleep improvement campaign.
Sleep Improvement
One-size-fits-all sleep advice ignores individual physiology. A 25-year-old male athlete and a 60-year-old sedentary woman respond differently to the same environmental change. BanditDB learns those differences automatically — routing each participant to the intervention most likely to work for their profile, improving with every reported outcome.
from banditdb import Client
db = Client("http://localhost:8080", api_key="your-secret-key")
# 1. Create the campaign once at startup
db.create_campaign(
"sleep",
arms=["decrease_temperature", "decrease_light", "decrease_noise"],
feature_dim=5,
metadata={
"owner": "wellness-team",
"features": ["sex", "age_norm", "weight_norm", "activity", "bedtime_norm"],
},
)
# 2. A participant is ready for tonight's intervention.
# Context: [sex, age/100, weight_kg/150, activity_0–1, bedtime_hour/24]
context = [
1.0, # female
0.35, # age 35
0.50, # 75 kg
0.60, # moderately active
0.96, # bedtime 23:00
]
# 3. Ask BanditDB which intervention to apply
arm, interaction_id = db.predict("sleep", context)
print(f"Tonight's intervention: {arm}") # e.g., "decrease_temperature"
# 4. Apply the intervention, then reward the next morning
score_before = 62
score_after = 79
# A raw improvement ratio is unbounded — a score that more than doubles
# exceeds 1.0 and the server rejects it. Clamp before sending.
reward = min(max((score_after - score_before) / score_before, 0.0), 1.0) # → 0.27
db.reward(interaction_id, reward)
Rewards must be in [0, 1]. As of 2.0.0 this is
enforced: anything outside the range returns 400. Earlier
versions accepted any finite number and silently corrupted the arm's confidence
bounds — an unscaled metric like revenue-in-dollars would make one arm look
unbeatable and collapse exploration. Divide by the maximum possible value, or
clamp a ratio as above.
Reward within the feedback window. A prediction is held in
memory until its reward arrives, then expires — 24 hours by default
(BANDITDB_REWARD_TTL_SECS). Rewarding the next morning fits
comfortably; a campaign whose outcome takes days needs a longer TTL, or the
reward comes back 404 and the observation is lost. Raise the
window and the pending cache holds proportionally more, so size
BANDITDB_MAX_PENDING_INTERACTIONS to match.
The call to reward() returns only once the record is fsynced to
disk, so a crash immediately afterwards cannot lose it. That costs a few
milliseconds per call and the cost is per-call, not per-record — submit rewards
in parallel if you are backfilling.