Feature flags lab
Implement one traceable happy path
Build a FastAPI RAG endpoint whose new index can be dark-launched, exposed to 5%, or disabled without a deploy as an operable release, not a slide-deck example.
1Try it yourself
Playground
Feature flag lab
Flags control who sees what — without redeploying code.
New RAG index for 5% of traffic
Before you start
Why this matters
Before changing code, write the single production outcome this chapter must prove and the signal that would stop you. For this lab, the service boundary is FlagContext(flag_key, user_id, tenant_id, environment) returning FlagDecision(enabled, variant, reason, config_version). Record one request identifier you can follow from ingress through the final decision. If you cannot name the owner of the stop decision, the rollout is not yet controlled.
The source lesson separates release from exposure and requires every trace to carry the evaluated variant. This chapter turns that compact lesson into implementation evidence. The running scenario is a FastAPI RAG endpoint whose new index can be dark-launched, exposed to 5%, or disabled without a deploy. You will keep the same scenario across all eight chapters so setup decisions, tests, telemetry, and rollback controls accumulate into one coherent system rather than eight disconnected exercises.
2Learn the idea
Read
Build the vertical slice
Implement the smallest real flow before adding retries or optimization. Keep orchestration explicit so each decision can be tested and traced:
decision = flags.evaluate("new_rag_index", {
"user_id": user.id, "tenant_id": user.tenant_id, "environment": ENV
})
retriever = index_v2 if decision.variant == "v2" else index_v1
trace.set_attributes({"flag.new_rag_index": decision.variant,
"flag.config_version": decision.config_version})
The handler should validate once, authorize before expensive work, and pass a structured context through each component. Record the release revision and configuration decision at the point they are made. Do not infer them later from deployment time; a request may cross a configuration update.
Read
Preserve causality
Use one correlation ID across the request, dependency calls, durable records, and outcome event. If the flow has an exposure or delivery ID, make it unique and immutable. Log a compact event with result category, duration, revision, and decision—not raw prompts, secrets, or complete customer objects. The resulting trace must answer: which code ran, which policy applied, what external facts were used, and what response class was returned?
Build one realistic fixture rather than a toy “hello world.” It should exercise the feature that justifies the release while remaining safe and deterministic. Assert the returned schema, cited or selected source identifiers, and side-effect count. Snapshot prose only when wording itself is contractual; otherwise assert structured facts so harmless model phrasing does not create brittle tests.
Read
Check operational behavior
Call the endpoint twice with the same stable identity or delivery identifier. The repeated call must preserve cohort or avoid duplicate work as required by this system. Restart the process and repeat the test; in-memory correctness is insufficient where state must survive deploys. Then inspect the trace to verify it includes the attributes needed to slice decision latency, evaluation-error rate, cohort error rate, and grounded-answer rate by variant.
Measure the local baseline now. Capture median and p95 duration over at least 100 fixture requests, dependency call count, and output-quality score on the golden set. This is not a capacity claim; it is a regression baseline. Save the command, machine assumptions, and revisions with the result.
Read
Keep scope disciplined
Do not add a dashboard, elaborate retry library, or generalized plugin system to make the happy path look production-ready. The chapter is complete when one valid request traverses real orchestration, emits attributable evidence, and produces the contracted result. The next chapters intentionally attack this slice. A narrow, visible implementation is easier to harden than a broad abstraction whose failure points are hidden.