# skills v0.1

Agent skills.

dif init installs four Claude Code skills into .claude/skills/. They teach a coding agent the dif workflow: reading the surface log, drafting frontmatter, writing a decision that becomes a learning, and acting on dif.sh Cloud verdicts.

Three skills map onto the daily loop: generate surfaces → author an experiment → conclude it. The fourth, triage, reads dif.sh Cloud verdicts for every active experiment and turns each one that needs action into a repo change. 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 them like any file in the repo. dif init --agents-only overwrites an edit to them.

.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
  dif-triage-experiments/    # act on Cloud verdicts: ramp, turn off, conclude
CLAUDE.md                    # managed dif block
AGENTS.md                    # managed dif block
.github/copilot-instructions.md # managed dif block
.cursor/rules/dif.mdc        # dif's Cursor project rule

All of this is installed by dif init unless you pass --agents none (the deprecated --no-agent-files alias still works too). Scaffold a subset with, say, --agents claude. The skills read from dif/context.json and the surface logs, so they stay current as your experiments change, with no separate sync step. The triage skill is new in dif v0.7.0. After you upgrade, run dif init --agents-only to get it.

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 ## Learnings log, so it builds on prior findings instead of re-testing an answered question.
  • dif/config.yaml: the declared audience_attributes it'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, control kept 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 E007 fires 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: getting the result from dif.sh Cloud, writing a decision, archiving the file, and running dif conclude. Edits are computed before any write; a later filesystem failure rolls back on a best-effort basis.

Triggers on: conclude / finish / ship / wrap up an experiment, recording a learning or writing a decision, or the command dif conclude.

Gets the evidence first

The skill reads the numbers before it writes a word. With the dif MCP server connected, it calls get_experiment_results for the experiment id. Without it, it calls the REST endpoint with $DIF_TOKEN. With neither, it asks you to connect one or to give it the numbers. When dif/config.yaml has mode: custom under events:, it asks for the numbers. It never invents them. Setup is in MCP and API.

Concludes on a verdict

The skill concludes only when the Cloud verdict is ship, turn_off or conclude_inconclusive. For any other verdict it stops and shows the reasons. You can override in chat, and the decision then says so, for example "Concluded early by owner request; not significant."

Starts from the draft

For those three verdicts Cloud writes a one-line decision_draft. The skill keeps its numbers and adds context only:

$dif conclude checkout-cta-v2 --decision "Shipped variant_a. +2.1% completed_checkout (95% CI +0.8% to +3.4%), p=0.004 over 14d. Guardrails: refund_rate clean."

What you write becomes the newest line in the surface's ## Learnings log, and the next dif new on that surface embeds it into the draft. The skill reads that log in dif/surfaces/<surface>.md as the source of truth, and calls get_learnings only when you ask about other surfaces.

That one command:

  1. moves dif/experiments/active/<id>.md → dif/experiments/concluded/<YYYY-MM>-<id>.md;
  2. sets status: concluded and concluded: <today> (the command changes only an exact status: active line; for a file with status: draft the skill then sets status: concluded itself);
  3. fills the ## Decision block;
  4. appends one dated line under ## Learnings on the surface.

Agents always pass --decision. Without it, dif conclude opens $EDITOR, and with no terminal it fails with no --decision given and stdin is not a terminal. Add --json to get ok, moved_to, decision_drafted, surface_appended and summary back as JSON.

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 validate and dif build, then removes each dif("<id>", ...) call site that W001 names. It keeps the winning variant's code for a ship, and the control code for a kill or an inconclusive result. Then it commits dif/ and the call-site change. When the triage skill sent it, it does not commit, and on a pull request it also leaves app code alone and lists those call sites in the PR.

// noteBad decisions to avoid: "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:

  1. surveys existing surfaces and dif/context.json, scoping to what's missing;
  2. reads product context (README.md, package.json, the routing files) to learn where experimentation actually happens;
  3. drafts 3-8 coarse-grained candidates, each with a one-line rationale;
  4. confirms the list with you before writing anything;
  5. writes each file in the same format dif init emits, then runs dif validate and dif 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.

dif-triage-experiments Cloud

