Best for
- Use when shaping product intent, writing a hypothesis, defining feature flags and rollout strategy, designing metrics and instrumentation, creating or analyzing experiment runs, deciding continue/pause/rollback/inconclu…
featbit/featbit-skills/skills/featbit-experimentation/SKILL.md
End-to-end FeatBit experimentation and release-decision workflow. Use when shaping product intent, writing a hypothesis, defining feature flags and rollout strategy, designing metrics and instrumentation, creating or analyzing experiment runs, deciding continue/pause/rollback/inconclusive, or capturing learnings for the next iteration.
Decision brief
This skill is the single entry point for the FeatBit release-decision loop:
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/featbit/featbit-skills --skill "skills/featbit-experimentation"Inspect the Agent Skill "featbit-experimentation" from https://github.com/featbit/featbit-skills/blob/4b542eb3c87e93d61ea8df4ea2f6ad6c6e395f3d/skills/featbit-experimentation/SKILL.md at commit 4b542eb3c87e93d61ea8df4ea2f6ad6c6e395f3d. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
When the user comes from the FeatBit Release Decision UI, do not require them to copy a long stage prompt. A short invocation is enough:
Infer the stage from state plus the user's message. Multiple signals can apply; choose the earliest stage that blocks a sound decision.
Review the “Execution Procedure” section in the pinned source before continuing.
Do not let an available tool define the user's problem before the decision type is clear.
The FeatBit API database is the canonical source for experiment state. Read it on entry and write it before moving stages.
Permission review
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 95/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 11 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
This skill is the single entry point for the FeatBit release-decision loop:
intent -> hypothesis -> implementation -> exposure -> measurement -> interpretation -> decision -> learning -> next intent
It replaces the old multi-skill layout (featbit-release-decision, intent-shaping, hypothesis-design, reversible-exposure-control, measurement-design, experiment-workspace, evidence-analysis, learning-capture, and project-sync). Do not call those split skills from this workflow. Treat their former behavior as internal stages of this skill.
The FeatBit API database is the canonical source for experiment state. Read it on entry and write it before moving stages.
Required MCP tools:
| Tool | Purpose |
|---|---|
featbit_experiment_get_experiment | Read experiment state, runs, messages, setup mode, and pasted input data |
featbit_experiment_update_experiment | Write goal, intent, hypothesis, constraints, lastAction, lastLearning |
featbit_experiment_set_stage | Set lifecycle stage |
featbit_experiment_update_metrics | Write primary metric and guardrails |
featbit_experiment_create_run | Create an experiment run |
featbit_experiment_update_run | Update run setup, status, decision, and learning fields |
featbit_experiment_update_run_traffic | Configure a run's experiment traffic assignment: analysis method, control/treatment roles, layer id/key, bucket slice, assignment unit, audience filters, allocation plan, and per-variation analysis sampling |
featbit_experiment_analyze_run | Run server-side analysis and persist inputData / analysisResult |
featbit_experiment_list_layers | List registered release-decision layers for the experiment environment |
featbit_experiment_create_layer | Create a registered layer after explicit user approval |
featbit_experiment_update_layer | Update a registered layer after explicit user approval |
featbit_experiment_archive_layer | Archive a registered layer after explicit user approval |
featbit_experiment_get_feature_flag | Read the real FeatBit flag, revision, variations, and targeting for the experiment environment |
featbit_experiment_create_feature_flag | Create a FeatBit-managed feature flag after the exposure contract is complete and the user explicitly approves |
featbit_experiment_update_feature_flag_targeting | Update flag targeting/rollout directly, or create a change request when useChangeRequest or reviewers are provided, after explicit user approval |
featbit_experiment_toggle_feature_flag | Enable or disable the FeatBit flag after targeting is configured, or during pause/rollback execution, after explicit user approval |
The MCP client configuration must provide normal FeatBit auth headers: Authorization, Organization, and Workspace. Do not ask for or pass a per-experiment access token.
For the canonical tool inventory and step-by-step usage rules, read references/mcp-tool-usage.md before executing MCP-backed experiment work.
Feature flag mutation safety:
featbit_experiment_create_feature_flag, featbit_experiment_update_feature_flag_targeting, or featbit_experiment_toggle_feature_flag, summarize the exact flag key, environment implied by the experiment, operation, rollout/toggle state, and rollback consequence, then ask the user for explicit approval.confirmedByUser: true in the MCP request only after the user clearly approves that exact operation. Do not infer approval from earlier setup discussion, a stage transition, or an analysis recommendation.Run traffic configuration safety:
featbit_experiment_update_run_traffic changes how experiment evidence is read; it does not mutate the live feature flag or who sees a variation.featbit_experiment_list_layers before assigning a run to a layer. If the layer does not exist, create it only after the user explicitly approves.sliceStart and sliceEnd for layer bucket ranges such as 30 to 60; layerTrafficPercent is the slice width. Avoid legacy trafficPercent / trafficOffset unless preserving older run behavior.collecting, analyzing, or decided, summarize the exact run, control/treatment roles, layer eligibility, and sampling rates, then ask the user to approve before passing confirmedByUser: true.analysisSamplingPlan include rates from the actual exposure distribution in the run window: includeRate = desired analyzed users for that variation / observed served users for that variation * 100, capped at 100. If the live flag rollout changed after data was collected, start a new run window or collect fresh data instead of reusing the old distribution.Layer mutation safety:
confirmedByUser: true only after approval for that exact layer operation.Valid values:
| Field | Values |
|---|---|
stage | intent, hypothesis, implementing, measuring, learning |
run.status | draft, collecting, analyzing, decided, archived |
method | bayesian_ab, frequentist, bandit |
decision | CONTINUE, PAUSE, ROLLBACK, INCONCLUSIVE |
metricType | binary, continuous |
metricAgg | once, count, sum, average |
primary expectedDirection | increase_good, decrease_good |
guardrail direction | increase_bad, decrease_bad |
At minimum, pass the experiment id:
/featbit-experimentation <experiment-id>
The web UI may also pass a stage hint and selected run:
/featbit-experimentation <experiment-id> --stage intent-hypothesis
/featbit-experimentation <experiment-id> --stage exposure
/featbit-experimentation <experiment-id> --stage measuring
/featbit-experimentation <experiment-id> --stage decision --run-id <run-id>
/featbit-experimentation <experiment-id> --stage learning
Parse experiment-id, optional stage, optional run-id, and optional unresolved UI item labels from the invocation. If experiment-id is missing, ask for it before proceeding.
Before asking or saying anything, call:
state = MCP("featbit_experiment_get_experiment", experimentId=experiment_id)
If the MCP call fails because the server is unreachable, retry once. If the retry still fails, treat this as a blank new project and proceed without diagnosing the database to the user. If the user later asserts that the database is reachable, call the read tool again before saying otherwise.
After the MCP read resolves featbitProjectKey, use product-context MCP capabilities if available. If none are configured, skip product facts silently. Never hardcode local product URLs.
When the user comes from the FeatBit Release Decision UI, do not require them to copy a long stage prompt. A short invocation is enough:
Use featbit-experimentation for experiment <experiment-id> at stage <stage>.
If a UI stage is explicit, read references/ui-stage-prompts.md and apply that stage protocol. Treat unresolved UI item labels as private routing hints only; do not recite them to the user.
Supported UI stages:
| UI stage | Internal protocol |
|---|---|
intent-hypothesis | CF-01 + CF-02 |
exposure | CF-03 + CF-04 |
measuring | CF-05 + CF-06 setup/run management |
decision | CF-06 + CF-07 decision for a selected run |
learning | CF-06 + CF-07 + CF-08 |
If the project is blank, ask only:
What are you trying to improve or learn?
Do not enumerate empty fields, announce state loading, or explain the stage.
If messages is non-empty, emit no greeting or recap. The user can already see the conversation history. Wait for their next prompt.
If meaningful state exists and messages is empty, summarize only the non-empty fields needed for the next decision in at most two short sentences.
If entryMode == "expert", the user already filled the setup wizard. Do not run blank intent, hypothesis, or measurement shaping unless explicitly asked. Acknowledge the configured method, metrics, priors, variants, window, and any experimentRuns[*].inputData. If inputData exists, route directly to analysis.
Infer the stage from state plus the user's message. Multiple signals can apply; choose the earliest stage that blocks a sound decision.
| Lens | Internal stage | Activate when |
|---|---|---|
| CF-01 | shape_intent | goal is empty/vague, user leads with a tactic, or says "improve", "increase adoption", "make it better" |
| CF-02 | design_hypothesis | Goal exists but no falsifiable causal claim ties change, metric, audience, and reason |
| CF-03 / CF-04 | control_exposure | Change needs a feature flag, rollout, targeting, protected audience, or traffic strategy |
| CF-05 | design_measurement | Hypothesis exists but primary metric, guardrails, or event instrumentation are incomplete |
| CF-05 / CF-06 | manage_experiment | Instrumentation is ready and the user wants to start, run, refresh, close, bandit, or holdout an experiment |
| CF-06 / CF-07 | analyze_evidence | Data exists and the user asks whether to continue, pause, rollback, or decide |
| CF-08 | capture_learning | A decision exists or the user wants to close the cycle and define the next iteration |
def on_session_start(argv, user_message):
experiment_id = parse_experiment_id(argv)
ui_stage = parse_stage_hint(argv, user_message)
run_id = parse_run_id(argv, user_message)
assert experiment_id, "experiment-id is required"
state = read_state_with_one_retry(experiment_id)
if ui_stage:
stage_protocol = read("references/ui-stage-prompts.md")
return route_ui_stage(ui_stage, experiment_id, run_id, state, stage_protocol)
if state.status == "unavailable" or is_blank_project(state):
ask_user("What are you trying to improve or learn?")
return
if state.featbitProjectKey and MCP.has_tool("featbit_product_context_get_facts"):
_product_facts = MCP("featbit_product_context_get_facts", projectKey=state.featbitProjectKey)
if state.messages:
return # no visible recap
if state.entryMode == "expert":
acknowledge_expert_setup(state)
if any(run.inputData for run in state.experimentRuns):
manage_experiment(experiment_id, "run analysis")
return
say(summarize_nonempty(state, max_sentences=2))
ask_user("What would you like to work on next?")
def on_user_turn(experiment_id, user_message):
state = MCP("featbit_experiment_get_experiment", experimentId=experiment_id)
lens = infer_cf_lens(state, user_message)
if lens == "CF-01": return shape_intent(experiment_id, user_message, state)
if lens == "CF-02": return design_hypothesis(experiment_id, user_message, state)
if lens in ("CF-03", "CF-04"): return control_exposure(experiment_id, user_message, state)
if lens == "CF-05": return design_measurement(experiment_id, user_message, state)
if lens in ("CF-05-RUN", "CF-06-RUN"): return manage_experiment(experiment_id, user_message, state)
if lens in ("CF-06", "CF-07"): return analyze_evidence(experiment_id, user_message, state)
if lens == "CF-08": return capture_learning(experiment_id, user_message, state)
answer_directly(user_message, state)
Use when the user has a desire or tactic but no measurable business outcome.
Read references/intent-goal-extraction-patterns.md.
Actions:
Persist:
MCP("featbit_experiment_update_experiment", experimentId=experiment_id, update={
"goal": goal,
"intent": original_user_framing,
"lastAction": "Intent clarified",
})
MCP("featbit_experiment_set_stage", experimentId=experiment_id, stage="intent")
When the user asks what next: tell them to mark CF-01 satisfied in the UI, then continue with CF-02 in the same Intent & Hypothesis stage.
Use when a goal exists but the causal claim is missing or vague.
Read references/hypothesis-template.md.
Template:
We believe [change X] will [move metric Y in direction Z] for [audience A], because [causal reason R].
Actions:
primaryMetric here. A complete primary metric belongs to CF-05.Persist:
MCP("featbit_experiment_update_experiment", experimentId=experiment_id, update={
"hypothesis": hypothesis,
"change": change,
"variants": variants,
"lastAction": "Hypothesis formed",
})
MCP("featbit_experiment_set_stage", experimentId=experiment_id, stage="hypothesis")
When the user asks what next: mark Intent & Hypothesis satisfied, move to Exposure, then run CF-03/04.
Use when a change needs reversibility, flag contract, targeting, rollout, rollback, or traffic allocation rules.
Read:
Default output is a concrete feature flag and exposure contract: flag name, stable key, type, variants, evaluation point, variant behavior, target audience, protected users, initial rollout, expansion checkpoints, stop conditions, rollback triggers, and metric-event requirements.
Rules:
constraints may propose a flag key, but the flag is not bound until featbit_experiment_get_feature_flag succeeds or featbit_experiment_create_feature_flag succeeds and is read back.featbit_experiment_get_feature_flag; if the tool returns ResourceNotFound, create the missing flag with featbit_experiment_create_feature_flag; read the created flag back; configure rollout/targeting with featbit_experiment_update_feature_flag_targeting; then call featbit_experiment_toggle_feature_flag only when exposure should begin now or a run is moving to collecting.featbit_experiment_toggle_feature_flag; do not send the user to the FeatBit UI only to switch the flag on or off.confirmedByUser: true only for the approved call.get_feature_flag still returns ResourceNotFound.Persist:
if MCP.has_tool("featbit_experiment_get_feature_flag"):
flag = MCP("featbit_experiment_get_feature_flag", experimentId=experiment_id, key=flag_key)
if flag.error == "ResourceNotFound":
require_explicit_user_approval("create feature flag", flag_contract)
flag_contract["confirmedByUser"] = True
MCP("featbit_experiment_create_feature_flag", experimentId=experiment_id, request=flag_contract)
flag = MCP("featbit_experiment_get_feature_flag", experimentId=experiment_id, key=flag_key)
assert not flag.error, "feature flag must exist before Exposure can be satisfied"
require_explicit_user_approval("update feature flag targeting", targeting)
MCP("featbit_experiment_update_feature_flag_targeting", experimentId=experiment_id, key=flag.key, request={
"confirmedByUser": True,
"revision": flag.revision,
"targeting": targeting,
"comment": "Initial experiment rollout",
# Optional only when approval is required:
# "useChangeRequest": True,
# "reviewers": reviewer_ids,
# "reason": "Initial experiment rollout"
})
if exposure_should_start_now and MCP.has_tool("featbit_experiment_toggle_feature_flag"):
require_explicit_user_approval("enable feature flag", {"key": flag.key, "isEnabled": True})
MCP("featbit_experiment_toggle_feature_flag", experimentId=experiment_id, key=flag.key, request={
"confirmedByUser": True,
"isEnabled": True,
"comment": "Enable flag for initial experiment exposure",
})
flag = MCP("featbit_experiment_get_feature_flag", experimentId=experiment_id, key=flag.key)
assert flag.isEnabled, "feature flag must be enabled before collecting exposure data"
MCP("featbit_experiment_update_experiment", experimentId=experiment_id, update={
"constraints": flag_contract_and_rollout_with_actual_flag_key_and_variations,
"lastAction": "Exposure contract defined",
})
MCP("featbit_experiment_set_stage", experimentId=experiment_id, stage="implementing")
When the user asks what next: mark Exposure satisfied, create/configure the flag through FeatBit MCP when available, otherwise use the FeatBit UI using the contract, then continue to Measuring / CF-05.
Use when the hypothesis exists but metrics, guardrails, or instrumentation are incomplete.
Read:
Rules:
metricName, metricEvent, metricType, metricAgg, and expectedDirection are required.expectedDirection is increase_good or decrease_good.direction is increase_bad or decrease_bad.Persist metrics through featbit_experiment_update_metrics, not update_experiment:
MCP("featbit_experiment_update_metrics", experimentId=experiment_id, update={
"metricName": primary_metric.name,
"metricEvent": primary_metric.event,
"metricType": primary_metric.metric_type,
"metricAgg": primary_metric.metric_agg,
"expectedDirection": primary_metric.expected_direction,
"metricDescription": primary_metric.rationale,
"guardrails": guardrails_json,
})
MCP("featbit_experiment_update_experiment", experimentId=experiment_id, update={
"lastAction": "Metrics defined",
})
MCP("featbit_experiment_set_stage", experimentId=experiment_id, stage="measuring")
When the user asks what next: mark Measuring satisfied only after event contract and instrumentation are ready, then continue to run setup or analysis.
Use when instrumentation is ready and the user wants to create, collect, analyze, refresh, close, run a bandit, or plan holdout.
Read:
Rules:
controlVariant and treatmentVariant; do not rely on manual text when the API can provide the source of truth.featbit_experiment_update_run_traffic, not the generic run update tool. The feature flag's actual served variation is the source of truth; layer eligibility only decides whether an exposure can enter this run, and analysis sampling is applied inside each served variation.isEnabled is false and featbit_experiment_toggle_feature_flag is available, enable it, read the flag back, and set observationStart no earlier than the enable time. If the toggle tool is missing, stop and ask the user to register the latest MCP tools instead of pretending collection has started.confirmedByUser: true unless the user has approved that toggle in this turn or an immediately preceding approval response.featbit_experiment_analyze_run; do not inline-compute and write analysisResult.inputData after analysis: k <= n, variants match, no zero n.draft, collecting, analyzing, decided, archived.Start a run:
state = MCP("featbit_experiment_create_run", experimentId=experiment_id)
run_id = newest_run_id(state)
flag = MCP("featbit_experiment_get_feature_flag", experimentId=experiment_id, key=flag_key)
if not flag.isEnabled:
require_explicit_user_approval("enable feature flag before collection", {"key": flag_key, "isEnabled": True})
MCP("featbit_experiment_toggle_feature_flag", experimentId=experiment_id, key=flag_key, request={
"confirmedByUser": True,
"isEnabled": True,
"comment": "Enable flag before starting experiment collection",
})
flag = MCP("featbit_experiment_get_feature_flag", experimentId=experiment_id, key=flag_key)
assert flag.isEnabled, "feature flag must be enabled before the run can collect data"
MCP("featbit_experiment_update_run_traffic", experimentId=experiment_id, runId=run_id, request={
"method": "bayesian_ab",
"controlVariant": control,
"treatmentVariant": treatment,
"assignmentUnitSelector": "user.keyId",
"layerKey": layer_key_or_null,
"layerTrafficPercent": layer_traffic_percent_or_100,
"analysisSamplingPlan": json.dumps([
{"variation": control, "role": "control", "includeRate": control_include_rate},
{"variation": treatment, "role": "treatment", "includeRate": treatment_include_rate},
]),
"audienceFilters": audience_filters_json_or_null,
})
MCP("featbit_experiment_update_run", experimentId=experiment_id, runId=run_id, update={
"slug": slug,
"status": "collecting",
"hypothesis": hypothesis,
"primaryMetricEvent": primary_metric_event,
"primaryMetricType": primary_metric_type,
"primaryMetricAgg": primary_metric_agg,
"guardrailEvents": guardrail_csv,
"minimumSample": minimum_sample,
"priorProper": prior_proper,
"priorMean": prior_mean,
"priorStddev": prior_stddev,
"observationStart": observation_start,
})
MCP("featbit_experiment_update_experiment", experimentId=experiment_id, update={
"lastAction": f"Created experiment {slug}",
})
MCP("featbit_experiment_set_stage", experimentId=experiment_id, stage="measuring")
Run or refresh analysis:
MCP("featbit_experiment_analyze_run", experimentId=experiment_id, runId=run_id, forceFresh=True)
Expert mode with inputData uses the stored pasted data automatically when live stats are not available. Do not ask the user to paste data again.
Use when data exists and a decision is being considered.
Read:
Sufficiency checks before deciding:
minimumSample.risk[trt] and risk[ctrl] both high).Decision categories:
CONTINUE: primary P(win) >= 95%, treatment risk is low, guardrails acceptable.PAUSE: primary P(win) is 80-95%, possible guardrail harm, SRM/instrumentation concern, or named risk needs investigation.ROLLBACK: guardrail crosses strong harm threshold or primary P(win) <= 5%.INCONCLUSIVE: sample is below floor, risk has not converged, or primary remains uncertain after the window.Respect analyzer output:
verdict, p_harm, or inverse-handled outputs silently.featbit_experiment_update_feature_flag_targeting to expand, hold, or rollback the rollout. Use featbit_experiment_toggle_feature_flag to enable a launch/expansion when the flag is off, or to disable exposure for an immediate pause/rollback. Use change-request mode only when reviewer ids are supplied or approval is required.Persist:
MCP("featbit_experiment_update_experiment", experimentId=experiment_id, update={
"lastAction": f"Decision: {category}",
})
MCP("featbit_experiment_update_run", experimentId=experiment_id, runId=run_id, update={
"status": "decided",
"decision": category,
"decisionSummary": summary,
"decisionReason": reason,
})
Stage stays measuring; it advances to learning only after CF-08.
Use when a decision exists or the cycle is ending.
Read references/learning-iteration-synthesis-template.md.
A complete learning contains:
Rules:
completed, finished, or any invented value.Persist:
MCP("featbit_experiment_update_experiment", experimentId=experiment_id, update={
"lastLearning": learning.summary,
"lastAction": "Learning captured",
})
MCP("featbit_experiment_set_stage", experimentId=experiment_id, stage="learning")
MCP("featbit_experiment_update_run", experimentId=experiment_id, runId=run_id, update={
"status": "archived",
"whatChanged": learning.what_changed,
"whatHappened": learning.what_happened,
"confirmedOrRefuted": learning.confirmed_or_refuted,
"whyItHappened": learning.why,
"nextHypothesis": learning.next_hypothesis,
})
Then route the next cycle back to CF-01 / CF-02 inside this same skill.
The old project-sync -> sync.ts -> web API -> database bridge is retired. New flows use:
featbit-experimentation -> configured FeatBit experimentation MCP server -> FeatBit API -> database
Do not call sync.ts, do not set SYNC_API_URL, and do not require a per-experiment access token in the slash command.
Frequently asked questions
This skill is the single entry point for the FeatBit release-decision loop:
The source record exposes this install command: npx skills add https://github.com/featbit/featbit-skills --skill "skills/featbit-experimentation". Inspect the command and pinned source before running it.
Alternatives
drafthq/draft
Decompose project or track into modules with dependency mapping. Project scope updates architecture.md and derives .ai-context.md. Track scope generates hld.md (always) and lld.md (when --lld or High-complexity module triggers it) — design-mandated artifacts that drive implement, deploy-checklist, and upload sign-off.
microsoft/Sico
Execute Android UI workflows on a sandbox device, review results, and produce a structured execution report.
wanshuiyin/Auto-claude-code-research-in-sleep
Use it for deployment and design tasks; the detail page covers purpose, installation, and practical steps.
Forward-Future/loopy
Discover, find, compare, audit, repair, adapt, craft, run, debrief, save, and prepare repeatable AI-agent loops for publication. Use when a user asks to analyze code or coding threads for recurring work, find a published loop, interview them to turn a goal into a bounded loop, review a loop for weak checks or unsafe authority, execute a loop with an evidence receipt, learn from completed runs, save or reuse a project loop, or validate and submit a loop to Loop Library.