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)
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.
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
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
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".
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
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)
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
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
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
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.
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.
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.
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().
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.
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 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:
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).
Direct query to
api.flagsmith.com/api/v1/environments/{env_key}/identities/?q={identifier}
with the admin token (bypasses every SDK cache layer).
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.
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.