Skip to content

Feature Flags Reference

Curaway uses Flagsmith for feature flag management. Flags control which features are active in each environment and allow gradual rollouts without code deploys.

Source of truth: config/feature_flags.yaml (58+ flags as of Session 95)

Architecture

  • Backend: The Python backend queries Flagsmith on startup and caches flag values for 60 seconds (configurable via FLAGSMITH_CACHE_TTL). This prevents excessive API calls and avoids the Flagsmith warning flood issue documented in the troubleshooting runbook.
  • Frontend: The React frontend uses the Flagsmith React SDK and evaluates flags client-side based on the current tenant identity.
  • Targeting: Flags can be targeted by tenant ID, environment, or percentage rollout.

Flag Inventory

Matching

Flag Default Description Session
agent_enhanced_matching false Enable LLM-enhanced matching (clinical analysis + reranking)
matching_v2_ml_enabled false Enable ML ranking matching strategy
matching_shadow_mode false Run v2 strategy in shadow mode (log only)
matching_engine_v2 false Route matching through the registry-driven scorer (Phase 1 of #767, ADR-0026). Off keeps the legacy WeightedScoringV1 / GraphEnhancedWeightedV1 path canonical. Flip per-tenant via Flagsmith identity-override after smoke. Runbook: matching-engine.md 95
matching_max_providers "3" Maximum number of providers to return in match results. Default 3 for demo, increase for production v1.3
DOCTORS_IN_MATCHING false Include doctor-level data in match results (language concordance, procedure stats) 26
embedding_document_matching true Use Voyage AI embeddings for document-to-requirement matching 25B

Agents

Flag Default Description Session
agent_xray_interpretation true Enable AI X-ray interpretation in intake
agent_risk_scoring true Enable automated risk scoring
agent_intake_enabled true Enable conversational intake agent
agent_explanations_enabled true Enable LLM-generated match explanations (replaces templates)
multilingual_explanations true Generate match explanations in patient locale

Guardrails

Flag Default Description Session
guardrail_input_classifier_enabled true Enable LLM-based input message classification and blocking 24
guardrail_output_validator_enabled true Enable output validation (regex + policy checks) on agent responses 24
guardrail_file_validation_enabled true Enable file upload validation (extension, MIME, size checks) 24

Performance

Flag Default Description Session
enable_timing_middleware true Enable per-request timing middleware. Records total_ms + segment timings, adds X-Request-Timing-Ms header, writes SYS_REQUEST_TIMING events for /api/ routes 31
enable_chat_cache true Enable Redis caching for chat pipeline hot data (patient state, conversation context). Reduces cold DB fetches per turn. TTLs: state 60s, context 120s 33
enable_parallel_pipeline true Run input classifier and conversation context fetch concurrently via asyncio.gather. Saves ~200-400ms per chat turn. When false, falls back to sequential execution 33
enable_deferred_extraction true Defer chat extractor LLM call and post-extraction persistence to a fire-and-forget background task. Patient gets response immediately. When false, extraction runs inline before returning 33
enable_response_streaming true Stream LLM response tokens to frontend via Redis SSE channel. When false, falls back to non-streaming ainvoke 36
enable_doc_pipeline_timing true Instrument each stage of the document pipeline and log SYS_DOC_PIPELINE_TIMING events 32
prompt_version "v2_compressed" Prompt version for LLM system prompts. 'v2_compressed' uses ~35% fewer tokens. 'v1_original' uses full verbose prompts 34

Conversation Flow

Flag Default Description Session
gates_v2 true Lower-the-gates conversation flow fix (Layer 1). Replaces six-AND intake completion gate with five-condition check (procedure + evidence + age/country + meds-or-skip + allergies-or-skip), drops completeness_for_matching 0.5→0.4, and adds patient explicit-advance phrase override ("find providers now", "i'm ready", "proceed"). Disable to revert to legacy gates instantly. Emits gates_v2.intake_complete, gates_v2.explicit_advance, gates_v2.matching_advanced events 33
chat_extractor_sync not implemented Layer 3, doc-only. Will run the chat extractor synchronously inline (instead of deferred via asyncio.create_task) so EHR is updated before the next routing decision. Spec: docs/specs/feat/conversation-flow-layer3-sync-extractor.md. Depends on Layer 1 in production for ≥1 week before implementation 33 (planned)
prompt_arch "v6" (YAML) / pending Flagsmith Production sync Conversation prompt architecture. Values: "v6" (stages.yaml + knowledge addendums; Phases 1-9 shipped 2026-05-16, PR-1 readiness fixes in #943) or "v4" (legacy phase×layer composition, rollback target). Identity-aware via Flagsmith — per-tenant and per-user overrides honored. The v4 path remains fully functional and is the fallback target on ANY v6 dispatcher error (see v6_fallback_reason Langfuse trace tag). Roll back by flipping default to "v4". Spec: docs/specs/conversation-v6-feature.md §8. v6 PR-1
prompt_arch_v6_tenant_allowlist '["*"]' (YAML) / pending Flagsmith Production sync Belt-and-suspenders gate. JSON-string list of tenant IDs allowed to receive v6 prompts when prompt_arch=v6. '["*"]' wildcard = all tenants (current YAML default after Phase 9 rollout — prod is internal-only at this stage). Set to a specific tenant-id list to scope down without flipping prompt_arch globally. Enforced inside app/agents/v6_dispatcher.py Layer 2. v6 PR-1
triage_prompt_cache_enabled false Enable Anthropic prompt cache marker on triage agent system message. Single cache_control: ephemeral marker wrapped around the full assembled system_text so within-case turns hit the prompt cache at 10% input cost. Zero patient-facing behavior change — markers don't affect generated text. Default OFF; flip ON after 48h prod soak. Issue #948, PR-4.5. v6 PR-4.5
v6_sop_enabled false SOP framework master kill-switch. When ON, compose_v6 substitutes get_sop_prompt_segment output into the {sop_context} placeholder; when OFF, the segment is empty and the v6 prompt is byte-identical to its pre-SOP form. Default OFF — Phase 1 is response-content-neutral until Naidu's TKR/THR SOPs land in config/prompts/sops/. Identity-aware via Flagsmith. Spec: docs/superpowers/specs/2026-05-19-sop-framework-design.md §8. SOP PR-2 (#1114)
sop_segment_max_words 400 Word cap on the assembled SOP prompt segment. Loader (app/services/sop_loader.get_sop_prompt_segment) truncates the block body but keeps the voice-rule closer intact. Int > 0. SOP PR-2 (#1114)
sop_sticky_min_turns 2 Minimum turns the SOP active layer holds before advancing. Spec §4 sticky window heuristic. Loader reads in get_active_layer. Int >= 1. SOP PR-2 (#1114)
prompt_arch_v6_intent_aware false v6.1a master gate. When ON, compose_v6 calls pivot_classifier + select_addendums for multi-addendum injection (PR-C). When OFF, byte-identical to the v6.0 single-addendum path. Default OFF — flip ON in Dev only after PR-D grader axis-10 validates the fixture corpus. Spec: docs/superpowers/specs/2026-05-24-v6.1-intent-aware-composition-design.md §5. v6.1 PR-A (#1160)
pivot_classifier_use_llm true Cost guardrail for the v6.1 pivot_classifier. When true, regex-miss turns fall through to a Haiku LLM call (~$0.0005/turn). When false, regex-miss returns the safe support / 0.5 / [] fallback and the LLM is not called. Flip false if monthly classifier spend exceeds the $5 alarm threshold (vs $0.09/mo projection). Spec §1. v6.1 PR-A (#1160)
pivot_classifier_min_confidence "0.7" (string-typed CONFIG flag) Confidence threshold above which the multi-addendum selector (PR-B) injects a secondary addendum. Below this, single-addendum path (today's v6.0 behavior). Caller parses with float(). String-typed so Flagsmith treats it as a CONFIG value. Spec §5. v6.1 PR-A (#1160)
prompt_arch_v6_intent_aware_stage false v6.1b (Phase 2) gate — stage-resolver intent input + truth-table extension. NEVER flipped in Phase 1. Declared in PR-A so the eventual Phase 2 rollout is a flag flip, not a flag-creation deploy. Spec §"Phase 2 — stage-resolver intent input (v6.1b) — DEFERRED". v6.1 PR-A (#1160)

OCR

Flag Default Description Session
ocr_fallback_provider "unstructured" Tier 2 OCR fallback when PyMuPDF returns insufficient text. Values: 'unstructured' or 'claude_vision' v1.2
enable_inline_ocr true Try PyMuPDF text extraction inline during confirm_upload. If >100 chars extracted, run full processing pipeline synchronously. Saves 200-500ms QStash round-trip for native PDFs. Falls back to QStash for scanned/image PDFs or on error 35

Voice

Flag Default Description Session
voice_stt_provider "client_side" Voice transcription provider. Values: 'client_side' (Web Speech API), 'whisper' (OpenAI Whisper API), 'deepgram' (future) v1.3

Storefront

Flag Default Description Session
PUBLIC_API_ENABLED true Enable public storefront API (/api/v1/public/*). When false, returns 503 29
STOREFRONT_PAGES_ENABLED false Enable storefront frontend routes. When false, redirects to homepage 29
PUBLIC_SEARCH_ENABLED true Enable public search endpoint independently 29
STOREFRONT_CACHE_ENABLED true Enable Redis caching for storefront. When false, all requests hit DB 29
PROVIDER_COMPLETENESS_DISPLAY false Show completeness badges on provider listing cards 29
procedure_reqs_from_graph true Route procedure requirements through Neo4j for rich test metadata (validity_days, source_acceptance, on_site_required). Falls back to PostgreSQL on Neo4j ServiceUnavailable (hardened in #773; renamed lowercase in #1093) 31

Data

Flag Default Description Session
behavioral_tracking true Enable PostHog behavioral event capture
gdpr_data_export true Enable patient data export endpoint
comorbidity_llm_shadow true Run LLM shadow check on lab values alongside rule-based detection. Logs discrepancies to Langfuse v1.3
provider_dashboard true Enable provider-facing dashboard
swagger_ui_enabled true Show Swagger UI at /docs (disable in production)
sparse_fieldsets_enabled true Allow ?fields= parameter for smaller payloads v1.1
fhir_provenance_v1 false Store document_id + case_id on FHIR resources as provenance metadata. When enabled, every FHIR resource generated by the Clinical Context Agent carries its source document and originating case. Spec: docs/specs/fhir-provenance-feature.md 35
case_record_porting_v1 false Enable the record-porting consent card between cases. When enabled, patients moving to a new case see a consent UI to port previously analyzed documents rather than re-uploading. Spec: docs/specs/case-record-porting-feature.md 39

Notifications

Flag Default Description Session
notify_email_enabled true Enable email notifications via Resend v1.1
notify_sms_enabled false Enable SMS notifications via Twilio (stub) v1.1
notify_push_mobile_enabled false Enable mobile push via FCM/APNs (stub) v1.1
notify_push_web_enabled false Enable web push notifications (stub) v1.1
notify_in_app_enabled true Enable in-app notifications v1.1
provider_webhooks_enabled false Enable outbound webhooks to provider systems v1.1

Frontend (Vite Env Vars)

These flags live in the frontend repo (curaway-health-navigator) as VITE_* environment variables, not in Flagsmith. They are baked in at build time on Vercel.

Flag Default Description Session
VITE_FULL_EHR_VIEW true Enable the "View Full Record" CTA on EHRPanel and the FullEHRDrawer overlay (15 collapsible sections, completeness ring, lab results with LOINC pills, FHIR validation badges, source provenance, risk factors). Disable to fall back to the in-panel summary only 33
VITE_POSTHOG_KEY empty PostHog project key. When empty, all PostHog calls become no-ops 23B

Video & Storage

Flag Default Description Session
video_consultations_enabled false Enable video consultation scheduling and sessions v1.1
presigned_uploads_enabled true Enable presigned URL uploads to R2 v1.1

LLM Fallback Gateway

Flag Default Description Session
llm_fallback_enabled true Enable GPT-4o mini fallback when Claude fails (5xx, 429, 401, timeout). When false, Claude failures raise immediately to agent-level deterministic fallback 39
llm_fallback_provider "gpt-4o-mini" Model ID for the fallback provider. Default gpt-4o-mini. Can be changed to gpt-4o for higher quality at higher cost 39

Chain-of-Thought Prompts

Flag Default Description Session
cot_clinical_parsing true Enable 7-step CoT reasoning in Clinical Context Agent's map_to_medical_codes node (ICD-10/SNOMED mapping). Reduces laterality errors. Disable to revert to non-CoT prompt 34
cot_clinical_reasoning true Enable 7-step CoT reasoning in Match Agent's analyze_clinical_picture node. Adds comorbidity_interactions field for shadow-mode divergence detection 34
examples_variant "" A/B test variant for few-shot examples. Empty string = default examples. Set to a variant name (e.g., 'concise') to load alternate prompt YAML 35

Multi-Tenancy Phase 0

Flag Default Description Session
rbac_enforcement false Enable RBAC permission enforcement on API routes. When false, middleware resolves roles/permissions but does not block requests 45
case_state_machine_v2 false Validate case status transitions via python-statemachine. When false, existing forward-only status list is used. Advisory logging only in Phase 0 45
redaction_engine false Enable field-level redaction when creating cross-tenant case shares. When false, case shares are created without redaction applied 45
wave2_layer_ui false Enable layer-based UI components (progress rail, summary panel, layer cards). Requires triage_agent_v3=true on backend for layer_state data 45

Multi-Tenancy Phase 1 — Provider Flow

Flag Default Description Session
provider_forwarding_v1 false Enable provider case forwarding and quote APIs 47
provider_clerk_integration false Create Clerk orgs for provider tenants (mock in Phase 1, live in Phase 2) 47
provider_portal_v1 false Enable provider portal frontend and patient quote comparison endpoint 47
clerk_role_auto_assign_enabled false JIT auto-assign user_roles from Clerk JWT org_role claims. When on, RBAC middleware reconciles roles on login. When off, manual user_roles INSERTs required (pre-2026-04-27). Spec: docs/specs/clerk-role-auto-assign-feature.md (Issue #430) 77

Multi-Tenancy Phase 2 — Risk Assessment + MSO

Flag Default Description Session
risk_assessment_enabled true Gate the entire risk workflow. When false, consent_given skips to providers_notified 49
risk_review_human_required true All cases need human review. When false, low-risk cases (score < 20, no blocking) auto-clear 49
risk_blocking_override_allowed false Reviewers can override blocking risk factors. Requires override_reason 49
mso_consultation_enabled false Enable MSO matching + consultation flow. Parent gate for all MSO features 49
mso_video_embedded false Use Daily.co API for video rooms. When false, mock URL. Only effective when mso_consultation_enabled=true 49

Triage + MSO Thresholds (PR #762, Session 95)

These flags are stored as Flagsmith per-identity overrides (keyed by tenant ID). They are tunable from the /admin/triage page without a code deploy. See docs/runbook/triage-tuning.md for adjustment guidance.

Flag Default Description Session
triage_layer_advance_threshold 0.7 Minimum triage confidence score required to auto-advance a patient past triage to provider matching. Lower = more patients advance automatically. 95
mso_completion_threshold 0.8 Minimum intent-completion score (from the MSO offer agent) required before an MSO consultation is offered. 95
mso_pfs_skip_threshold 60 If the patient's PFS signal exceeds this value (days), the PFS branch of the MSO offer is skipped. 95
mso_eligible_decision_stages ["comparing_options", "decided_on_destination"] JSON array of patient decision stages that qualify for MSO routing. 95
mso_max_offers_per_case 3 Maximum number of MSO consultations a single case may receive. Set to 0 to disable MSO for a tenant. 95

Multi-Tenancy Phase 3 — Facilitators + Coordinators

Flag Default Description Session
facilitator_consent_enabled false Enable facilitator consent grant/revoke API. Patient can delegate case access to facilitators 50
coordinator_assignment_enabled false Auto-assign coordinator at payment_locked. Ranking by workload, tier, language, availability 50
coordinator_queue_enabled false Coordinator case queue dashboard with filtering and pagination 50
coordinator_services_transport_v1 false Transport vendor directory + booking CRUD for coordinators 50
coordinator_timeline_enabled false Timeline milestones, events, and coordinator notes on cases 50
coordinator_escalation_enabled false Enable escalation triggers (coordinator_manual, system_overdue). Phase 4 adds SMS/push 50
coordinator_csat_enabled false Enable CSAT/ratings collection for coordinators and facilitators 50

Patient-facing Transport (#959)

Flag Default Description Session
patient_transport_offer_enabled false Patient-facing transport offer card. When true and providers_selected is true and workflow_state.transport_offered is not set, the orchestrator routes to _handle_transport_offer and emits a transport_offer rich_content card. Distinct from coordinator_services_transport_v1 (admin UI). Default off pending Naidu clinical sign-off + product confirmation on eligibility rules. Operator must run scripts/seed_transport_vendors.py against the target Postgres before flipping. 60
transport_phase_context_version "v1" Active version of config/prompts/phase_contexts/v2/transport_offer.yaml. Bump to v2 once a vN.M patch lands and clinical re-review passes — Flagsmith rollback path per CLAUDE.md PROMPTS rule. 60

Adding New Flags

  1. Add the flag to config/feature_flags.yaml with a default and description.
  2. Create the flag in the Flagsmith dashboard with a descriptive name using snake_case.
  3. Set the default value and any tenant-level overrides.
  4. In the backend, access the flag via the flagsmith_client:
from app.core.flagsmith import flagsmith_client

if flagsmith_client.get_flag_value("my_new_flag", tenant_id=tenant_id):
    # Feature-flagged code path
    ...
  1. In the frontend, use the Flagsmith React hook:
import { useFlags } from 'flagsmith/react';

function MyComponent() {
  const flags = useFlags(['my_new_flag']);

  if (flags.my_new_flag.enabled) {
    return <NewFeature />;
  }

  return <ExistingFeature />;
}
  1. Document the flag in this page and update config/feature_flags.yaml.

Flag Lifecycle

Stage Description
Experimental Flag defaults to false; enabled only for specific test tenants
Beta Flag enabled for a subset of production tenants via targeting
GA (General Availability) Flag defaults to true for all tenants
Cleanup Flag is removed from code; the feature is permanently enabled

Flags should not remain in the "Experimental" stage for more than 90 days. If a feature is not promoted to Beta within that window, it should be removed.


Flags Added in Session 40

Flag Default Category Description Session
matching_weights_v1 JSON object Matching Matching engine weight overrides loaded from Flagsmith. Keys: clinical_relevance, outcome_score, semantic_match, cost_score, travel, accreditation, preferences. Falls back to hardcoded defaults if flag missing. 40
consent_validity_days 365 Data Number of days a consent record remains valid before triggering expiry warning. Used by check_consent_expiry(). 40

Flags Added in Slice 2a (Admin Users + Tenants)

Backs /api/v1/admin/users/* and /api/v1/admin/tenants/*. Both default OFF per the spec rollout: Flagsmith identity-override for SD's Clerk user_id first, then 100%. The router calls is_feature_enabled(flag, tenant_id, identity=actor_user_id) so per-user overrides work.

Flag Default Category Description Session
admin_user_management_enabled false Admin Gates /api/v1/admin/users/* (search, detail, grant, revoke, by-email lookup). 503 FEATURE_DISABLED when off. Slice 2a
admin_tenant_management_enabled false Admin Gates /api/v1/admin/tenants/* and /admin/org-mappings/*. Reserved for slice 2b. Slice 2a

Identity-override precedence

is_feature_enabled(name, tenant_id, *, identity=None) resolves the Flagsmith identifier as identity or tenant_id. For an admin route the caller's Clerk user_id is passed as identity so a single admin can be granted access without flipping the flag for everyone.

Identity override propagation (multi-replica caveat)

Identity overrides set via set_identity_override are immediate at the Flagsmith core API (api.flagsmith.com). The Curaway backend runs the Flagsmith SDK in local-evaluation mode with environment_refresh_interval_seconds=60, so the SDK normally takes up to 60 seconds to pick up changes for any given replica's in-memory env document.

To eliminate that lag on the writing replica, every admin write (set_identity_override, env-level update_feature_state, dual-env dual_env_patch, drift resolve_drift, and the matching-engine update_matching_config route that writes matching_weights_v1) calls force_refresh_local_eval_env() after the upstream Flagsmith write succeeds. That helper invokes the SDK's public update_environment() method (flagsmith/flagsmith.py:293-311), which re-GETs the env document and refreshes the local-eval cache in-place. The next is_feature_enabled / get_feature_value call on that replica then resolves the freshly-written value without waiting for the polling cycle. Failures are logged + swallowed so a transient api.flagsmith.com blip never breaks the admin write that just succeeded.

Sibling replicas still wait up to 60s for their own poll cycle (see #1129). For testing-grade "did my override take effect" verification, prefer:

  1. Same-replica check via the admin /api/v1/admin/flags/{feature_name}/scope-summary endpoint (sticky-routes to the writing replica when the load balancer honours session affinity).
  2. Direct query to api.flagsmith.com/api/v1/environments/{env_key}/identities/?q={identifier} with the admin token (bypasses every SDK cache layer).
  3. Wait 60s for sibling replicas to converge.

Future work (#1129 Option D): Redis pub/sub eviction broadcasts env-doc-changed events across replicas for immediate cross-replica consistency. Out of scope today; admin tool runs on a single Railway container so the writing-replica refresh covers the operator UX.


Flags Added in Commerce PR-6 (Webhook Router)

Backs the unified canonical webhook ingress at /api/v1/commerce/webhooks/{provider} (spec commerce-module-feature.md §6). Default OFF in both Production and Development; legacy /api/v1/webhooks/{stripe,razorpay} remains authoritative until cutover.

Flag Default Category Description Session
commerce_webhooks_enabled false Commerce Gate the unified commerce webhook ingress. When off the endpoint returns 503 + COMMERCE_WEBHOOK_DISABLED_001 and providers retry on their exponential schedule. Per Curaway dual-env flag policy this MUST flip in BOTH Production and Development envs together at cutover (planned PR-10). Commerce PR-6
commerce_receipts_enabled false Commerce Gate auto-generation of receipts on commerce.payment_intent.captured webhook events. When false the webhook hook is a no-op; manual ReceiptService.generate_receipt calls (admin routers, scheduled jobs) still work. Per Curaway dual-env flag policy this MUST flip in BOTH Production and Development envs together at cutover. Commerce PR-7
commerce_consultation_charges_enabled false Commerce Gate the consultation-charge strangler cutover (spec §13 PR-9). When false app/services/teleconsultation_payments.py serves 100% of traffic. When true TeleconsultationService routes authorize/capture/cancel through commerce IntentService. Reconciliation test asserts identical net ledger position for a fixture set; PR is blocked on diff = 0. Checked per-tenant so a single tenant can be dark-launched before fleet-wide cutover. Per Curaway dual-env flag policy this MUST flip in BOTH Production and Development envs together. Commerce PR-9
commerce_admin_ui_enabled false Commerce Gate the admin commerce surface (app/routers/admin_commerce.py, 12 endpoints backing spec §11.1..§11.8). When false every route returns 503 COMMERCE_ADMIN_DISABLED_001. Flip to true in BOTH Production and Development envs once apps/admin-app/src/pages/commerce/ ships (FE PR-8). Per Curaway dual-env flag policy this MUST flip in both envs together. Commerce PR-8

Flags Added in Procedure Onboarding Admin PR-1 (#857)

Backs the admin Procedures page + per-facility readiness grid (spec docs/specs/procedure-onboarding-admin-ui.md §9.1). Default OFF in both Production and Development. When the flag is off every /api/v1/admin/procedures/* route returns 503 with error_code PROCEDURE_ADMIN_DISABLED_001 so the admin SPA can grey-out the menu entry. Per Curaway dual-env flag policy flip in BOTH envs together.

Flag Default Category Description Session
procedure_admin_ui_enabled false Admin Gate the /api/v1/admin/procedures/* admin surface (procedure catalog + per-facility readiness cells). When false every admin-procedure route returns 503 PROCEDURE_ADMIN_DISABLED_001. Flip on once UI ships (PR-2). Procedure Admin PR-1
flagsmith_admin_ui_enabled true Admin Gate the Flagsmith dual-env admin UI endpoints (/admin/flags/dual-env, /drift, /stale, /yaml-defaults, /history). When false new endpoints return 503; existing single-env endpoints stay active. Enabled by default post-launch; flip OFF in both envs together to take the UI down. 95
transport_admin_ui_enabled true Admin Gate the admin transport vendor catalog UI (/transport-vendors) and its supporting /api/v1/admin/transport/* endpoints. When false the page renders an "admin not available" card. Enabled by default; flip OFF in both envs together to disable. 95