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.
Declare the value once, in code
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.
A given unit buckets identically on every platform — that's a conformance requirement, not a coincidence.
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
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.
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.
Parameters no active policy touches keep their default. That's why an unused parameter costs nothing.
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.
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.
Discount + free-shipping optimization. High-value carts get a dedicated bandit.
Relevance weights. A user's bucket here is unrelated to their bucket above.
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.
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.
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?"
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.
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.
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.
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.
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.
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.
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.
"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.
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.
refund_rate ≤ +0.5% blocking, checkout_error_rate ≤ +1% blocking, margin ≥ −2% warning — as certified on the day the policy started.
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.
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.
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.
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.