One number, four places
Here is a discount the way most codebases hold it:What a flag buys you, and where it stops
The usual answer is a feature flag. You wrap the new behavior inif (flag), deploy it dark and switch it on later. That separates the release from the deploy for one change. It does nothing for the value itself.
- A flag holds one bit. The 15% still lives in code, next to the 10%. Trying 12% is a new code change and a new deploy.
- Every variant becomes a branch. Three flags on one feature (
new_layout,new_copy,new_discount) make eight combinations, and nobody tested most of them. - Flags pile up. Each one needs a cleanup pull request after it ships. The ones nobody cleans up stay in the code as dead branches that still run.
- Flag-first
- Parametrized
Parametrize the substance
To parametrize a value, you make it a typed parameter with three properties:- It has a type.
string,number,booleanorjson, with optional constraints such asmin,max, apatternor a list of allowed values. Traffical enforces them on every edit, so nobody can serve a 500% discount. - Its default is today’s value. On day one nothing changes for any user. You can parametrize a whole page before you have a single test planned.
- Your code reads it through the SDK. Pass the same default in code. If Traffical is unreachable, your app keeps running on it.
ui.promo_banner.visible and its headline, CTA and threshold as parameters. Feature flags covers the binary case.
One release, then values
Over the same stretch of time, here is how many values each approach gets live, and how many deploys it costs. Illustrative timeline. A config change reaches your apps on the SDK’s next config refresh, 60 seconds by default. The lower lane is the life of one parameter. After the first deploy, every step is a config change on the same line of code:- Default. The parameter ships with today’s value, 10%. Nothing changes.
- Canary. A small share of users gets 15% while health checks watch assignments, sample ratio and guardrails.
- A/B test. Traffic splits between 10% and 15%. The measurement plan fixes the primary metric and the guardrails, such as margin, before the test starts.
- Rollout. The winner ramps from 25% to everyone. Health checks can pause or roll back the ramp before anyone notices a problem.
- New default. Completing the change writes the winning value into the parameter default. There’s no cleanup pull request, because there was never a flag to remove.
- Next question. The next test starts from the new default. Or hand the parameter to a bandit that keeps moving traffic toward the best value.
The payoff compounds
A flag pays off for one decision. A parameter keeps paying off. The code that readspricing.discount_pct is the same for the first experiment and for the tenth: new values, a new split, a new audience or a new goal metric are all a new policy on the same parameter. The same holds for a group of parameters. Once checkout reads its layout, headline and discount from Traffical, every later checkout experiment, whatever it combines, needs no code change and no deploy.
So the cost of parametrizing is paid once per value, while the cost of a flag is paid once per experiment. The more often a value is worth revisiting, the bigger that difference gets.
Examples across your product
If a decision can be written down as a value, it can be parametrized. These are the patterns we see most.- Pricing
- Ranking
- AI features
- Risk
- Onboarding
- Messaging
- Page layout
- Mobile apps
What you gain
Iterate without deploys
The deploy happens once. Every value after that is a config change that reaches your apps within a minute.
Test more than two options
A/B/n tests and value sweeps on the same parameter. 10%, 12%, 15% and 20% in one test, with no new branches.
Roll out a value
Ramp a new price, weight or model version gradually, with health checks that can pause or roll back.
Let the optimizer tune it
Bandits need values to choose between. A typed parameter gives them a range, and contextual bandits pick the best value per visitor.
One value everywhere
Web, iOS, Android, backend services and emails read the same parameter, and each user gets the same value on all of them.
A kill switch for every value
Pause the policy and its users are back on the default with the next config refresh. You don’t need a separate flag for that.
Ship by changing the default
The winning value becomes the new default. No cleanup pull request, no flag debt, no dead branches.
More people can act
Product managers, analysts and agents can change values from the dashboard, CLI or MCP server, under the same approvals.
Product teams run their own experiments
With flags, every experiment starts as an engineering ticket, because every new variant is code. Once the values are parameters, that dependency goes away. A product manager, analyst or designer can choose the values, the split, the audience and the goal metric in the dashboard, draft the change and see the results, without waiting for a sprint. Engineers stay involved where they add the most: deciding what to parametrize, and reviewing the experiments that carry real risk. Removing the deploy also removes the review that came with it, so Traffical puts the review where the experiment now happens. You decide who signs off on what:- Building and shipping are separate permissions. Any member can design an experiment end to end. Starting traffic, ramping it or changing a default takes the operate permissions, which only admins and owners hold.
- Teams own their parameters. The pricing team can own the
pricing.*group. Then only its members can edit those parameters, and its approvers sign off changes to them. Checkout, search and CRM get the same arrangement for their own values. See teams. - Risk decides how much sign-off is needed. Traffical computes a risk class for every change. A low-risk copy test with certified metrics can start on its own. A pricing change on a backend service waits for a human, and no experiment carries traffic without an approved measurement plan.
- Every decision is on record. Each start, ramp, approval and promotion is logged with who acted and the evidence they saw. See approvals and autonomy.
AI agents can experiment, within limits you set
Parametrization is also what makes experimentation safe to hand to an AI agent. Through the MCP server, an agent can draft a change on existing parameters with new values, a split and a goal metric, request its start, watch the evidence and propose the next step. None of that touches your source code or needs a deploy. It also only works on parameters that already exist: the agent’s tools can’t add a parameter to your code. Every value you parametrize is one an agent can help you test. The limits are the same ones that apply to people, plus a few more:- Constraints bound the values. If
pricing.discount_pctallows 0 to 50, no policy can serve 80%, whoever writes it. Allowed values work the same way for strings such as a model name or a layout. - Scopes bound the actions. An agent’s token only sees the tools its scopes allow. A read-only connection can’t change anything.
- Risk decides what runs and what waits. Pausing and reverting always run, so an agent can always stop a bad experiment. By default, starting or ramping traffic runs without a person only for low-risk changes, and changing a default waits for a human at every risk level. Agent write access is off until your organization turns it on.
- Agents never approve. Approvals happen only in the dashboard. When an agent’s action needs sign-off, it becomes a proposal that a person reviews.
What still needs a deploy
Parametrization removes most deploys, not all of them. Know where the line is.- Values your code can’t interpret. A parameter can only choose between behaviors the code already has. If
pdp.layoutgets a new value"carousel", the carousel component has to exist first. Values the code uses directly, such as copy, thresholds, weights and prompt text, need nothing new. - Older app versions. An installed app only knows the parameters and values of the build it came from. Keep allowed values backward compatible, and make every code default a value that build supports.
- Instant changes. A config change reaches SDKs on their next refresh, 60 seconds by default. If a kill switch has to act faster, lower
refreshIntervalMson the client. - Measurement. Being able to change a value doesn’t mean you can learn from it. Track the outcome event, or define the metric in your warehouse, before the first test.
Choosing what to parametrize
Good candidates:- Values people argue about in meetings: discounts, thresholds, headlines.
- Revenue and cost levers.
- Ranking and recommendation weights.
- Model, prompt and provider choices.
- Any value hardcoded in more than one place.
- Constants nobody will change, like a protocol version or a timeout tuned once.
- One JSON blob for unrelated settings. A test on one field changes all of them, and you can’t constrain any field on its own. Group values that change together and split the ones that don’t.
- A parameter for every pixel. Ten parameters on a card nobody plans to test add overhead, not options.
Parametrize an existing value
1
Name it after what it is
pricing.discount_pct, not new_discount_enabled. Dot notation keeps related parameters together. See namespaces.2
Define it with today's value as the default
.traffical/config.yaml
npx @traffical/cli push. Nothing changes for any user, because the default is the value they already get.3
Replace the literal with an SDK read
Every surface reads the same key, with the same default in code:
4
Track the outcome
Fire the event where the outcome happens, such as
track("purchase", { value: orderTotal }) on the order confirmation, so the first test has something to measure.Next steps
Parameters
Types, defaults, constraints and config-as-code.
Feature flags
Where a boolean is still the right tool.
Optimization
Let bandits tune a parametrized value.
Agent skill
Have your coding agent find and parametrize values.