
The Experiments page.
How experiments work
KaireonAI runs experiments using a champion/challenger pattern, not a generic control/treatment A/B test. The vocabulary maps as follows:
The full lifecycle is
draft → active → paused → archived (the four values accepted by the status field). Status transitions happen via PUT /api/v1/experiments/{id} and are guarded by RBAC (admin/editor).
End-to-end data flow
Champion/challenger mode
This is the default mode. Traffic is split among the champion and one or more challengers. Each model scores the same candidates but on different customers; outcomes feed back through/respond to compare conversion rates head-to-head.
To set it up:
- Train a candidate model alongside your live champion (see Algorithms & Models). It must reach
registryStatus = "production"or"challenger"to be eligible for promotion via auto-promote. POST /api/v1/experimentswith:championModelId: the current live modeltrafficSplit: { championPct: 50 }— what fraction of non-holdout traffic the champion getschallengers: [{ modelId: "model_xyz", trafficPct: 50 }]— the competing model and its allocationholdoutPercent: 10— bypass 10% entirely for measurementstatus: "active"
- Validation rule:
championPct + sum(challengers[].trafficPct)must equal 100. The endpoint returns400otherwise. - Fire traffic. Variant assignment is deterministic per customer — the same
customerIdlands on the same variant on every call so personalization is consistent. - After enough samples (see
requiredSampleSizein the results response), check/experiments/{id}/resultsfor statistical significance. - Decide the winner: manually update the experiment via
PUT /api/v1/experiments/{id}settingstatus: "archived"and (optionally) swap the championModelId. Or letautoPromote: truehandle it afterpromoteAfterDaysif the challenger crossespromoteThreshold.
Shadow mode
Shadow mode is a different mechanism that lives on the model registry itself, not on theExperiment resource. Use it when you want to evaluate a candidate model on real production traffic without changing any decision the customer sees.
How it works:
- A model with
registryStatus = "shadow"(or any model listed in aScoreNode.shadowModelKeysarray) scores every candidate in parallel with the live champion. - Shadow scores are written to the decision trace’s
scoringResults[].shadowScoresmap, keyed by model key. - Shadow scores never enter ranking, never enter
/recommendresponses, never affect what the customer is shown. They are recording-only. - After enough traffic, you can compare per-customer ranking similarity (Kendall tau, top-K overlap, expected lift on observed outcomes) between champion and shadow off-line.
To promote a model into shadow mode:
Models flow through a strict five-stage registry lifecycle:
draft → shadow → challenger → champion → archived. Transitions are made through a dedicated promotion endpoint that enforces transition legality, the “one champion per family” invariant, and an auto-rollback guard, and writes an AuditLog row for every change. There is no direct PATCH on registryStatus — write attempts via PUT /algorithm-models/{id} are ignored.
toStatus (required, one of draft/shadow/challenger/champion/archived), family (optional grouping key — the “one champion per family” rule fires here), bypassRollbackGuard (optional, admin escape hatch), metricsSnapshot (optional key-value map recorded with the promotion). Returns 409 if the rollback guard trips, 400 for invalid transitions, 404 if the model isn’t in your tenant.
The accompanying read endpoint GET /api/v1/algorithm-models/resolve-lifecycle returns the current champion plus all challengers and shadow models for the tenant — useful for diagnosing which models are wired into which lifecycle slot.
Or attach the model to a specific decision flow’s score node:
decision_traces.scoringResults[].shadowScores or aggregate them via the Decision Traces API.
Holdout group
TheholdoutPercent field reserves a slice of customers who bypass the experiment entirely. The variant engine sticky-hashes them to __holdout__ and the platform delivers a baseline (no personalization, or whatever the kill_switch fallback is) for those calls. Outcomes still flow through /respond, which gives you the denominator for incrementality math:
Statistical methods
The results endpoint uses three textbook procedures:- Two-proportion z-test for significance:
Φ is the standard normal CDF, approximated via Abramowitz–Stegun 7.1.26 with input z/√2 (accurate to ~1e-7). The platform’s implementation has been verified against textbook tables: at z = 1.96 it returns p = 0.0500 (matching the canonical 95% threshold), at z = 2.576 it returns p = 0.0100, etc.
- Wilson 95% confidence interval for each variant’s conversion rate. Wilson CIs are preferred over the normal-approximation interval because they remain valid for small samples and rates near 0 or 1.
- Required sample size estimate, using baseline rate × minimum detectable effect × number of variants:
requiredSampleSize so operators know when they have enough power to declare a result.
Mode comparison at a glance
GET /api/v1/experiments
List all experiments with their champion model and challengers. Supports cursor-based pagination.Response
POST /api/v1/experiments
Create a new experiment. Traffic split must sum to 100%.Request Body
Validation
- Traffic split must sum to 100%:
championPct + sum(challengers[].trafficPct)must equal exactly 100. Returns400if not. - Key must be unique per tenant. Duplicate key returns
400.
Example
201 Created
GET /api/v1/experiments/
Get experiment details with champion and challenger models.PUT /api/v1/experiments/
Update an experiment. Challengers are replaced entirely when thechallengers array is provided.
Request Body
All fields optional. Accepts the same fields as POST exceptkey (which is immutable) — including status, championModelId, trafficSplit, autoPromote, promoteThreshold, promoteAfterDays, holdoutPercent, challengers, and results (object, for stored outcome data). When both trafficSplit and challengers are supplied, their percentages must still sum to 100.
DELETE /api/v1/experiments/
Delete an experiment and its challengers. Response:204 No Content
DELETE also works at the collection level:
DELETE /api/v1/experiments?id={experimentId}. Both the path parameter and query parameter forms are supported.GET /api/v1/experiments//results
Returns uplift analysis and statistical significance for treatment vs holdout. The endpoint first checks for live variant assignment data. If no assignments exist, it falls back to stored JSON results. ThedataSource field reports which was used — "live" (aggregated from variant_assignments + interaction_history) or "stored" (from the experiment’s results JSON). If neither is available, it returns { "experimentId", "hasResults": false, "message": "…" } instead of the full analysis below.
Response
variants[] breakdown is derived from the experiment’s stored results JSON, so it is [] when no per-model results have been recorded (each entry carries auc; challenger entries also carry pValue).
Statistical Methods
- Two-proportion z-test for significance testing (p < 0.05)
- Wilson confidence intervals for per-variant conversion rates
- Required sample size estimation based on baseline rate and minimum detectable effect
Roles
See also: Algorithms & Models