---
name: "feature_flags"
description: "Designing and operating feature flags: flag types, rollout strategies, kill switches, and cleanup discipline for agents shipping code changes. Triggers on: roll out a feature gradually, add a kill switch, feature flag design, darken-launch, remove old flags."
metadata: { "includeInPrompt": true }
---

## Purpose

Feature flags decouple deployment from release: ship code to trunk behind a flag, then expose it to users on your own schedule. Use them to roll out gradually, run experiments, and kill a bad change in seconds without a redeploy. The discipline that makes them work is cleanup — an undeleted flag is permanent conditional logic in your codebase, i.e. tech debt you opted into.

## Workflow

1. **Classify the flag.** Pick one type before writing any code (types below). The type determines default value, owner, and expiry policy.
2. **Name and register it.** Use `team.feature.state` (e.g. `checkout.applepay.v2`). Record owner + expiry date at creation in your flag registry — a registry row without both is rejected.
3. **Choose the rollout strategy.** Start with targeted (your team, then beta cohort), widen to percentage, go to 100%. Ring deployments for infrastructure-level changes.
4. **Add the kill switch first.** For any flag guarding a risky path, the kill branch is written before the feature branch. Default the flag to the SAFE value and decide fail-closed vs fail-open per blast radius (payments: fail-closed; UI copy: fail-open is usually fine).
5. **Ship, observe, complete.** Track exposure (who saw the flag on) alongside errors. When rollout hits 100% and holds for the agreed bake period, the flag comes out in the next sprint — the old path is deleted, not left behind.

## Output Contract

Given a change to ship safely, produce:

- **Flag type** (release / experiment / ops-kill-switch / permission / migration) and why it fits.
- **Flag name** in `team.feature.state` format, plus owner and expiry date.
- **Rollout plan**: stages (targeted → percentage → 100%), bucketing key, kill-switch default and fail mode.
- **Evaluation snippet** implementing the plan (see sketch below), wired to real user context.
- **Cleanup ticket**: the removal task with its due sprint, filed at creation time, not later.

Flag types (one paragraph each):

- **Release flags (trunk gating).** Gate unfinished or unreleased work so trunk stays deployable. Short-lived by design: the flag dies the moment the feature is fully rolled out and the old path is removed. Prefer one flag per feature slice.
- **Experiment flags (A/B with bucketing).** Split users into variants with stable bucketing so a user sees the same variant on every request. Keep the bucketing key fixed for the experiment's life — changing it mid-run invalidates results.
- **Ops / kill-switch flags.** Long-lived, rarely-on flags that disable a subsystem fast (payment provider, outbound email, expensive job). Default to the SAFE state; the kill branch must be the simplest possible code path.
- **Permission / entitlement flags.** Gate features by plan or entitlement (pro tier, beta program). These tend to be long-lived — that's acceptable, but treat them as access control, not rollout: they still get owners and review dates.
- **Migration flags.** Route traffic between old and new implementations of the same behavior (DB schema, API client, queue). Prefer dual-write + read-verify patterns where the blast radius justifies it; delete the old path aggressively once parity is proven.

## Operating Rules

**Rollout strategies.** Boolean on/off for kill switches and dark launches. Percentage rollout with consistent hashing on a stable user key — stable assignment matters because a user flipping between variants mid-session sees inconsistent behavior and corrupts experiment data. Targeted rollout by user IDs, cohorts, or geography for staged exposure. Ring/canary deployments for infra changes (one region, then all).

**Evaluation sketch (verified — see report).** Context-based rules evaluated top-down: kill switch first, then explicit targets, then percentage bucket, then default. Evaluation failures return the safe default.

```python
import hashlib

def _bucket(user_key: str, flag: str) -> float:
    h = hashlib.sha256(f"{flag}:{user_key}".encode()).hexdigest()
    return int(h[:8], 16) / 0xFFFFFFFF  # stable 0..1

def get_flag(user_key, flag_name, rules, default=False):
    try:
        r = rules.get(flag_name) or {}
        if r.get("kill"): return False                    # kill switch wins
        if user_key in r.get("users", set()): return True # targeted
        return _bucket(user_key, flag_name) * 100 < r.get("pct", 0)
    except Exception:
        return default  # eval failure -> fail-closed to safe value
```

**Tooling landscape.** OpenFeature: open standard / vendor-neutral SDK — prefer when you want to avoid lock-in or swap providers. Managed (LaunchDarkly): prefer when you want dashboards, targeting UI, and audit trails without operating infra. Self-hosted / open-source (Unleash, Flipt, Flagsmith): prefer when data must stay in your infrastructure or cost matters. Zero-dependency option for small projects — a config file:

```yaml
flags:
  new-checkout: { users: [u_alice], pct: 50, default: false }
```

**Lifecycle / cleanup discipline.** This is the #1 operational failure mode, so it gets teeth: every flag is created with an owner and an expiry date, recorded in the registry. Cleanup rule — remove the flag within 2 sprints of reaching 100% rollout (release/experiment/migration types); permission flags get a quarterly review instead. Run a stale-flag audit on a fixed cadence (monthly for active teams): any flag past expiry without a renewed date gets a removal ticket assigned to its owner. Prefer deleting the old path over leaving "just in case" branches — the flag did its job; keeping it is the debt.

**Anti-patterns.** Flags that are never removed (the default outcome without the cleanup rule above). Flag-dependent flags — nesting one flag's evaluation inside another's branch; flatten to independent flags or a single rule set. Using flags as general config for non-rollout settings (timeouts, URLs, credentials) — that's what config management is for; flags gate behavior changes. Percentage rollout keyed on unstable identifiers (session ID, request ID, IP) — assignment must survive across requests, so key on user ID or another stable key.
