Skip to main content

Evolution depth — the model tier ladder

“Model depth” is the algorithm-complexity tier a model sits at. Kaireon defines an evolution ladder so a model can graduate from a simple, data-cheap algorithm to a richer one as it accumulates evidence and demonstrates lift.

The tier ladder

ALGORITHM_TIERS (platform/src/domain/algorithms.ts) lists six tiers:
In practice the auto-upgrade path covers only the first fourscorecard → bayesian → logistic_regression → gradient_boosted. The /upgrade endpoint returns 400 No upgrade available once a model reaches gradient_boosted, and the readiness thresholds below stop there too. thompson_bandit and epsilon_greedy are model types you create directly, not rungs the upgrade ladder climbs to.

Readiness thresholds — EvolutionConfig

EvolutionConfigSchema defines a per-model, nullable config (defaults apply when unset): The thresholds block gates each promotion on BOTH a minimum sample count and a minimum AUC:

Reading readiness — GET /evolution-history

GET /api/v1/algorithm-models/{id}/evolution-history reports where a model sits on the ladder and how close it is to the next rung:
  • progress.progressPct — the average of the sample-progress % and AUC-progress % toward the next tier (each capped at 100).
  • progress.readyToEvolvetrue once progressPct >= 100 (both sample and AUC targets met).
  • timeline[] — the model’s version history with the model type at each version.

Triggering the upgrade — POST /upgrade (manual)

Promotion is a manual action — POST /api/v1/algorithm-models/{id}/upgrade (RBAC admin/editor). It:
  1. Creates a new draft model of the next tier (key: "{key}-upgraded-{newType}"), seeded with that tier’s default hyperparameters and the current model’s targetField, targetSchemaKey, and predictors.
  2. With {"createExperiment": true} in the body, also creates a draft champion/challenger Experiment (50/50 split) with the current model as champion and the upgraded model as challenger.
  3. Returns { upgraded, experiment } with 201.
The upgraded model starts as draft and inert — you still run it through the lifecycle to make it score.
autoEvolve is advisory only — nothing auto-promotes. The autoEvolve flag persists (schema, PUT, and the tier-progression UI toggle) and evolution-history reports readyToEvolve, but no background job reads autoEvolve to trigger an upgrade. Crossing a threshold does not create the next-tier model on its own — an operator must call POST /upgrade. Automatic tier promotion is roadmap work.

Auto-rollback guard on champion promotion

A champion promotion that includes a metrics snapshot (AUC and error rate) now consults the auto-rollback evaluator BEFORE the transaction that demotes the incumbent.

Trigger

The guard fires only when:
  1. toStatus === "champion", AND
  2. metricsSnapshot.auc is supplied by the caller, AND
  3. The incumbent champion in the same family has a stored metricsSnapshot.auc to compare against.
If any of those is missing, the guard silently skips — there’s no honest comparison to make.

Thresholds

Override per call via rollbackThresholds. To force a promotion that breaches a threshold, pass bypassRollbackGuard: true — the returned object surfaces rollbackGuardBreaches so the bypass is auditable.

Failure mode

When the guard fires and bypass is not set, promoteModel throws ModelRegistryError(...) with reason rollback_guard. The incumbent champion stays in place; no DB state changes.

TS-side preprocessing orchestrator

lib/ml/preprocessing.ts bundles WOE binning + target encoding into a single fitPreprocessing / applyPreprocessing API. Why TS-side instead of porting into gbm_trainer.py:
  1. Most Kaireon scoring engines (bayesian, thompson, online, epsilon, scorecard) live entirely in TS and never cross the Python boundary. A TS-side bridge makes binning + encoding available to all engines, not just GBM.
  2. Keeps W6.2 testable + deterministic without a Python service in CI.

Honest limit

V1 fits encoders only. Saving / loading them across train + score boundaries is the caller’s responsibility — AlgorithmModel.modelState is the natural home but is not yet auto-populated from this orchestrator. The Python gbm_trainer.py path IS wired, though: serializeFitForGbmTrainer() emits the {bin_edges, target_encodings} payload the ml-worker /train/gbm route accepts, so a caller can ship the fit alongside the training data and keep Python-side training aligned with TS-side scoring.