Flags and experiments belong in the repo.
One .md file per feature flag or A/B test, in the repo with the code. Holdouts and staged rollouts are the same format, versioned in git and reviewable in a PR. Your coding agent reads dif/context.json on session start, so prior learning travels with the work.
More than a place to keep flags.
Segmentation, conflict checks, analytics, and cleanup are part of the file format and the CLI, not a separate service you log into.
Your agent reads the experiments.
Every dif build regenerates dif/context.json: active flags and experiments, variants, recent learnings. Coding agents pick it up on session start, like a CLAUDE.md for experiments.
Target on attributes you declare.
Declare audience attributes once, then include or exclude on country, plan, or returning_visitor. Values arrive at runtime; no customer list is ever committed.
Send results where you already look.
Add a Dif Cloud key and dif.track() computes lift for you, no join code. Or run dif init --events custom and own the two handlers that forward events to Segment, Amplitude, or your warehouse.
Clashes fail the build.
The build resolves the exclusion graph and refuses to compile when two live tests would collide on one user. It breaks in CI, not in production.
Keep tests off each other.
One frontmatter line groups experiments so no user is bucketed into two at once. Enforced at build, every time.
Conclude, don't abandon.
Archives the file to dif/experiments/concluded/, drafts the Decision block, and appends one line to the surface log so the next test starts informed.
Up and running in one build.
Install, point your code at a flag or experiment id, and ship. Same files, same CLI, whichever way you render.
$ npm install -g @dif.sh/cli $ npm install @dif.sh/sdk
$ dif init $ dif new checkout-cta --surface checkout $ dif build # emits dif/generated/client.ts + dif/context.json
// import the generated client once, at boot import "dif/generated/client"; import { dif } from "@dif.sh/sdk"; const checkoutCta = dif("checkout-cta", { control: () => "Buy now", variant_a: () => "Get it today", }); button.textContent = checkoutCta();
Just files. That's the point.
It all lives in one dif/ folder, checked into your repo. Each flag or experiment is a single .md file: experiments/active/ for what's live, experiments/concluded/ for what's done. surfaces/ keeps the running log of what each screen has taught you, and the agent's context.json sits right alongside.
A flag, an A/B test, a holdout, and a staged rollout are the same file.
Different frontmatter, same shape. Fewer concepts to learn, fewer ways to write a bug.
Git is the audit log. PR review is the approval flow.
No separate database or change history to reconcile against your code.
Guardrails compile to a tiny client. Production stays small.
Exclusion groups and conflict detection all happen at dif build, avoiding clashes and failed experiments.
The schema is in git. Customer data isn't.
Audiences declare which attributes you target on (country, plan, returning_visitor). Values arrive at runtime from your app's user context. No customer list ever gets committed.
One CLI, start to finish.
Each command is small, scriptable, and friendly to coding agents. None of them open a browser. None of them ask you to log in.
Sets up the folder.
Creates the dif/ folder, a config file, and one .gitignore line. All of it checked into git.
Drafts a new experiment.
Fills in the frontmatter and reads the surface log, so the new test starts from what you already learned.
Checks everything lines up.
Frontmatter is valid, the owner and surface exist, exclusion groups do not clash, and no variant in code points at a missing test.
Shows what a user gets.
Prints the exact variant a user lands in, and hands you a preview link with that variant forced. No dashboard needed.
Builds the client into your app.
Generates a typed client your app imports directly, so production never calls out to pick a variant. Refreshes context.json, and fails on any conflict.
Wraps up a test.
Moves the file to dif/experiments/concluded/, drafts the Decision block, and adds one line to the surface log so the next test starts where this one ended.
Yesterday's learning is in tomorrow's draft.
Every experiment runs on a surface. Each surface has its own markdown file with a description, the known landmines, and a running log of what you have learned. When your next test gets drafted on that surface, the surface file is the first thing the PR mentions or the agent reads.
One line per concluded test, written by the agent at the moment of conclusion. Over a year, you build a real institutional memory of what works on a given screen and what does not.
---
id: checkout-cta-v3
status: draft
surface: checkout
hypothesis: >
v2 won on returning visitors. Test whether
the same copy lifts cold traffic, where
prior copy tests were inconclusive.
---
## Brief
Surface log shows v2 shipped at +2.1% on
returning, cold unchanged. This is a follow-on
to close that gap. ← drafted from logWhen your team wants the view across every repo.
Dif Cloud is the optional hosted layer. It reads your repo and gives the team a live read on every experiment, with the confidence intervals to make the call. Git stays canonical: every change it proposes lands as a PR you review.

Live pulse
Every running experiment, its lift, and a ready-to-conclude flag, on one page.
Confidence intervals
Lift over time with a 95% interval, control and variant compared side by side.
Verdict as a PR
Cloud drafts the ship, hold, or investigate call into the experiment's .md.
Put your next flag in git.
Already shipping? Open Dif Cloud →