Skip to main content
A freshly-created algorithmModel row does nothing until an operator advances it through four orthogonal lifecycle dimensions. This is the page that explains what those dimensions are, what the safe defaults look like, and the explicit sequence to take a model from creation to scoring real customer requests.

The four lifecycle controls at a glance

A model in status: "active", registryStatus: "draft" is “operationally live but not a champion” — it can be referenced by name from a decision flow’s score node, but it isn’t the default scorer for its family. A model in status: "draft", registryStatus: "champion" is impossible to construct via the API — the promote endpoint refuses to advance a draft-status model. These two axes are deliberately separate so operators can stage operational rollouts independently of model-evaluation lifecycle decisions.
Out-of-the-box defaults are intentionally inert. Every new model row starts as status: "draft", registryStatus: "draft", autoLearn: false, learnMode: "none", outcomeWeights: null. There is no automatic “go live” path. This is by design — you should never wake up to find a model you forgot about scoring production traffic.

What happens when you POST a model with no overrides

The persisted row will be:
This model:
  • ❌ Is invisible to /recommend (filtered out by the status: "active" predicate).
  • ❌ Is not a champion for any registry family (registryStatus: "draft").
  • ❌ Will not retrain on schedule (autoLearn: false).
  • ❌ Has no learned state, no metrics, no AUC.
  • ✅ Exists in the database and can be inspected via GET /algorithm-models/{id}.
It’s a placeholder. Nothing more.

The four-step path to live champion

To turn the inert row into a model that actually scores production traffic, an operator does four explicit things — and they correspond exactly to the four lifecycle dimensions above.

Step 1 — Activate operationally

Set status: "active". This makes the model visible to /recommend and to the registry-promote logic. You can do this on creation by passing "status": "active" in the POST body, or via PUT later:
After this step the model is operational but still inert from a scoring standpoint — no decision flow refers to it yet, and it isn’t the registry champion.

Step 2 — Promote through the registry

Move the model through the lifecycle: draft → shadow → challenger → champion. Each transition is enforced by POST /algorithm-models/{id}/promote and writes an AuditLog row. The “one champion per family” invariant means only one model in each registryFamily can sit at champion at a time — promoting a new one auto-demotes the old.
Champion promotion is four-eyes gated. promoteModel refuses toStatus: "champion" unless an approved ModelApproval row exists for this model at its current version — otherwise it throws approval_required and the promote endpoint returns 409. Request one via POST /api/v1/model-governance {"action":"request_approval"}, then have a different admin approve it (the reviewer must differ from the requester). Emergency-only escape hatch: bypassRollbackGuard: true skips the gate and writes a louder audit entry.
The registry family is not taken from the promote request body — it’s read from the model’s registryFamily (set when the model was created, defaulting to the model name). The one-champion-per-family invariant auto-demotes the prior champion in the same family to archived in the same transaction. See Experiments — shadow vs champion/challenger for the full registry lifecycle invariants (auto-rollback guard, one-champion-per-family rule, audit-log row written on every transition). Promote-endpoint status codes: 404 model not found, 400 invalid transition, 409 rollback-guard breach or missing approval, 200 on success. Alternative: instead of going through the registry, you can wire the model into a specific decision flow’s score node by its key. The decision-flow engine looks up the score node’s modelKey directly, bypassing the registry-champion resolution. Use this for per-flow specialization (e.g. “this flow’s credit propensity is bayesian-v3 even though the default credit family champion is gbm-v7”).

Step 3 — Enable learning (or accept stasis)

For tabular models, learning is off by default. Without flipping the toggle, your model will keep producing the same scores forever:
Bandits, online-learners, and Bayesian-with-priors do NOT need this — their continuous-update path is hardcoded in the respond handler and runs on every outcome regardless of autoLearn. See Learning cadence for the full per-algorithm cadence table.

Step 4 — Configure outcome weights

outcomeWeights is a JSON map from outcome-type key to a signed numeric weight. The default behavior — when outcomeWeights is null — falls back to +1 for any outcome classified as positive and −1 for any classified as negative. That’s almost always wrong for nuanced workloads.
Misconfigured outcome weights silently invert your learning. If outcomeWeights is null but your most common positive outcome key isn’t classified "positive" in outcome_types, the respond handler logs a warning (“no explicit weight for outcome X; using default”) and treats it as a neutral signal. Repeat this 10,000 times and your bandit’s posteriors lock onto whichever offer happens to NOT be your business’s best one. Always set explicit weights for the outcomes your business actually cares about.

Reading the lifecycle of an existing model

GET /algorithm-models/{id} returns everything you need to inspect a model’s lifecycle position. Useful field combinations:

What the platform does NOT do automatically

To prevent surprises, the platform deliberately does none of the following:
  • ❌ Activate models on creation. You must set status: "active" explicitly.
  • ❌ Promote models to champion. Even an active model never becomes the default scorer until you POST /promote.
  • ❌ Enable auto-learning. Tabular models stay frozen until you flip autoLearn: true.
  • ❌ Infer outcome weights. The default +1/−1 mapping is a fallback, not a recommendation.
  • ❌ Train on creation. Even gradient-boosted with autoLearn: true waits for the first cron tick after learnSchedule elapses; if you want a one-off immediate retrain, call POST /algorithm-models/{id}/train.
If you want any of these to happen, configure them — every dimension is independently controllable, every default is conservative.

Bulk operations

Setting up several models at once (e.g. shadow-mode rollout of a model family) is supported but requires the same per-model explicit configuration. The platform does not have a “bulk go-live” endpoint and is unlikely to add one — each model going live should be a deliberate, audited decision. For programmatic setup, the recommended pattern is:
After enough shadow-mode evidence (compare shadowScores in decision_traces against the current champion’s scores), continue to challenger and then champion.

Scope hierarchy

A single AlgorithmModel row is not channel- or direction-bound. Instead, the learned state is kept in ModelAdaptation rows, one per (scope, scopeId) cell. This is how the same Thompson bandit can maintain independent posteriors for “Platinum Card on email” vs “Platinum Card on web” vs “Platinum Card on inbound calls”. Read order in /recommend propensity scoring (most-specific to least):
This is the order of the standalone read chain in pipeline-runner.ts propensity scoring — channel and category are tighter cells than direction, so they’re consulted first. (The offer+blend fallback at tier 2 uses a slightly different internal order for its shrinkage target — channel → direction → category → global — because it wants the most-specific broader cell.) Each tier has its own evidence threshold before it’s trusted: The propensitySource field on the decision trace records which tier fired for each candidate — useful for debugging “why did this offer rank where it did?”.

Storage shape

The (scope, scopeId) row is the unit of adaptation, not the model instance. One model row holds many scoped posteriors, so overlapping cells in the hierarchy don’t duplicate state. This keeps the table compact and easy to reason about — one row to look up, one set of posteriors to compare across scopes.
See also: Learning cadence | Maturity Ramp (BCB-MR) | Uplift Modeling (T/X-learner) | Algorithm Models API | Experiments — shadow vs champion/challenger | Decision Traces — provenance deep-dive