# BanditDB — full reference for LLMs and agents BanditDB is an open-source decision database for AI agents and applications, written in Rust. It stores learned policies — which action works in which context — as contextual-bandit state behind a plain HTTP API. Single ~11MB binary, ~10K predictions/s on one node, WAL durability, checkpoints, RBAC, Prometheus metrics. License: Apache-2.0. ## Concept - A **campaign** is one recurring decision. It has **arms** (the possible actions) and a **feature_dim** (length of the context vector describing each situation). - The write unit is the **prediction-action-reward tuple**: `predict()` opens it (context, chosen arm, propensity), your application acts, `reward()` closes it by interaction_id. BanditDB does the attribution join. - State per arm: d×d inverse covariance matrix `A_inv` + weight vector `theta` — a compressed posterior, updated in place per reward with an O(d²) Sherman-Morrison step. No retraining pipeline, no batch jobs on the hot path. - Unrewarded predictions expire from a TTL cache (BANDITDB_REWARD_TTL_SECS, default 86400). For slow outcomes (e.g. chargebacks), raise the TTL. - Rewards are floats; normalise to a stable range per campaign (0..1 recommended). Reward grades the decision, not the event. ## Algorithms - `linucb` (default): deterministic UCB scoring, logs propensities → causal/off-policy analysis. - `thompson_sampling`: posterior sampling; better under delayed/batched rewards. - `neural_lin_ucb` / `neural_thompson_sampling`: MLP embeds high-dim contexts (e.g. 256-dim LLM embeddings) into a compact space (NeuralLinUCBConfig: context_dim, embed_dim, hidden_dim, hidden_layers, retrain_every, retrain_steps, learning_rate, lambda). MLP retrains off-path at checkpoints. - `progressive`: autonomous tournament — a base and a challenger algorithm share traffic; challenger traffic grows with SNIPS-evaluated wins (shadow learning on every reward). - `alpha` (campaign creation): exploration width. 1.0 default; 0 → deterministic greedy; ~0.5 recommended for binary rewards. - Optional `decay_half_life_hours`: forgets old evidence for non-stationary environments. ## HTTP API (default port 8080) Auth: header `X-Api-Key` when BANDITDB_API_KEY / BANDITDB_API_KEYS is set. Roles: admin (everything), writer (predict/reward), reader (GET only). - GET /health — ok | degraded (open, no auth) - GET /metrics — Prometheus format - GET /campaigns — list campaigns - POST /campaign — create: {campaign_id, arms[], feature_dim, alpha?, algorithm?, metadata?, decay_half_life_hours?} (admin) - DELETE /campaign/{id} — delete (admin) - POST /campaign/{id}/archive — soft delete; archived campaigns reject predict/reward - POST /campaign/{id}/restore — undo archive - GET /campaign/{id}/report — regret, convergence, per-arm stats (needs ~30 rewards/arm) - GET /campaign/{id}/diagnostics — theta norms, covariance bounds, entropy health, tournament state - POST /predict — {campaign_id, context[]} → {arm_id, interaction_id} - POST /batch_predict — up to 100 items, partial-failure semantics - POST /reward — {interaction_id, reward} - POST /checkpoint — snapshot matrices, export Parquet, rotate WAL - GET /export — Parquet export index ## Python SDK (`pip install banditdb-python`) from banditdb import Client, NeuralLinUCBConfig, ProgressiveConfig db = Client("http://localhost:8080", api_key="...") db.create_campaign("prices", ["10", "15", "20"], feature_dim=5) arm, iid = db.predict("prices", [0.3, 0.7, 0.1, 0.9, 0.4]) db.reward(iid, 1.0) # embeddings / non-linear rewards: cfg = NeuralLinUCBConfig(context_dim=256, embed_dim=32) db.create_campaign("model_routing", ["fable", "opus", "sonnet"], feature_dim=256, algorithm=cfg) Also: banditdb-dashboard (terminal TUI), banditdb.eval (IPS / doubly-robust off-policy evaluation). ## TypeScript/JavaScript SDK (banditdb-js) import { BanditDBClient } from "banditdb-js"; const db = new BanditDBClient({ url: "http://localhost:8080", apiKey: "..." }); await db.createCampaign("prices", { arms: ["10", "15", "20"], feature_dim: 5 }); const { arm_id, interaction_id } = await db.predict("prices", [0.3, 0.7, 0.1, 0.9, 0.4]); await db.reward(interaction_id, 1.0); ## MCP server (for agents) claude mcp add banditdb banditdb-mcp --env BANDITDB_URL=http://localhost:8080 Tools: create_campaign, get_intuition (→ arm + interaction_id), record_outcome, batch_get_intuition, campaign_report, campaign_diagnostics, list_campaigns, archive_campaign, restore_campaign. All agents sharing a server share one learned policy. ## Operations - Run: `docker run -d -p 8080:8080 simeonlukov/banditdb:latest` or native binary. - Env: DATA_DIR, PORT, BANDITDB_API_KEYS ("key=admin;key2=writer;key3=reader"), BANDITDB_RATE_LIMIT_PER_SEC (default 1000), BANDITDB_REWARD_TTL_SECS, BANDITDB_CHECKPOINT_INTERVAL, BANDITDB_MAX_WAL_SIZE_MB, BANDITDB_AUDIT_LOG, LOG_FORMAT=json. - Durability: WAL append before state mutation; checkpoint = flush → Parquet export → snapshot → rotate; restart = load checkpoint + replay WAL (deterministic, nothing lost). - Campaign ids and arm ids: [a-zA-Z0-9_-]{1,128}. ## Links - Site: https://banditdb.com - Docs: https://banditdb.com/docs/ - API (rendered): https://banditdb.com/api/ - OpenAPI: https://banditdb.com/openapi.yaml - GitHub: https://github.com/dynamicpricing-ai/banditdb - PyPI: https://pypi.org/project/banditdb-python/ - Docker: https://hub.docker.com/r/simeonlukov/banditdb - Sandbox: https://sandbox.banditdb.com/ui - Discord: https://discord.gg/s5ge8xrym