Most experiment tools hand your code a variant name. Your code then decides what the name means:
variant = experiments.variant_for(user, 'checkout-button-test')
color = variant == 'test' ? 'green' : 'gray'That works for the length of the test. Then the test ends, green wins, and the branch stays. Somebody has to come back, delete the condition, hard-code green, and deploy. Until they do, the code refers to an experiment that no longer runs. Multiply that by every test a team runs, and you get a codebase full of dead if variant == 'test' branches.
ABMeter makes a different choice. Your code never names an experiment or a variant. It asks for the value of a parameter, and ABMeter answers.
One button, three stages
Take one parameter: checkout-button-color, a string with the default gray. With the Ruby SDK, the checkout page reads it like this:
user = ABMeter::User.new(user_id: current_user.id)
color = ABMeter.resolve_parameter(
user: user, parameter_slug: 'checkout-button-color'
)
render_checkout_button(color: color)These three lines do not change in any of the stages below.
Before any experiment. Nothing controls the parameter, so every user gets its default, gray.
During the experiment. You create a variant that sets checkout-button-color to green and start an experiment with two groups: control, which keeps the defaults, and the green variant. ABMeter splits users between the groups. Each user consistently gets gray or green, and the call records that the user saw it. Your code did not change.
After the experiment. You stop the test, read the result, and pick green as the winner. ABMeter archives the experiment and writes the winner’s values into the parameters’ defaults: checkout-button-color is now green for everyone. Your code did not change, and there is nothing to clean up.
If you pick control instead, nothing is written, and everyone stays on gray. Between the stop and your decision, every user gets the current default too: only a running experiment changes what users get.
A feature flag fits into the same picture. A flag that sets checkout-button-color to green for your own team shows the new color to them before any experiment starts. Again the code does not change.
The call has the same shape in every SDK:
| SDK | Call |
|---|---|
| Ruby | ABMeter.resolve_parameter(user:, parameter_slug:) |
| Python | abmeter.resolve_parameter(user, parameter_slug) |
| Node.js | await abmeter.resolveParameter(user, parameterSlug) |
| Go | abmeter.ResolveParameter(user, parameterSlug) |
| Browser JavaScript | abmeter.resolveParameter(slug) |
| React Native | abmeter.resolveParameter(slug) |
The browser and React Native SDKs take the user once, in configure, so the call needs only the slug. The Go call also returns an error, as Go calls do.
Why the parameter is global
Why does the parameter not belong to the experiment? Because the code outlives the experiment.
A test runs for a few weeks. The checkout button stays in your code for years. If the parameter belonged to the test, the code would depend on something that is gone in a few weeks, and that is exactly the dead branch from the start of this article.
So a parameter does not belong to any experiment. Its name is unique across all of your spaces, and it has one default; A space is a pool of users explains why. An experiment borrows it for a while and gives it back with a new default. The next experiment on the same button borrows it again, say green against orange, and the checkout page still runs the same three lines.
One rule follows from this. Two experiments cannot test the same parameter at the same time: ABMeter refuses to start the second one. If they could, the first one to finish would change the default under the other one’s control group, and the other test would compare against a moving target.
Name the knob
Every behavioral difference you want to test is a parameter value, so pick values that make sense after the test ends. checkout-button-color: green makes sense forever. A boolean like use-new-checkout: true works too, and once the new checkout wins, you delete the old path when you are ready.
So the question to ask before a test is: what is the knob? A color, a headline text, a number of steps, a price shown with or without the annual option. If the knob is a value, the test leaves nothing behind. If the knob is “old code or new code”, it leaves one deletion, and you know that up front.
Name the parameter for what it controls, checkout-button-color, not for the test, checkout-test-2. The first name still means something in a year.
How an AI assistant benefits
When an assistant works with ABMeter over MCP, the split is clean. Your code reads parameters, and the assistant changes what the parameters return.
In practice that means the assistant creates the parameter and adds the one resolve_parameter call to your code once. Everything after that happens in ABMeter, not in your repository: the assistant creates variants with new values, sets up the next experiment, and reads its results with you. A new test on the same button is not a pull request.
It also keeps the assistant away from live traffic. The ABMeter MCP server has tools to create and update parameters, variants and experiments, and no tool to start, stop or conclude an experiment. When a test ends, the assistant opens the decision page with its recommendation filled in, and you click Confirm. The new default is your decision, not the assistant’s.
Try it on one value
Pick one value in your app you have an opinion about: a button label, a headline, the number of items on a page. Make it a parameter with today’s value as the default, and read it with resolve_parameter. Nothing changes for your users yet. The next time you want to try a different value, it is an experiment in ABMeter, not a change in your code.