Feature Flags for LLM Changes: Ship Fast, Roll Back in 30s
Most teams treat a model swap or prompt change as a config edit — push to main, deploy, done. That's fine until a release quietly degrades your top-revenue flow and you find out three days later from a customer complaint, not your dashboards.
Feature flags are a solved problem in traditional software. In AI products, almost nobody uses them properly — and the gap is costing teams their release velocity and their sleep.
Why AI Changes Are Uniquely Dangerous to Ship Dark
A backend API change either works or throws an exception. An LLM change can "work" — no 5xx, no crashed pod — while producing subtly worse outputs for 30% of inputs. Token counts look normal. Latency is fine. Your monitors stay green. The regression lives entirely in the semantic quality of the response, invisible to infrastructure observability.
This is the core problem. The failure mode for AI isn't availability, it's silent quality degradation. And the blast radius is proportional to how many users you expose before you notice.
The fix isn't slower releases. It's controlled exposure — shipping to 1% before you ship to 100%, with the instrumentation to know the difference.
The Four AI Changes That Need Flag Coverage
Not everything needs a flag. Here's the decision rule:
| Change Type | Flag Required? | Why |
|---|---|---|
| Model version swap (e.g. gpt-4o → gpt-4o-mini) | Yes | Output quality and cost both shift |
| System prompt edit (>10 tokens changed) | Yes | Behavior can drift in non-obvious ways |
| Retrieval strategy change (chunking, reranker) | Yes | Precision changes are hard to catch in staging |
| Temperature / top-p adjustment | Yes | Stochastic — staging tests miss tail behavior |
| Infrastructure-only change | No | Standard deploy + monitors sufficient |
| Embedding model upgrade | Yes | Similarity scores shift; recall degrades silently |
The pattern: any change that affects what the model sees or how it reasons needs a flag. Changes that only affect how the response is delivered don't.
A Minimal Flag Architecture That Ships in a Day
You don't need a full feature flag platform on day one. Here's a pattern that a single engineer can wire up in hours. The implementation below is Python — the logic is the same in any language: hash the user ID deterministically, bucket it modulo 100, compare to your rollout threshold.
pythonimport os import random from dataclasses import dataclass from typing import Literal @dataclass class AIVariant: model: str system_prompt: str temperature: float variant_name: str VARIANTS: dict[str, AIVariant] = { "control": AIVariant( model="gpt-4o", system_prompt=SYSTEM_PROMPT_V1, temperature=0.3, variant_name="control", ), "treatment": AIVariant( model="gpt-4o", system_prompt=SYSTEM_PROMPT_V2, temperature=0.3, variant_name="treatment", ), } def get_variant(user_id: str, rollout_pct: float = 0.05) -> AIVariant: """ Sticky assignment: same user always gets same variant. rollout_pct=0.05 means 5% see 'treatment'. """ # Deterministic hash so assignment is stable across requests bucket = int(user_id[-4:], 16) % 100 # last 4 hex chars of UUID if bucket < int(rollout_pct * 100): return VARIANTS["treatment"] return VARIANTS["control"]
In non-Python stacks, the equivalent is: take the last N hex characters of the user UUID, parse as an integer, mod 100, compare to your threshold. No database, no race condition, no added latency.
Key details that matter:
- Sticky assignment — the same user gets the same variant on every request. Flipping a user between variants mid-session produces garbage signal.
- Deterministic hashing — no database lookup required, no race condition, no latency added.
- Rollout percent is a deploy-time env var — you go from 5% to 20% to 100% by changing one variable, not by redeploying code.
For teams already using LaunchDarkly, Unleash, or Statsig, plug your AI variant selection into the same SDK you use for everything else. The AI context is just another flag. Don't build a parallel system.
What to Measure During the Rollout Window
The flag is useless without the right metrics. Infrastructure metrics (latency, error rate, token count) are table stakes — they catch the obvious breaks. What actually catches silent regressions:
LLM-as-judge scoring. Run a sample of treatment outputs through a lightweight eval prompt that scores on your quality dimensions (relevance, factual consistency, format compliance). You can do this async, post-response, at 10–20% sample rate without adding latency. The cost is negligible.
pythonasync def score_response_async(user_input: str, model_response: str, variant: str): score_prompt = f""" Rate this AI response on a scale of 1-5 for: - Relevance to the user's question - Factual consistency (no hallucinations you can detect) - Format adherence User input: {user_input} Response: {model_response} Return JSON: {{"relevance": int, "consistency": int, "format": int}} """ # Use a cheap model for scoring — gpt-4o-mini at $0.15/1M input tokens result = await openai_client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": score_prompt}], response_format={"type": "json_object"}, ) scores = json.loads(result.choices[0].message.content) scores["variant"] = variant emit_metric("llm_quality_score", scores) # to your observability stack
User behavioral signals. Did treatment users regenerate the response more often? Copy it less? Abandon the flow? These are leading indicators you can measure in hours, not the days it takes to accumulate enough explicit feedback.
Cost-per-successful-completion. Not just token cost in isolation — cost relative to a successful outcome. A model that costs more but completes meaningfully more tasks can still be the better business choice. Measure the ratio, not the absolute.
The Rollout Cadence That Prevents Surprise Regressions
Here's a schedule that works for non-trivial AI changes. The specific hours are guidelines — the underlying logic is what matters: give each gate enough clock time to surface the failure modes that only appear at that traffic level.
- Hour 0: Deploy with flag at 1%. Monitor LLM-as-judge scores and error rate for 2 hours.
- Hour 2: If scores are within ±5% of control, bump to 10%. Watch for 24 hours to catch daily usage pattern differences.
- Day 2: If behavioral metrics are stable, go to 50% for 48 hours. This is where you catch edge cases that only appear at volume.
- Day 4: Full rollout. Kill the control code path within one sprint to avoid flag debt.
The total elapsed time is 4 days. That's not slow — the engineering hours invested are minimal. The rest is clock time you need anyway to observe stochastic behavior across real usage patterns. The 24-hour hold at 10% specifically exists because LLM quality issues often correlate with time-of-day request distributions that your synthetic tests never replicate.
If at any point a metric degrades past your threshold, flip the flag back to 0%. The rollback takes 30 seconds. No hotfix, no incident, no 2am page.
The Flag Debt Problem (Don't Skip This)
Every flag you add is a branch in your codebase. Let flags accumulate and you end up testing combinations nobody understands, debugging interactions between flags, and carrying dead code for months.
The rule: every flag has a kill date. When you create the flag, create the ticket to delete it. Four weeks is the maximum. If you haven't reached 100% rollout in four weeks, that's a product decision problem, not an engineering one — address it explicitly.
This is where most teams fall down. They build the flagging infrastructure, use it once, then let it rot. The discipline is in the cleanup, not the creation. Stale flags cause two categories of damage that are easy to underestimate:
- Silent interaction bugs. Two flags that were individually safe can combine to produce behavior nobody tested. Imagine a prompt-variant flag and a retrieval-strategy flag both live simultaneously — the combination is a third, untested configuration that your evals never saw.
- Onboarding drag. Every live flag is a conditional branch a new engineer has to understand before they can reason about a code path. Three stale flags across a critical agent flow can double the cognitive load of tracing a request end-to-end. Teams with poor flag hygiene consistently see slower PR review cycles on agent code because reviewers can't confidently reason about what state a given user will be in.
The cleanup discipline matters as much as the flagging discipline.
When to Skip the Flag and Just Ship
Flags add overhead. Not every AI change warrants one. The counter-pattern matters as much as the pattern.
Don't flag:
- Bug fixes that correct provably wrong output (ship directly, write a regression test)
- Changes that are fully reversible via a single env var without code
- Styling or formatting changes with no semantic effect
- Changes to non-user-facing internal agents where you have complete observability already
The point of flagging is controlling exposure when the feedback loop is slow and the failure mode is silent. If you've already closed that gap another way, flags are just friction.
For teams building multi-agent systems, agent retry logic is another place where silent cost amplification hides — the same principle applies: instrument before you optimize, don't discover the problem in a billing alert.
Staging Environments Won't Save You Here
The teams shipping AI fastest aren't skipping tests — they're running tests in production with a 2% blast radius instead of a staging environment that doesn't match reality.
Staging environments for LLM products are close to useless for catching semantic regressions. The data distribution is wrong, the volume is too low to surface tail behavior, and the model's outputs are stochastic enough that you need hundreds of real requests to get a reliable signal. A well-documented example of this class of problem: Knight Capital's 2012 trading system failure wasn't an LLM issue, but it's the canonical case of a staged rollout that missed a production-only code path — the lesson generalises. In LLM systems, the "production-only path" is the long tail of real user inputs your staging fixtures never contain. Stop pretending staging catches what flags catch at 5% traffic.
What to Actually Do
- Audit your last five AI changes. How many went straight to 100%? What was your rollback time if one had degraded? If the answer is "we'd have to redeploy", you have a gap.
- Pick one upcoming change — model upgrade, prompt edit, retrieval tweak — and instrument it with a minimal sticky-assignment flag this week. The implementation above is a starting point regardless of your language stack.
- Wire three metrics before you touch the rollout percentage: infrastructure error rate, LLM-as-judge quality score at 15% sample rate, and one behavioral signal (regeneration rate, task completion, or equivalent).
- Write the delete ticket the same day you write the flag. Set a 4-week deadline. Put it in the same sprint cycle.
- Document your rollout threshold. What number on your quality score triggers a rollback? Decide before the rollout, not during it — you don't want to be making judgment calls at 11pm.
Shipping fast without breaking things isn't about moving slower. It's about making the blast radius of any single change small enough that a mistake is recoverable before it's visible at scale.
Working on something like this? I take on a few fractional-CTO and AI engagements at a time.
Get my AI playbooks — straight to your inbox
Practical notes on shipping production AI, scaling teams, and the calls a CTO actually has to make. A few times a month. No spam, no fluff.