Feature flags
DevCycle-backed feature flags loaded once at startup into an in-memory store, with env-var fallback and webhook-driven updates.
Breeze Buddy uses DevCycle for runtime feature flags without paying a network tax per flag check. At startup the server pulls all flags from DevCycle’s CDN into a global dict; every subsequent get_feature_flag() is a dict lookup. Webhooks push updates in real time — no polling.
Prerequisites
- A DevCycle account and server SDK key.
- Network access from the app to DevCycle’s CDN.
- Optional but recommended: webhook configured in the DevCycle dashboard pointing at
https://your-app/webhooks/devcycle.
How the store works
- On startup,
initialize_feature_flags()calls the DevCycle SDK once and stores every flag in_FEATURE_FLAGS. - At runtime,
get_feature_flag(key, default)checks the store, then the environment variable, then returns the default. Sub-millisecond latency. - When a flag changes in DevCycle, the configured webhook hits
POST /webhooks/devcycleand the store updates.
If DevCycle is unreachable at startup, the app logs the error and continues with env-var-only resolution. No flag check ever fails.
Key settings
| Env var | Purpose |
|---|---|
DEVCYCLE_SERVER_KEY | DevCycle server SDK key. Without it, the store stays empty and everything falls through to env vars. |
Per-flag fallbacks are set via environment variables with the same name as the DevCycle flag key. Example: flag enable_smart_turn → env var ENABLE_SMART_TURN.
Setup
DEVCYCLE_SERVER_KEY=dvc_server_xxx
# Per-flag fallback (used if DevCycle is unreachable)
ENABLE_SMART_TURN=falseCall initialize_feature_flags() once during application startup:
from app.services.live_config.store import initialize_feature_flags
#
async def startup():
await initialize_feature_flags()Configure the webhook in DevCycle’s dashboard so flag changes propagate instantly:
- URL:
https://your-app/webhooks/devcycle - Method:
POST
Using a flag
from app.core.config.dynamic import get_feature_flag, is_feature_enabled
#
if is_feature_enabled("enable_smart_turn", default=False):
# use smart turn detection
...
#
timeout = get_feature_flag("user_speech_timeout_ms", default=1500)Operational notes
- Missing key — no server key is logged as info, not error. Tests and local dev can run without DevCycle entirely.
- Network error on startup — logs an error and continues. The app starts; flags fall through to env vars.
- Invalid webhook payload — returns 400 and logs a warning. The stored flag is unchanged.
- Unknown flag — returns the provided default. No error, no throw.
- Type coercion — values are stored as-is from DevCycle. Callers pass a default of the expected type; env-var fallback coerces from strings.
Health check
A /health/feature-flags endpoint exposes the store size and last-update timestamp. Alert if size drops to zero unexpectedly — that usually means DevCycle credentials rotated.