banditkeep
Give a swarm of agents a memory for decisions. banditkeep
is a small client for a BanditDB server: a contextual
bandit database that learns, per campaign, which action
tends to pay off in which context. An agent asks which
arm to pick, acts on the answer, then reports whether it
worked — and every later agent pointing at the same
server benefits from that outcome immediately, with no
retraining step.
Two exploration algorithms anchor the campaigns:
LinUCB, deterministic and tunable through alpha, and
Thompson Sampling, which explores naturally in a
Bayesian way and needs no tuning. Campaigns can also
carry budgets for actions that cost something limited,
so spend is steered across a horizon instead of running
dry on the first day.
Configuration
Three environment variables control the client:
BANDITDB_URL (optional) — base URL of your BanditDB
server. Defaults to http://localhost:8080.
BANDITDB_API_KEY (optional) — when set, it is sent
on every request as Authorization: Bearer <key>.
Omit it when your server runs without auth; leaving
it unset is never an error by itself.
BANDITDB_TIMEOUT_MS (optional) — per-request
timeout in milliseconds, enforced with an
AbortController. Defaults to 15000. (This variable
is this rebuild's own addition.)
Tools
create_campaign — create a new decision campaign.
Requires campaign_id and arms; optionally takes
features (names, in order) or feature_dim,
alpha, algorithm ("linucb" or
"thompson_sampling"), metadata, budgets, and
decay_half_life_hours.
list_campaigns — list all active campaigns, showing
algorithm and alpha; useful to check what exists
before calling get_intuition.
campaign_diagnostics — inspect per-arm learning
state: theta norms, prediction counts, reward rates,
and entropy health. Reach for it when a campaign does
not seem to be learning or one arm is dominating.
campaign_report — a business-level convergence
report: whether the campaign has statistically
converged, and which arm is winning, with confidence
intervals.
get_intuition — ask which arm to pick for a given
context (an object of feature-name to number, or an
array of numbers). Optional eligible_arms /
exclude_arms narrow the choice for this request
only. Returns the arm and an interaction_id to save.
batch_get_intuition — get decisions for multiple
campaigns in a single round-trip: pass predictions
as a list of {campaign_id, context} dicts, at most
100 per call.
record_outcome — report whether the chosen action
succeeded (1.0) or failed (0.0), closing the loop
for an interaction_id. Rewards outside [0, 1] are
rejected locally with reward must be between 0 and 1.
archive_campaign — soft-delete a campaign: it
pauses predictions and rewards but preserves all
learned weights.
restore_campaign — restore an archived campaign to
active status with all weights intact.
campaign_budget — budget spend per resource:
consumed, remaining, utilization, shadow price, and
exhaustion.
add_arm — add an action to a live campaign,
optionally warm-started from existing arms and with
budget costs.
set_arm_status — pause, retire, or reactivate an
action (status is "active", "paused", or
"retired"); the arm keeps everything it learned.
Unset optional arguments are omitted from requests
entirely. Responses come back exactly as the server
returned them. An upstream non-2xx reply fails with
banditdb error: HTTP <status> and an unreachable
server (including a timeout) with banditdb unreachable.
Endpoint provenance
The original project's README and MCP documentation pin
the twelve tools, their arguments, the configuration
above, and the decision-loop semantics — but no HTTP
API is published in those sources. The request paths
below are reconstructions, labelled as such:
POST <base>/campaigns with the arguments as the
JSON body (used by create_campaign).
GET <base>/campaigns (used by list_campaigns).
GET <base>/campaigns/{campaign_id}/diagnostics
(used by campaign_diagnostics).
GET <base>/campaigns/{campaign_id}/report (used by
campaign_report).
GET <base>/campaigns/{campaign_id}/pacing (used by
campaign_budget; the SDK method is named pacing).
POST <base>/predict with body {"campaign_id": ..., "context": ...} (used by get_intuition).
POST <base>/predict/batch with body
{"predictions": [...]} (used by
batch_get_intuition).
POST <base>/reward with body
{"interaction_id": ..., "reward": ...} (used by
record_outcome).
POST <base>/campaigns/{campaign_id}/archive with
body {} (used by archive_campaign).
POST <base>/campaigns/{campaign_id}/restore with
body {} (used by restore_campaign).
POST <base>/campaigns/{campaign_id}/arms with body
{"arm_id": ...} (used by add_arm).
POST <base>/campaigns/{campaign_id}/arms/{arm_id}/status
with body {"status": ...} (used by
set_arm_status).
Path segments are sent verbatim. One discrepancy worth
knowing: the vendor's MCP docs page lists nine of the
twelve tools (it omits campaign_budget, add_arm,
and set_arm_status); the package's own MCP table
enumerates all twelve, and this rebuild follows the
package table.
CLI
Each tool is a subcommand taking one JSON argument
string:
Output is the tool's result text (the upstream body on
success). Exit codes: 0 on success, 1 on a runtime
error, 2 on a usage error.
MCP server
MCP client config (provide the key through the client's
environment when your server uses one):
Docker
Development
License
MIT — Copyright (c) 2026 Rene Turcios