How it works

Parameterize once.

A/B experiment, gradual rollout, targeted override, adaptive bandit — in most stacks those are four products. Here they're four policy types over the same parameter. Once a value is parameterized, you never touch the client again to change how it's being tested.

This is the engine, and it works with no process attached. If you want the governed lifecycle on top of it — intent, approvals, retained learning — that's the change model.

1 primitive: the parameter4 policy types, same infrastructure<1ms in-process resolution
01
Parameterize

Declare the value once, in code

One-time setup, per codebase

A typed, versioned value with a default. This is the only integration step.

A parameter is a typed value with a default, an optional constraint range, and a namespace. It's declared in version-controlled YAML and pushed, so the set of controllable values is reviewable like any other config — with a CI check that fails on drift.

In code you read it through the SDK with an inline default. That default is authoritative when nothing else applies, which is what makes this safe to adopt incrementally: parameterize a value today, run nothing on it for a month, and behaviour is identical.

Type-safe SDKs, one shared conformance spec
React Next.js Svelte Node Python React Native TypeScript PHP Swift + OpenFeature

A given unit buckets identically on every platform — that's a conformance requirement, not a coincidence.

traffical.yaml
parameters:
  pricing.discount_pct:
    type: number
    default: 10
    constraints: { min: 0, max: 50 }
    layer: optimization-discounts
  pricing.free_shipping:
    type: boolean
    default: false
    layer: optimization-discounts
pricing.ts
const p = traffical.getParams({
  context: { userId, cartValue, plan },
  defaults: {
    'pricing.discount_pct': 10,
    'pricing.free_shipping': false,
  },
});

The context you pass is what targeting and contextual algorithms can reason about. Pass more than you need today.

02
Resolve

Every value resolves in the same explainable order

In-process, deterministic, sub-millisecond. No per-request call to us.

Your SDK holds a pre-built configuration document and resolves locally. There is no network call on the hot path — which means this works at the edge, works offline, and keeps working on the last known configuration if we're unreachable.

Resolution is deterministic: the same unit always lands the same way, so a user is consistent across sessions, devices, and surfaces without anyone storing per-user assignments.

Resolution order for a single parameter
1 Base default 10 — from your code
2 Environment override per environment
3 Policy override highest priority wins

Parameters no active policy touches keep their default. That's why an unused parameter costs nothing.

The Holdout / Parameters / pricing.discount_pct number
pricing.discount_pct
Discount percentage applied to all products (0–50)
Layer
optimization-discounts
1,000 buckets
Constraints
min: 0 · max: 50
enforced
Consumed on
Checkout · Mobile · Pricing service · Email
4 surfaces
high-value-cart adaptive
priority 1 · overriding this parameter
running
discount-optimization experiment
priority 2 · overriding this parameter
running

Two policies can target the same parameter. Priority decides who wins where they overlap — and the layer guarantees a user only ever gets one of them.

03
Isolate

Layers are how fifty tests run at once without colliding

Running one test is easy. Running fifty on the same users is the actual problem.

A layer owns a group of related parameters and a fixed pool of traffic. The rule inside a layer is simple: a given user is in at most one policy. Two tests that could interfere with each other belong in the same layer, and the layer makes overlap impossible rather than merely discouraged.

Across layers, traffic is allocated independently — a user's position in one layer tells you nothing about their position in another. So the checkout team and the search team run in parallel without comparing calendars, and neither contaminates the other's results.

  • Within a layer — mutually exclusive. One policy per user, guaranteed.
  • Across layers — independent. Concurrent tests stay clean.
  • Traffic is visible — the layer shows exactly what's allocated and what's free.
  • Reused across phases — advancing a change reuses the same slice, so nobody is re-randomized mid-flight.
layer optimization-discounts 3 policies · 1,000 buckets

Discount + free-shipping optimization. High-value carts get a dedicated bandit.

02505007501000
1 high-value-cart 0–300
2 discount-optimization 300–800
3 free-shipping-test 800–950
unallocated 5%
layer search-ranking 1 policy · independent bucketing

Relevance weights. A user's bucket here is unrelated to their bucket above.

1 relevance-tune 0–1000 · full layer
04
Choose the mechanism

Four policy types, one parameter, zero client changes

This is the part that usually requires a second vendor. Here it's a dropdown.

A policy is what decides which users get which parameter values right now. The policy type determines how that decision gets made — a fixed split, a ramp, a targeting rule, or an algorithm that keeps learning. Switching between them is a configuration change in the dashboard. Your application code is already done.

Static — A/B experiment

A fixed split between a control and one or more treatments, held constant for the run. This is what you want when the question is "which of these is better, on average?" and you need a clean causal answer.

allocation: fixedanswers: which is better
Rollout — gradual ramp

One treatment, exposure increasing in health-gated steps: 1% → 10% → 50% → 100%. No control arm, because the question isn't "is it better" — it's "does this hold up as more people hit it?"

allocation: rampinganswers: is it holding
Targeted — conditional override

A value that applies only to users matching a condition — a plan tier, a market, a cart value, a device. Useful for entitlements, staged geographic launches, and giving one segment different economics.

