Agent skills.
dif init installs three Claude Code skills into .claude/skills/. They teach
a coding agent the dif workflow: reading the surface log, drafting frontmatter, and writing a decision
that becomes a learning.
The three skills map onto the daily loop: generate surfaces → author an experiment → conclude it. Each is a plain SKILL.md with a trigger description; an agent loads one when your request matches
(say "draft an experiment" and dif-author-experiment picks it up). They're markdown.
Read or edit them like any file in the repo.
.claude/skills/ dif-author-experiment/ # draft + iterate on an experiment dif-conclude-experiment/ # write the decision, archive, learn dif-generate-surfaces/ # propose the surface set from the codebase CLAUDE.md # managed dif block AGENTS.md # managed dif block .cursorrules # managed dif block
All of this is installed by dif init unless you pass --no-agent-files. The skills read from dif/context.json and the surface logs, so they stay current as your experiments change,
with no separate sync step.
dif-author-experiment
Drafting, editing, fixing, or iterating on an experiment .md under dif/experiments/active/. It covers the loop from dif new through dif validate to dif build.
Triggers on: create / draft / edit / fix an experiment, or any mention of an A/B
test, variant, audience, hypothesis, surface, exclusion group, or the commands dif new, dif validate, dif build.
Reads context first
The draft is downstream of three reads, so the skill does them before writing anything:
dif/context.json: what's active, on which surface, and how long it's been running.dif/surfaces/<surface>.md: the## Learningslog, so it builds on prior findings instead of re-testing an answered question.dif/config.yaml: the declaredaudience_attributesit's allowed to write predicates over.
The loop
$dif new <kebab-id> --surface <surface> # fill in hypothesis, ## Brief, ## Rationale, audience, metrics $dif validate # collects every error at once $dif build # also runs validate $git add dif/ && git commit
It fills what dif new leaves as stubs, with opinions:
- hypothesis: one falsifiable sentence naming the change, the expected primary-metric direction, and the audience.
- variants: at least two, weights summing to exactly 100,
controlkept as the first id. - audience: predicates only over attributes declared in
dif/config.yaml. - metrics.primary: exactly one metric this experiment is moving.
- exclusion_group: added when
E007fires on a same-surface overlap.
The skill ships reference files for the details: frontmatter.md (full schema + a worked
example), validation-errors.md (every E/W code with its fix),
and audiences.md (predicate grammar and the resolver contract).
dif-conclude-experiment
Concluding an experiment that has run its course: writing a decision, archiving the file, and running dif conclude. The whole operation is atomic; if any step fails, every file change
rolls back.
Triggers on: conclude / finish / ship / wrap up an experiment, recording a learning
or writing a decision, or the command dif conclude.
The decision is load-bearing
What you write becomes the next line in the surface's ## Learnings log, and the next dif new on that surface embeds it into the draft. So the skill won't conclude until it
can answer four things:
- Is there a real analysis of the primary metric over the run, not a hunch?
- Did the experiment run long enough, on enough of the right traffic, to answer its hypothesis?
- Can the outcome compress to one sentence with a metric direction and magnitude?
- Did the guardrails hold? A primary win that regresses a guardrail isn't a ship.
$dif conclude checkout-cta-v2 --decision "Shipped variant_a. +2.1% checkout conversion, p<0.01 over 14d. Guardrails clean."
That one command, atomically:
- moves
dif/experiments/active/<id>.md→dif/experiments/concluded/<YYYY-MM>-<id>.md; - sets
status: concludedandconcluded: <today>; - fills the
## Decisionblock; - appends one dated line under
## Learningson the surface.
A good decision is one line: did the hypothesis hold, by how much, over what period, and what's the
next move (ship / kill / iterate). After concluding, the skill runs dif build and, if
the decision was to ship, removes the now-dead dif("<id>", ...) call site (it
would otherwise trip W001) and bakes the winning variant into the code.
"Inconclusive." (no next move), "Looked good, shipping it." (no metric or magnitude), "See Notion doc." (the surface log is the source of truth for future drafts; links rot). Either you have an answer or
you're not ready to conclude.dif-generate-surfaces
Producing the initial dif/surfaces/<name>.md set by reading the app's structure
and product context. Most useful right after dif init (which writes only the one
default surface), or when dif new --surface X exits with "surface does not exist."
Triggers on: set up / scaffold / populate / propose surfaces, especially a sparse dif/surfaces/, or a dif new that failed on a missing surface.
A surface is a unit of experimentation agency, not a unit of code: the place where you'd reasonably A/B test something. A typical app has 3-8 surfaces, not one per route or component. The skill:
- surveys existing surfaces and
dif/context.json, scoping to what's missing; - reads product context (
README.md,package.json, the routing files) to learn where experimentation actually happens; - drafts 3-8 coarse-grained candidates, each with a one-line rationale;
- confirms the list with you before writing anything;
- writes each file in the same format
dif initemits, then runsdif validateanddif build.
# Surface: checkout (One-sentence description: where in the app is this surface? Who sees it?) ## Known landmines (Vendor DOM, regulated regions, race conditions — one bullet each. Empty to start.) ## Learnings (One line per concluded test, appended by dif conclude. Empty to start.)
It keeps names kebab-case and singular (checkout, search-results), leaves ## Known landmines and ## Learnings empty (both accrue from real
experience; guesses pollute future drafts), and never auto-flips default_surface in
your config. From here, dif-author-experiment takes over.
Agent guidance files
Alongside the skills, dif init writes a managed dif block into CLAUDE.md, AGENTS.md, and .cursorrules. dif owns only its
marked block; anything else you've written in those files is preserved across re-runs.
The block points the agent at the workflow and at dif/context.json, the session-start
read that lists active experiments, their surfaces and variants, and each surface's most recent
learning. dif build regenerates it, and it lives outside the gitignored dif/generated/ directory so it's checked in and any agent picking up the repo sees the
current state.
dif init --no-agent-files to skip the skills and the
guidance blocks entirely. The CLI, SDK, and build are unaffected. The skills are an accelerant for
the agent-assisted workflow, not a dependency of it.