Skip to main content
Most flags in this repo are binary: a change is off, or on for everyone. A canary is the rung in between — the same change, enabled for a stable slice of installs, ramped as the signal holds.
A canary is a measured rollout: a slice is enrolled so it can be compared against everyone else. So opting out of telemetry opts you out of canaries — an opted-out install is never bucketed at all. See the telemetry command for the opt-out routes.

Add one

1. Register it at 0% in packages/core/src/canaryRegistry.ts:
At 0 it is inert, so this lands safely on its own. 2. Read it at the decision point.
That is the whole API. The percentage lives in the registry, never at the call site. 3. Ramp it by editing percentage in a patch release: 0 → 5 → 25 → 100. Widening is inclusive — everyone already in the 10 stays in the 25, so a before/after comparison survives the ramp. Do not rename a live canary: the name is part of the bucket hash, so renaming reshuffles the cohort mid-rollout. 4. Delete it at 100 and holding — the registry entry and the branch it guarded. The scheduled Canary sunset workflow enforces this: it runs weekly and fails once sunsetAfter passes, naming the overdue rollout and its owner in the run log. It notifies nobody, so watch it if you own a canary. Run it yourself with bun scripts/check-canary-sunset.ts.

Override one

Upper-snake-case the name. In Studio, use ?hf_canary_my_feature=on. An override always wins over the percentage, in both directions — escalations, dogfooding, bisects, a panic-off. It is also the way to exercise a canary with telemetry off. Everything else fails closed below 100%: no bucket seed, an unregistered name, or a CI machine all resolve to not enrolled. At exactly 100% those exclusions stop applying, so nothing takes the new path for the first time at deletion.

Remove one from your machine

Cohorts bucket on a seed in install-state.json, which sits beside config.json in ~/.hyperframes — not on the telemetry id. So a full reset is one command:
hyperframes telemetry status prints both paths if you want to inspect or delete them individually. Nothing canary-related is stored anywhere else.

Where it lives

The evaluator is dependency-free so Studio and the embeddable player can use it too; each surface supplies its own unit id through a thin binding. Studio adopts the CLI’s decisions rather than re-deriving them, so one render spanning both surfaces never runs half-enrolled.