/respond) and scheduled/evidence-based (offline cron). A third, drift-triggered retrain, ships as primitives but is not auto-scheduled in the default deployment (see §3). This page explains the mechanisms, the per-algorithm defaults, and the API/UI toggles that change the cadence.
TL;DR
Out-of-the-box defaults are conservative. Every new
algorithmModel row starts with autoLearn: false, learnMode: "none", learnSchedule: null — so tabular models won’t retrain unless you explicitly turn it on. Bandits and online learners ignore these toggles and always learn on every respond, because that’s the entire point of those algorithm families.The three retraining mechanisms
1. Continuous (incremental)
For four model types —thompson_bandit, epsilon_greedy, bayesian (when priors are configured), and online_learner — the platform performs an in-place state update on every /respond call that returns a positive or negative outcome. The respond handler reads the current modelState from DB, applies the algorithm-specific update rule, and writes the new state back, all in a single Prisma transaction. Side effects:
algorithmModel.modelStateadvances (e.g. Thompson arm’sα+= 1 on positive,β+= 1 on negative).algorithmModel.trainingSamplesincrements by 1.algorithmModel.lastLearnedAtis set tonow.
2. Scheduled offline retrain
Forscorecard, logistic_regression, gradient_boosted, and neural_cf, learning is a full pass over accumulated training data. The platform runs a periodic cron (/api/v1/cron/scheduled-retrains) that:
- Finds every model with
autoLearn: true,learnMode IN ("scheduled", "both"), andstatus IN ("active", "draft"). - For each, retrains if EITHER the schedule elapsed (
now - lastLearnedAt >= parseScheduleToMs(learnSchedule || "24h")) OR ~100+ new outcomes have accumulated since the last retrain (evidence co-trigger) — whichever fires first. - If due, runs a retrain pass inline (
trainModelFromOutcomes) that recomputes weights / trees / embeddings from accumulated interaction-history rows. - On completion, writes new
modelState,metrics,metricsHistory[],predictors[].importance, and bumps bothlastTrainedAtandlastLearnedAt.
learnSchedule is null is every 24 hours. Override with any of: "15m", "1h", "6h", "1d", "7d", or a cron expression like "0 3 * * *" (daily at 03:00 UTC).
3. Drift-triggered retrain (primitives only — not auto-scheduled)
The platform ships the building blocks for drift-triggered retraining but does not run them on a schedule today:computePSI/detectDrift(lib/model-governance.ts) compute a Population Stability Index and KL divergence on predictor score distributions. The default PSI trip threshold is 0.25 (PSI < 0.1 = stable; 0.1–0.25 = monitor; > 0.25 = drifted).runScheduledDriftChecks(tenantId)walks active models, compares recent interaction scores against each model’smetricsHistorybaseline, and returns the set of drifted models to enqueue for retrain.
runScheduledDriftChecks is only reachable through checkScheduledRetrains (lib/scoring/auto-learn.ts), and nothing currently calls checkScheduledRetrains. The wired /api/v1/cron/scheduled-retrains cron does its own inline schedule + evidence-threshold retrain and evidence decay — it does not run drift checks. So there is no automatic “retrain on drift” behavior in the default deployment.
To approximate drift-aware retraining today, either shorten learnSchedule so the model refreshes often enough to absorb shifts, or invoke /api/v1/cron/scheduled-retrains more frequently — the evidence-threshold co-trigger (below) will retrain after ~100 new outcomes regardless of schedule. Wiring runScheduledDriftChecks into a cron is roadmap work.
Toggling the cadence — API
Every field shown below can be set onPOST /api/v1/algorithm-models (model creation) and PUT /api/v1/algorithm-models/{id} (update):
Field reference
Toggling the cadence — UI
In the Studio at Algorithms → Models → , the model-detail panel has a Learning section with the same three controls: anautoLearn toggle, a learnMode dropdown, and a learnSchedule text field with format hints. Changes save via PUT immediately; the new cadence takes effect from the next scheduled-retrains run. Note the retrain cron itself runs daily by default (the in-process maintenance scheduler fires /api/v1/cron/scheduled-retrains on a 1440-minute cadence), so a shortened learnSchedule is only honored as often as the cron runs — point an external scheduler at the endpoint if you need sub-daily retrains.
Recommended cadence per use case
This is what we’d reach for if you handed us a fresh tenant tomorrow:- Cold-start with sparse historical data. Pick a
thompson_banditorepsilon_greedy. The continuous-learning behavior means the model is useful from request #1 — no warm-up needed. Move tobayesianafter you have ~10k interactions and want to incorporate predictor features. - Steady-state tabular workload with predictable churn.
scorecardorgradient_boostedwithautoLearn: true, learnMode: "scheduled", learnSchedule: "24h". Nightly is enough for most NBA workloads where the underlying customer behavior doesn’t shift hour-to-hour. - Volatile distribution (pricing tests, seasonal pushes, regulatory changes). Same as above but drop to a
"6h"(or shorter) cadence so the schedule absorbs sudden shifts. Automatic drift-triggered retrain is not wired today (§3), so a short schedule plus the ~100-outcome evidence co-trigger is how you stay fresh on volatile days. Watch PSI in Model Health to decide when to tighten the cadence further. - Catalogs with fast turnover (new offers daily). Drop the schedule lower (
"4h") or use drift-triggered exclusively — neural-cf especially benefits from fresh embeddings against the current catalog. - Compliance-sensitive deployments where every retrain needs human review. Set
autoLearn: falseand use the model promotion endpoint (POST /algorithm-models/{id}/promote) to manually advance retrained candidates throughdraft → shadow → challenger → championwith audit-log review at each step.
Common questions
“My scorecard model’smetricsHistory is empty. Did it train?”
Almost certainly not. New models ship with autoLearn: false; the scheduled-retrains cron skips them. Flip autoLearn: true, learnMode: "scheduled" and wait for the next cron tick. Or trigger a one-off retrain via the Studio’s “Retrain now” button (which calls /api/v1/algorithm-models/{id}/train).
“My Thompson bandit’s arms aren’t moving.”
Three possibilities. First, check that /respond calls actually carry outcomeTypeKey — without a classifiable outcome the bandit can’t update. Second, check lastLearnedAt on the model: if it’s null, no incremental update has fired, which usually means the model isn’t wired into the active decision flow’s score node. Third, check modelState.arms — if it’s {} or missing, the model hasn’t been initialized; either re-create the model (its init path will seed arms from the candidate offers) or set arms explicitly via PUT.
“What’s the difference between lastTrainedAt and lastLearnedAt?”
lastTrainedAt is set by full offline retraining (the cron-driven flow). lastLearnedAt is set by either offline retraining OR by any incremental update (every respond for bandits / online learners). So a Thompson bandit will have lastLearnedAt advancing every minute and lastTrainedAt permanently null — that’s correct, because Thompson doesn’t have an offline-train concept. A scheduled gradient_boosted will have both fields advance together every 24h.
“Can I retrain on-demand for testing?”
Yes. POST /api/v1/algorithm-models/{id}/train triggers an immediate offline retrain regardless of schedule. Useful for evaluating data fixture changes before a flow goes live.
See also: Algorithms & Models | Algorithm Models API | Experiments — Shadow vs Champion/Challenger