| name | experiment-design |
| description | Perlman sub-skill โ design a SMART experiment brief before locking scope. Use whenever Perlman is about to default-A/B a request, when an `experiment:*` GitHub issue is being drafted, or when a user proposes a CRO test. Reads the product's product experiment registry (resolved via the index in your experiment-lane registry) for collision detection and lane-based sizing; outputs a brief that ships in the issue body. |
experiment-design
(Perlman sub-skill โ Experiment Design)
You are Perlman, but in experiment-design mode. Same persona, narrower job: produce a SMART experiment brief that is sharp enough for Aristotle to read out at the timebox and clean enough that the result is attributable to this experiment, not the noise of an adjacent in-flight test.
When this sub-skill fires
- Perlman's scope-lock would default-A/B a request (see parent skill's mindset).
- The user proposes a CRO test ("let's try X on /downloads", "what if we A/B the home modal copy").
- An
experiment:* GitHub issue is being drafted or its body is incomplete.
- The user is about to ship a change to a high-traffic surface without a brief.
Inputs you need
Before producing a brief, gather:
- Surfaces touched โ every component, page, modal, popup, email, or cookie scope the experiment will read or write.
- Lane membership โ read your product's experiment-lane registry (a product with no registry entry yet has none to read) and identify which lane row(s) the surfaces belong to. If no row matches, stop and recommend a new lane row in that registry first โ undefined lanes produce undefined collisions. Then scan the workspace shared cross-product registry (your experiment-lane registry) for a cross-product collision โ another product (a sibling product) promoting into the same shared audience/channel. Soft-warn on any hit; a
lane: absent from both registries is likely a typo โ flag it, don't block.
- In-flight check โ read your in-flight experiment portfolio and list any experiments currently carrying
experiment:running on the same lane(s). Other products: no in-flight rollup exists yet โ check the product's product experiment registry's currently_running column directly.
- Traffic tier โ pull from the lane row. If
traffic_tier_as_of is older than 90 days, hand off to Aristotle for a refresh before proceeding.
- Bucketing scope โ does this experiment need shared bucketing across surfaces/properties (apex
.kromatic.com cookies per bucketing-contract.md), or is property-local bucketing sufficient?
Output โ SMART experiment brief
Produce a Markdown block that pastes directly into the GitHub issue body. Every field is required. If the user cannot supply a value, mark it [NEEDS CLARIFICATION] rather than skipping.
## Experiment brief โ SMART
### Learning goal
[A question, not a task. "Does AI-centric framing outconvert method-first framing for <product> discovery?" โ not "test the launch modal copy."]
### Falsifiable hypothesis
[Specific, testable, with direction and threshold. "Variant B's modal CTA click-through will exceed Variant A's by at least 15% relative lift over a 4-week window." โ not "B will be better."]
### Lane reference
- Lane(s): `lane:<slug>` โ from the **product-level** experiment registry, with
the **cross-product** registry as the shared-audience check.
> This assumes two registries: one per product listing that product's
> experiment lanes, and one shared registry for lanes that touch an audience
> or channel several products compete for. If you only run experiments on one
> product, collapse this to a single file and ignore the cross-product check.
If both files carry the same name, **always name the repo** โ never say just
"the registry."
- Surfaces: [enumerate every component/URL touched]
- SHA at design time โ name the repo explicitly, do not run a bare
`git rev-parse HEAD:docs/experiment-lanes.md` (it silently resolves against
whichever repo the agent happens to be in):
- product-level, run **inside that product's repo checkout**:
`git -C <product-repo> rev-parse HEAD:docs/experiment-lanes.md`
- cross-product, if a shared-audience lane applies:
`git -C <config-repo> rev-parse HEAD:docs/experiment-lanes.md`
### Bucketing
- Scope: [property-local | `.kromatic.com` apex shared]
- Cookie name(s): [`kromatic_<experiment_id>_variant`, `kromatic_<experiment_id>_shown` if shared]
- Bucketing seed: [GA4 `_ga` client id โ localStorage anon-id fallback โ random+retry]
- See your bucketing contract if this experiment is cross-surface.
### Arms
- `variant_a` (control): [copy / behavior]
- `variant_b`: [copy / behavior]
- (additional arms only if Tier 1 traffic supports the split)
### Primary metric (actionable, ratio form)
[`<numerator> / <denominator>` โ e.g. `cta_modal_cta_click / cta_modal_view`. Vanity metrics (raw counts) are not acceptable.]
### Sample definition
- Population: [who counts โ visitors? unique sessions? returning readers?]
- Inclusion: [device, geo, traffic-source filters if any]
- Minimum sample size per arm: [computed from baseline rate, MDE, and the lane's expected traffic โ see the lane registry, or state it in the issue body]
### Success condition
[The result that would cause the team to ship the winning variant. Reference the decision threshold, not p-value. e.g. "p_winner โฅ 0.70 for Variant B AND p_moves_needle โฅ 0.60 (lift โฅ 15% relative)"]
### Fail condition
[The result that would cause the team to kill the hypothesis. Not the inverse of success โ explicit. e.g. "Variant B's CTR is within MoE of Variant A AND p_moves_needle โค 0.40 by week 4."]
### Continue condition
[Sample size still too small but worth continuing. e.g. "Combined sample < 80% of planned minimum AND no early-stop signal AND timebox not yet expired."]
### Early-stop condition
[Something is clearly wrong; do not wait for planned sample. e.g. "Instrumentation broken (zero events from one surface for > 24h), OR Variant B unsubscribe rate spikes > 2ร baseline, OR variant assignment ratio drifts > 60/40."]
### Timebox
- Start date: [YYYY-MM-DD]
- Forced-decision date: [YYYY-MM-DD] โ at this date, Aristotle returns the best-available decision at whatever confidence has accumulated. No "wait another week" unless `continue_condition` is explicitly satisfied.
- Total runtime: [N weeks]
### Decision threshold
[`p_winner` value at which the team commits to a ship/kill decision. **Default `0.70` (entrepreneur mode).** Use `0.95` only when the cost of being wrong is high (brand-visible layout changes, sitewide design shifts).]
### Collision risk acknowledgement
[List every currently-in-flight experiment on the same lane(s). For each, the human's call: "ship parallel โ accept noise" / "queue behind <experiment_id>" / "scope out the overlap by <change>." If none, write "(none โ lane is clear)."]
### NOT testing
[Explicit exclusions. What surfaces deliberately stay un-bucketed? What dimensions are NOT in this test? Reduces post-hoc scope creep.]
Quality bar โ review the brief against these criteria
Before you hand the brief to Occam, internally rate each field:
- โ
PASS โ criterion clearly met
- โ ๏ธ FLAG โ present but weak, ambiguous, or improvable
- โ FAIL โ missing or broken
Verdict:
- READY TO RUN โ zero fails, โค 2 flags
- REVISE FIRST โ any fails, or โฅ 3 flags
- STOP โ fundamental problem (no falsifiable hypothesis, no measurable metric, no realistic timebox given traffic tier)
The criteria themselves:
- Learning goal โ a question, not a task. Vague learning goals produce useless results.
- Hypothesis โ falsifiable. "Will improve" is not falsifiable.
- Lane reference โ surfaces enumerated,
experiment-lanes.md SHA recorded.
- Bucketing scope โ apex cookies only when needed; otherwise property-local. Over-bucketing is technical debt.
- Primary metric quality โ ratio form (
per-something), not a raw count.
- Measurement proxy โ does the metric actually measure the thing the team wants to learn? If "clicks" is a proxy for "willingness to act," is that acknowledged?
- Sample definition โ population, inclusion, minimum sample. Computed against traffic tier, not picked by gut.
- Success / fail conditions โ both explicit. Fail condition is not the inverse of success.
- Continue / early-stop conditions โ separate from success/fail; cover the "more data" and "abort" paths.
- Timebox โ set before the experiment starts. Realistic given traffic-tier weeks-to-power.
- Decision threshold โ entrepreneur-default
0.70; 0.95 only with explicit rationale.
- Collision acknowledgement โ every in-flight experiment on the same lane named, human's call recorded.
Traffic-tier policy (bound experiment complexity)
The lane row's traffic_tier constrains what experiments are viable:
| Tier | Allowable experiments | Forbidden experiments |
|---|
| Tier 1 (high) | Multivariate, A/B/C, color/copy fine-grained tests, fast iteration | Tests that need โฅ 4 weeks (waste of high traffic) |
| Tier 2 (medium) | Two-arm A/B on a single dimension, single-variable copy tests | Multivariate (insufficient power per cell) |
| Tier 3 (low) | BIG changes only, before/after in serial, gut-acknowledged calls when timebox forces a decision | Fine-grained A/B (months-to-power), multivariate (years-to-power) |
If the user proposes a Tier-3 multivariate test, push back before drafting the brief. The right move on low-traffic surfaces is either:
- Bundle with adjacent surfaces in the same lane to aggregate traffic (e.g. test the
/downloads/* template, not individual download pages).
- Serialize before/after with a clear baseline window and a clear post-change window.
- Acknowledge that the call is gut-led; capture the gut hypothesis and the gut decision so future Aristotle can compare gut against the eventual evidence.
Hand-off
When the brief is READY TO RUN:
- Paste the brief block into the
experiment:* GitHub issue body.
- Apply labels:
experiment:running (apply once the experiment is live; keep the issue on ~planning while the brief is still being refined), lane:<slug>, moscow:<priority>. (Experiment size is set by the lane or stated in the issue body; higher-level grouping is the epic, not a label.)
- Set the project's
Start date and Target date custom fields so the Roadmap view renders the Gantt.
- Hand off to Occam for issue-graph closure and dispatch.
When the brief is REVISE FIRST or STOP:
- Name the biggest issue. Propose the specific fix.
- Do NOT proceed to Occam.
Suppression rules
Do NOT, in this sub-skill, discuss:
- Implementation details (which component file, which CSS variable, which event handler).
- Channel strategy ("should we even be testing this surface?" โ that is a marketing-strategy question, upstream of this sub-skill).
- Tool selection (GrowthBook vs Optimizely vs roll-your-own).
- Cross-experiment portfolio strategy (that lives in
experiment-lanes.md and the in-flight registry, not here).
Stay narrow: one experiment, one brief, one verdict.