allocation: by conditionanswers: who gets what
Adaptive — bandit optimization

Traffic shifts continuously toward better-performing values, within your guardrails. For questions where the best answer moves over time or differs per user, a fixed 50/50 split spends most of its life on the losing option.

allocation: learnedThompson · UCB1 · ε-greedy · contextual

Contextual bandits personalize without a separate ML stack

A linear contextual bandit uses the context you already pass into getParams — plan, cart value, market, device — to learn which value works best for which kind of user, not just which is best on average. Same parameter, same SDK call, no model to deploy.

05
Measure

Every policy type reports through the same analysis engine

A verdict for the review, with the per-arm detail your data team needs underneath it.

Layers / experiments-catalog / catalog-layout-test running · static
Metrics & Significance
Keep running — intervals still cross zero
Leading arm is up 1.9%, but not yet separable from control. Verdict expected in ~3 days.
leading arm
+1.9%
spacious, on add_to_cart conversion
measurement progress
58%
of the sample needed for a verdict
guardrails
1 / 1
purchase conversion within threshold
add_to_cart conversion primary
spacious +1.9% [−6.9, +10.8] · normal −0.1% [−8.8, +8.7]
anytime-valid
purchase conversion guardrail
both arms inside the acceptable band
passing

Metrics resolve from certified definitions owned by your data team, so "conversion" means one thing across every policy and every team. Compute them from Traffical's events or directly against your warehouse — Postgres, BigQuery, Snowflake, Databricks, ClickHouse — and the analysis is identical either way.

Postgres BigQuery Snowflake Databricks ClickHouse

Adaptive policies use the same metric definitions as their reward signal, so an optimizing policy and a static experiment are measured against exactly the same yardstick. That's what makes the two comparable.

Anytime-valid sequential testing

Peek whenever. No alpha penalty for looking early.

CUPED variance reduction

Uses pre-period behaviour to reach a verdict on less traffic.

Correct ratio-metric variance

Per-session and per-order denominators handled properly, not approximated.

06
Govern & retain

The primitives are flexible. The record is not optional.

Raw policies are powerful. Changes add the meaning and the memory on top.

You can operate at the policy level directly — that's the whole machinery, exposed. But a policy says nothing about why it exists or whether anyone approved exposing users to it. That's what a change adds: intent, an approved measurement plan, per-phase guardrails, and a decision record on every transition.

Because every policy runs through one system, the results accumulate. A completed run keeps its question, its arms, its evidence, and its verdict as a durable record — searchable by parameter, surface, or metric. The next proposal starts from what you already know instead of from a blank page.

The audit trail is the proof beneath that memory: who acted, what the evidence said, which thresholds were in force. Snapshotted, so an old decision can still be reconstructed exactly.

What the platform retains
The question and its answer

"Does a deeper checkout discount pay for itself?" — 15% beat 10% on revenue per session by 4.2%, margin held. 25% won conversion but lost margin.

3 policies · shipped 15% · May 2026
The things that failed, and why

High-urgency cart copy raised add-to-cart but pushed refunds past the guardrail. Auto-reverted at 03:41. Do not retry without a refund-rate plan.

1 policy · reverted · Mar 2026
The thresholds that were in force

refund_rate ≤ +0.5% blocking, checkout_error_rate ≤ +1% blocking, margin ≥ −2% warning — as certified on the day the policy started.

Protocol v3 · snapshotted
Built for automation

Autonomy with a hard ceiling

Anything that reduces risk can happen automatically. Anything that increases it waits.

Nothing here hands your product to a model. Every action carries a fixed permission that your team configures — and the only ones that run unattended are the ones that can only reduce risk.

Pause a running policy
Exposure stops, users fall back to defaults
runs unattended
Revert a change
Restores the previous default
runs unattended
Reduce exposure
Shrinks the blast radius
runs unattended
Start traffic on a low-risk change
Only with a certified plan and zero open risks
configurable
Increase exposure
Ramping toward more users
needs approval
Change a metric or a threshold
Measurement belongs to your data team
not permitted
Make a value the product default
The permanent decision
human only

The pattern: anything that reduces risk can happen automatically. Anything that increases it waits for a person. Measurement is never negotiable.

Two layers. Adopt the second when you want it.

The engine runs on its own. The operating model is what a team drives it with.

Layer 1 · this page

The engine

Parameters, layers, policies, and the analysis engine. Everything above works with no process attached — create a policy, split traffic, read the result. Teams who already know how they want to run experiments can stop here and get full value.

  • No new vocabulary — policies and layers, operated directly.
  • No approval flow — you decide when something ships.
Layer 2 · the change model

The operating model

A change wraps the same machinery in intent, an approved measurement plan, per-phase guardrails, and a decision record — so the safe path becomes the default path and what you learn is retained rather than remembered.

  • Add it later — enabled per organization, on your timeline.
  • Same primitives underneath — every phase still materializes one policy in one layer.

Most teams start at layer one and grow into layer two when the number of people — or agents — touching production makes "we'll remember why we did that" stop being true.

Parameterize one value. Decide the mechanism later.

You don't have to know whether it's an experiment, a rollout, or a bandit when you write the code. That's the point.