# AutoGPT Feature Flag System: How Gradual Rollouts Work with LaunchDarkly

> Discover how AutoGPT leverages LaunchDarkly for gradual rollouts. Explore backend decorators and frontend React wrappers that manage feature flags and stream real-time updates.

- Repository: [AutoGPT/AutoGPT](https://github.com/Significant-Gravitas/AutoGPT)
- Tags: internals
- Published: 2026-02-24

---

**AutoGPT implements a comprehensive feature flag system using LaunchDarkly to enable gradual rollouts, with backend decorators and frontend React wrappers that evaluate flags per-request and stream real-time updates to users.**

The **feature flag system** in the Significant-Gravitas/AutoGPT repository allows developers to safely release functionality to specific user cohorts without deploying new code. Built on LaunchDarkly's SDK, the architecture spans both the FastAPI backend and the Next.js frontend, providing immediate toggles that default to safe fallback values when the service is unavailable.

## Backend Architecture and Flag Enum

The backend implementation centers on a single source of truth for all flag keys defined in [`autogpt_platform/backend/backend/util/feature_flag.py`](https://github.com/Significant-Gravitas/AutoGPT/blob/main/autogpt_platform/backend/backend/util/feature_flag.py). This file contains the **Flag** enum, which guarantees consistency across the application by centralizing every LaunchDarkly key identifier.

### LaunchDarkly Client Lifecycle

The system manages the LaunchDarkly client through explicit lifecycle functions: `initialize_launchdarkly()`, `get_client()`, and `shutdown_launchdarkly()`. These utilities implement a **singleton pattern** with lazy initialization, ensuring the client starts only when first needed and shuts down gracefully during application termination.

If the SDK key (`settings.secrets.launch_darkly_sdk_key`) is missing or the client fails to initialize, the system operates in **degraded mode**, automatically returning the `default` value supplied to any flag check—typically `False` to keep features disabled.

### User Context and Targeting

For precise gradual rollouts, the system builds rich user contexts via `_fetch_user_context_data()`. This function queries Supabase to enrich the LaunchDarkly context with attributes including user role, email, domain, and anonymity status. To avoid repeated database calls, the context is **cached for 24 hours** per user.

Flag evaluation occurs through `is_feature_enabled(flag, user_id, default)`, which returns a boolean, or `get_feature_flag_value()` for raw flag data (including string or numeric variations). When LaunchDarkly evaluates a flag, it checks if the user's context matches the current rollout percentage or targeting rules defined in the LaunchDarkly dashboard.

### Route Protection and Dependencies

The system offers two declarative approaches to protect FastAPI endpoints:

- **`feature_flag()` decorator** – Wraps route handlers to check the flag before execution. If disabled, it raises `HTTPException(status_code=404)`.
- **`create_feature_flag_dependency()`** – A factory function that returns a FastAPI `Depends()` callable, allowing entire routers to require a specific flag.

Both methods evaluate the flag **per request**, meaning changes to rollout percentages in the LaunchDarkly UI take effect instantly for subsequent API calls without requiring a server restart.

## Frontend Implementation with React

The frontend leverages `launchdarkly-react-client-sdk` to stream flag states to the browser in real time.

### Higher-Order Component Pattern

The `withFeatureFlag` higher-order component, located in [`autogpt_platform/frontend/src/services/feature-flags/with-feature-flag.tsx`](https://github.com/Significant-Gravitas/AutoGPT/blob/main/autogpt_platform/frontend/src/services/feature-flags/with-feature-flag.tsx), provides declarative UI gating. It wraps any React component and automatically redirects to `/404` when the specified flag is disabled.

```typescript
// Usage in a Next.js page
import { withFeatureFlag } from "@/services/feature-flags/with-feature-flag";
import ChatPage from "./ChatPage";

export default withFeatureFlag(ChatPage, "chat");

```

The HOC waits for `hasFlagLoaded` to prevent flickering, then checks `flags["chat"]` from the `useFlags()` hook. Because LaunchDarkly pushes updates over a WebSocket connection, users see features appear or disappear instantly when rollout percentages change.

### Direct Flag Checks

Components can also access flags directly for conditional rendering:

```typescript
import { useFlags } from "launchdarkly-react-client-sdk";

export function NewUI() {
  const flags = useFlags();
  if (!flags["beta-blocks"]) return null;
  return <div>Experimental UI</div>;
}

```

## Gradual Rollout Execution Flow

The **feature flag system** enables safe, staged releases through the following automated flow:

1. **Configuration** – The application initializes the LaunchDarkly client using the SDK key from environment secrets.
2. **User Identification** – When a protected endpoint receives traffic, the system extracts the `user_id` from the JWT and constructs a cached LaunchDarkly Context containing Supabase user attributes.
3. **Evaluation** – LaunchDarkly compares the user's context against active rollout rules (percentage rollouts, role-based targeting, or email domain lists).
4. **Decision** – The system returns `True` or `False`. If `False`, the backend returns HTTP 404 or the frontend redirects to the 404 page.
5. **Real-time Updates** – Changing a flag's rollout percentage in the LaunchDarkly dashboard immediately affects new requests and streams updates to open browser sessions.

## Testing and Mobile Considerations

For unit testing, the `mock_flag_variation` context manager allows developers to temporarily override flag values without a LaunchDarkly connection:

```python
from backend.util.feature_flag import mock_flag_variation

async def test_chat_disabled(client):
    with mock_flag_variation("chat", False):
        response = await client.get("/chat", headers=auth_header)
        assert response.status_code == 404

```

The **classic mobile client** uses a different approach. Located in `classic/frontend/lib/utils/feature_flags.dart`, the mobile implementation relies on static compile-time constants like `userExperienceIterationTwoEnabled`. These require a new app build to change, meaning mobile rollouts are manual rather than dynamic, with most feature gating deferred to backend flag evaluation.

## Summary

- The **feature flag system** uses a centralized `Flag` enum in [`feature_flag.py`](https://github.com/Significant-Gravitas/AutoGPT/blob/main/feature_flag.py) to ensure backend and frontend use identical keys.
- LaunchDarkly contexts are enriched with Supabase user data and cached for 24 hours to optimize performance while enabling sophisticated targeting.
- Backend routes are protected via decorators or FastAPI dependencies that return HTTP 404 when flags are disabled.
- The React frontend streams flag states via WebSocket, allowing `withFeatureFlag` to hide or show UI instantly as rollout percentages change.
- Graceful degradation ensures features remain disabled if LaunchDarkly is unavailable, and `mock_flag_variation` facilitates testing without external service dependencies.

## Frequently Asked Questions

### How does AutoGPT handle feature flag evaluation if LaunchDarkly is offline?

If the LaunchDarkly client fails to initialize or the SDK key is missing, the system falls back to the `default` value provided to `is_feature_enabled()`. Typically set to `False`, this ensures features stay disabled safely rather than causing application errors or exposing unfinished functionality.

### Can I target specific user groups for gradual rollouts instead of random percentages?

Yes. The `_fetch_user_context_data()` function pulls role, email domain, and other attributes from Supabase into the LaunchDarkly context. This allows you to configure rules in the LaunchDarkly dashboard targeting specific user cohorts, such as beta testers or internal staff, while maintaining random percentage rollouts for general users.

### Why does the backend use HTTP 404 when a feature flag is disabled?

The `feature_flag` decorator and dependency factory raise `HTTPException(status_code=404)` to maintain security through obscurity. Returning a 404 rather than 403 or a "feature disabled" message prevents attackers from discovering hidden endpoints and makes disabled features indistinguishable from non-existent routes.

### How do I test code that depends on a feature flag being enabled?

Use the `mock_flag_variation` context manager in your test suite. This utility temporarily overrides the flag value for the duration of the test context, allowing you to verify both enabled and disabled states without requiring a live LaunchDarkly connection or modifying environment variables.