“Contextual” is easy to say and hard to keep small. Many decisioning products treat context as a feature warehouse: hundreds of columns, a mapping project, and a model that needs dense history before it is useful. BanditBuzz takes the other route. Faraday resolves the person from the identifiers already on the decide call, returns a compact score payload, and BanditBuzz turns that payload into a short feature vector the learner can use the same day.
This post is the one place BanditBuzz explains Faraday by name. It covers the real objects involved, the exact features and encodings, how shared context differs from account-trained context, how snapshots and fallbacks work, where history comes from, and what the privacy limits are today.
Why the vector stays small
A contextual bandit is only as good as the person features it can learn from when data is sparse. Li, Chu, Langford, and Schapire (2010) showed that on Yahoo Front Page traffic, giving a bandit user features lifted clicks 12.5% over a context-free version, with a larger advantage when data was thinner. They also distilled a large input set down to a handful of numbers before the bandit saw them. That design choice is the one BanditBuzz copies.
Faraday’s public REST API is built around Connections, Datasets, Cohorts, Outcomes, Persona Sets, Scopes, and Targets. BanditBuzz does not ask buyers to assemble that graph by hand. It creates the pieces it needs behind the decide and record surface, then consumes Faraday through two delivery paths: a live Lookup API Target for request-time decisions, and a batch Target that writes rows into each account’s person_context table for push sweeps.
Faraday’s own Lookup API specification states the contract plainly: “Faraday's Lookup API targets can be used in real-time contexts to retrieve any payload element set on the associated Scope.” It also says lookups are fast because the predictions are pre-computed, and that the API does not score on the fly. BanditBuzz depends on that precomputed payload, not on scoring a giant feature store at request time.
The object flow
Every BanditBuzz account gets an identity backbone at creation. Faraday receives a Cloud SQL connection into that account’s client schema, plus Datasets over the flat events and decisions tables BanditBuzz already writes. Nightly passport reads stamp a resolved person id onto ledger rows when identity matches. Attribution, cooldown, unsubscribe checks, and push population grouping use that id when present, with normalized email and phone as the unresolved fallback.
Account-trained context is a second chain. It starts only when the account has mapped customer history or turns on the paid contextual tier. BanditBuzz then creates, in order:
- A people Dataset over the account’s known identifiers.
- A Cohort for people with activity in the account stream.
- An Outcome trained as a lookalike of that attainment population.
- A Persona Set that clusters the same cohort.
- A Scope whose payload carries one shared consumer attribute, the Outcome, and the Persona Set.
- A batch Target that writes scored rows into
person_context. - A Lookup API Target for live decide calls.
Faraday’s Outcome docs say outcomes “tell you how likely individuals are to take virtually any action, such as convert or churn.” Persona Sets “organize individuals in a cohort … into a thematic group.” BanditBuzz uses both as features for the bandit, not as the policy itself. The Outcome probability is one number in the linear model. It is not a Bayesian prior over options. The Persona assignment becomes a one-hot indicator. Outcome percentile is stored on the decision-time snapshot for later analysis and is not a model feature.
The implemented context path. A missing match or a reached monthly cap falls back to the standard policy.
Context delivery is marked ready only when the live Lookup Target reports ready through Faraday’s webhook events. Until then, decide calls keep using the shared lookup path described below.
Shared context versus account context
There are two concrete context kinds on every decision row: shared and account.
Shared context is available without a client warehouse project. The decide call’s identifiers go to a shared Lookup Target. A successful match returns a Faraday person id and one shared consumer credit proxy. Outcome probability, outcome percentile, and persona fields are null. Missingness flags keep “no score” distinct from a zero score.
Account context is used only when the account’s Lookup Target exists, the Outcome and Persona Set ids are set, and context delivery status is ready. The same lookup then returns the shared proxy plus account-trained Outcome and Persona fields. Faraday’s lookup response shape matches its public docs: propensity probability and percentile keys for the Outcome, and persona id and name keys for the Persona Set (lookup response elements; lookupOnTarget reference).
Replay and nightly fitting never mix the two kinds. Training uses only decisions whose stored context_kind matches the kind currently active for the account. That keeps the feature contract stable when an account graduates from shared-only to account-trained signals.
Exact features and encoding
The paid contextual policy is hierarchical linear Thompson sampling over a fixed base feature list plus persona indicators. The base names are:
| Feature | Encoding | Missing value |
|---|---|---|
intercept |
Always 1 |
Never missing |
credit_score_proxy |
Clamp the raw proxy to 1-12, then scale to 0-1 as (value - 1) / 11 |
Numeric 0, plus credit_score_proxy_present = 0 |
credit_score_proxy_present |
1 if a finite proxy was present, else 0 |
Not applicable |
outcome_probability |
Clamp to 0-1 | Numeric 0, plus outcome_probability_present = 0 |
outcome_probability_present |
1 if a finite probability was present, else 0 |
Not applicable |
persona:<id> |
One-hot membership for the assigned persona id | All persona indicators 0 when no persona is assigned |
Faraday’s lookup docs also warn that a match does not guarantee a propensity score: identity can resolve while Outcome fields remain null when enrichment is too thin to score. BanditBuzz treats those nulls through the present flags above, so absence stays distinct from a zero probability.
Shared ridge precision is fixed at 1. Option-to-shared transfer precision is fixed at 2. Those are product settings, not learned hyperparameters. The model is a Gaussian linear approximation: cheap to fit nightly, able to transfer evidence across sparse options, and not a claim that reward is truly linear or Gaussian. Predicted means used in replay are clamped to 0-1.
How a decide call uses Faraday
On POST /decide, BanditBuzz:
- Loads the standard Beta-Bernoulli Thompson policy for the decision point.
- Chooses a Faraday context lookup: account lookup if delivery is ready, otherwise shared lookup.
- Calls Faraday’s Lookup API with the person’s normalized identity set.
- If live lookup returns no match, falls back to a batch
person_contextrow matched by prior resolved person id or by email, phone, or name-plus-address history on the account. - If still no context, keeps the standard policy. The choice is still logged against the raw identifiers so later identity resolution can attach a person id.
- If the account is on the contextual tier, context exists, and the monthly person cap still has room, samples the linear contextual policy. Holdout assignment stays identical to the standard policy. Hitting the monthly person limit falls back to standard for that choice. Missing context, holdout, and standard fallback do not bill a contextual person unit.
Push sweeps reverse the lookup preference for scale: they read batch person_context first for people who already have a resolved person id, then call the live Lookup Target only for candidates the batch table has not covered yet. Every actionable or nothing pick still stores the same snapshot fields as /decide.
Snapshots freeze decision-time truth
Each decision stores:
policy_kind:standardorcontextualcontext_kind:sharedoraccountcontext_snapshot: the Faraday person id, credit proxy, outcome probability, outcome percentile, persona id, and persona name that existed when the choice was made
Nightly fitting and doubly robust replay read that snapshot. They never re-look up a later Faraday score and pretend it was available earlier. That blocks a class of time leakage where a model trained after the fact would score a past decision with context learned from later outcomes. Upstream Faraday model rebuilds still need their own leakage discipline; BanditBuzz’s guarantee is only that training and replay use the frozen decision-time vector.
The upgrade preview fits five person-level folds and scores only held-out people with the doubly robust estimator of Dudík, Langford, and Li (2011). Context decisions with no snapshot drop out of the estimate. The scoreboard stays in collecting until there are at least 500 context-bearing decisions, effective sample size of at least 200, and no importance weight above 20. Once those checks pass, the preview reports a sequential range. A cautious buyer should also require its lower end to exceed zero, but the current checkout path does not enforce that rule.
History sources that deepen account context
None of these sources is required for the first decide call. Each one deepens the account Outcome and Persona Set.
- API events and decisions. Every
/decideand/eventscall already carries identifiers. Accumulated account activity can train account signals even before a file upload. - CSV upload. A mapped customer file gives Faraday years of history in one shot. It is a snapshot: useful today, stale until the next upload.
- Database connection. BigQuery, Postgres, MySQL, RDS, or Snowflake can map a table’s row id, event time, value, and person identifiers. Faraday ingests on a schedule. Resolved rows import into BanditBuzz’s ordinary event ledger, so reward definitions stay the same while history grows. This path also tends to carry denser identifiers, which tightens identity resolution for attribution itself.
Mapping a source starts Outcome, Persona Set, and context delivery setup together. The public decide and events shapes do not change when those resources become ready.
Privacy and profiling limits
This path sends and stores personal identifiers. BanditBuzz stores decision rows, event rows, identifiers, and decision-time context. Connected history is imported into the event ledger. Destination credentials are encrypted at rest and opened only by delivery workers. Picks can write identifiers and choices to Klaviyo, BigQuery, Snowflake, S3, or a downloadable CSV when you connect a destination.
What is not claimed yet:
- Public commitments for data residency, retention and deletion schedules, subprocessors, a DPA, or SOC 2.
- Buyer-facing profiling opt-outs or account-level feature blocklists beyond what Faraday’s own Outcome configuration can express.
- A browser pixel or anonymous-visitor resolution path. Identified people only.
- A full decision, propensity, context, and reward export package for offline work outside BanditBuzz.
- Fine-grained key scopes, team roles, or a buyer-visible change audit log.
Billing is tied to resolved people whose context shapes a non-holdout contextual choice. The first such person in a Stripe billing month costs $1 at list price; further contextual choices for that person in the month are included. Standard fallbacks stay free. The current self-serve monthly cap is 1 million people.
What Faraday is and is not in this product
Faraday is the identity and trained-context layer. It matches people across identifiers, trains the account Outcome and Persona Set, and serves a compact Lookup payload. BanditBuzz owns the decision ledger, reward attribution, holdout, Thompson policies, nightly fit, and replay preview.
Faraday is not the action chooser. It does not pick among offer_10_off, postcard, and nothing. It does not replace your ESP or warehouse. It does not turn BanditBuzz into a general Faraday console. Buyers still see decide, record, scoreboard, sources, and destinations. Faraday’s resource graph stays an implementation detail except in posts like this one that need to name it.
The end-to-end picture is short:
identifiers and history → Faraday match and scores → compact vector (credit proxy, outcome probability with missingness flags, persona one-hot, plus intercept) → stored snapshot → standard or contextual Thompson choice → nightly fit and replay.
The practical test for the contextual tier is not whether Faraday can return a rich payload. Faraday’s Scope and Target model can carry many payload elements. The test is whether the small vector BanditBuzz actually uses improves reward on the account’s own logged traffic under cross-fitted doubly robust replay. Compact context is a product choice: sparse accounts cannot fit a large model well, and a large feature project would break the two-call start.
If lookup fails, if delivery is still building, or if the monthly person cap is reached, the call remains useful. It falls back to the free standard policy, logs the attempt, and keeps learning. Context is an upgrade to the same surface, not a second product to integrate.