How-to

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

  1. On startup, initialize_feature_flags() calls the DevCycle SDK once and stores every flag in _FEATURE_FLAGS.
  2. At runtime, get_feature_flag(key, default) checks the store, then the environment variable, then returns the default. Sub-millisecond latency.
  3. When a flag changes in DevCycle, the configured webhook hits POST /webhooks/devcycle and 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 varPurpose
DEVCYCLE_SERVER_KEYDevCycle 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

.env
bash
DEVCYCLE_SERVER_KEY=dvc_server_xxx
# Per-flag fallback (used if DevCycle is unreachable)
ENABLE_SMART_TURN=false

Call initialize_feature_flags() once during application startup:

run.py
python
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

example.py
python
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.

Next steps

Was this helpful?