Reviewing every active experiment at once and acting on the ones that need it. It reads verdicts from dif.sh Cloud, so it needs a read token: through the dif MCP server, which you set up with one, or in DIF_TOKEN for REST. See MCP and API. For REST, the skill reads the base URL from DIF_CLOUD_URL when it is set, and uses https://cloud.dif.sh otherwise.

Triggers on: triage, review all experiments, what needs action, ramp, turn off, kill, winners, or a dif.sh Cloud overview.

The workflow

  1. calls get_overview for the repo;
  2. shows a table: experiment id, verdict, top reason, next action;
  3. acts on each row's next_action.kind, not on the verdict name.
next_action.kindwhat the skill does
set_weightsChecks that the weights in next_action.file still equal the weights Cloud analysed. Then it changes only the weight: numbers to next_action.weights and runs dif validate and dif build.
concludeHands off to dif-conclude-experiment with the decision_draft, unchanged.
fix_instrumentationReports the failed check. Never changes files.
set_metric_directionReports the metric to set in Cloud, on the Metrics page. Never changes files.
waitReports the summary: what is missing, or the ETA in days when Cloud has one. Never changes files.

Broken data comes back as investigate with fix_instrumentation, so the skill never acts on it. A harmful variant takes two runs: the first sets control to 100 and every other variant to 0, and a later run concludes the experiment. The skill builds its git and gh commands only from the checked experiment id, the verdict and fixed text. The one other text that reaches a shell is the decision: the conclude skill passes it to dif conclude in single quotes, and stops if it holds a single quote, $(, a backtick or a line break.

Interactive and autonomous

Interactive is the default: the skill shows each change and asks before it writes. It works on your current branch and does not commit, push or open a pull request unless you ask. If you ask for pull requests, it first needs a clean working tree: git status --porcelain --untracked-files=all must print nothing. Otherwise it changes nothing. Then it follows the branch, commit and pull request rules below, and puts you back on the branch you started on.

Autonomous mode is for unattended runs only: you tell it to run unattended, or no person can answer, such as a scheduled job. It follows fixed rules:

  • It needs a clean working tree before the first change. Otherwise it stops.
  • It runs git fetch origin and cuts every branch from origin/<default>. It never pulls into, or builds on, your local default branch. At the end it goes back to the branch it started on. After a run that made a branch, run dif build on your branch.
  • Just before acting, it calls get_experiment_results for that experiment. It acts only if the verdict is the same as in the overview and analysis_computed_at is under 30 minutes old. It reads the clock with node; without node it skips the row as stale.
  • One branch and one pull request per experiment and step: dif/<verdict>/<id>/w<n> for a weight change, where <n> is the new treatment weight, and dif/<verdict>/<id>/conclude for a conclusion. For example dif/ramp/new-checkout/w25, or dif/turn_off/new-checkout/w0.
  • If that branch already exists, it skips the row. With no open pull request the branch is stale: delete it to let the step run again.
  • The commit is chore(dif): <verdict> <id>. It stages only the files the skill changed. The PR title is dif: <verdict> <id>, with urgent: in front for turn_off.
  • If git add or git commit fails, it stops and pushes nothing. If a push fails, it keeps the local branch and reports it. If the pull request cannot be opened, it reports the pushed branch.
  • The PR body carries the verdict, reasons, caveats, analysis_computed_at and the exact change. A ship PR says not to merge it before the call sites keep the winning code.
  • At most 5 pushed branches per run.
  • It never merges, never force-pushes and never pushes to the default branch. A person merges.

Agent guidance files

Alongside the skills, dif init writes a managed dif block into CLAUDE.md, AGENTS.md, and .github/copilot-instructions.md. dif owns only its marked block; anything else you've written in those files is preserved across re-runs. For Cursor it writes its own project rule, .cursor/rules/dif.mdc, which Cursor loads when a task is about flags or experiments or a file under dif/ is open. A dif block left in .cursorrules by an older dif is refreshed; a new .cursorrules is never created.

To install the skills into an agent other than Claude Code, or before running dif init, use the skills CLI: npx skills add dif-sh/dif. It also installs dif-docs, a reference skill that points at these docs.

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.

// notePass dif init --agents none (or the deprecated --no-agent-files alias) 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.