feature-flag-engine: A Terminal-First Control Plane for Feature Flags and Remote Config

A clean-architecture Python stack that combines deterministic rollouts, live WebSocket updates, and a Textual dashboard to manage app behavior without SaaS lock-in.

8 to 10 min read • View on GitHub • More from swara6822

A terminal operator sits at the center of a small control room while flag state fans out through thin network lines to multiple clients, including a phone app. It shows feature management as infrastructure control, not a browser dashboard.
The project’s strongest opinion is visible immediately: the terminal is the control surface, and one toggle can ripple across every connected client.
Key Takeaways

The Control Room Lives in the Terminal

The most opinionated choice in feature-flag-engine is not the rollout math. It is the interface. Instead of a browser dashboard, the project puts operators in a Textual terminal, where keyboard shortcuts and async actions replace mouse-heavy admin work.

That matters because feature flags are not a one-time setup. They are an operating surface. If the tool is built for fast toggles, quick edits, and immediate feedback, the terminal becomes a serious advantage rather than a nostalgic flourish.

# dashboard/app.py style interaction
# Typical bindings in the TUI
bindings = {
    "space": "toggle_flag",
    "e": "edit_flag",
    "r": "refresh_state",
}

# Actions map directly to async API calls
# while WebSocket updates keep the view current.

The architectural signal is clear. This is not a demo wrapped around an admin panel. It is a control room built for people who want to move fast from the keyboard.

The Real Trick: Rollouts Without State

The engine’s core evaluation flow is simple and surprisingly disciplined. First it checks whether the flag is globally enabled. Then it checks segment membership. Only after those gates does it fall through to percentage rollout, where a deterministic hash turns a user and flag key into a stable bucket.

A deterministic rollout pipeline keeps assignment stable without storing per-user state. The same user returns to the same bucket, so the system stays predictable across repeated requests.

A close-up machine sorts a user ID and feature key through a hash wheel labeled 0 to 99, then a gate opens only for buckets under a rollout threshold. It explains how percentage rollout can stay consistent without saving assignment state.
The clever part is the combination of simplicity and consistency. Hash once, compare once, and the same user keeps landing in the same bucket.

That is the elegant part of the design. The backend does not need to remember which users were assigned where. The hash function does the remembering for it, which keeps rollout behavior predictable across repeated requests and across instances.

import hashlib

def bucket(flag_key: str, user_id: str) -> int:
    digest = hashlib.md5(f"{flag_key}:{user_id}".encode()).hexdigest()
    return int(digest, 16) % 100


def evaluate(flag, user):
    if not flag.enabled:
        return False
    if flag.target_segments and user.segment not in flag.target_segments:
        return False
    return bucket(flag.key, user.id) < flag.rollout_percentage

Why the Broadcast Layer Matters

Deterministic evaluation is only half the story. The rest is distribution. The backend keeps active WebSocket connections in a manager, and when a flag changes, the mutation is broadcast immediately to connected clients.

That turns the engine from a static API into a live control plane. A toggle in one place becomes visible everywhere else without waiting for a refresh cycle or a redeploy. Dead sockets get cleaned up during failed sends, which keeps the broadcast list from rotting over time.

Clean Architecture Keeps the Flag Logic Pure

The repository is organized like a system that expects to grow. Domain entities stay separate from application use cases, repositories handle persistence, and delivery concerns live at the edge in FastAPI, WebSockets, and the TUI.

That split is not academic. It makes the feature logic testable without the database, keeps the persistence layer swappable, and prevents the rollout engine from becoming tangled with transport code.

LayerResponsibilityWhy it helps
DomainFeatureFlag, audit records, and repository interfacesKeeps business rules pure and easy to test
ApplicationEvaluation and mutation use casesCentralizes rollout logic and operator workflows
InfrastructureSQLAlchemy repositories and JSON storageHandles persistence without leaking ORM details
DeliveryFastAPI, WebSockets, Textual dashboardLets different clients share the same engine

The storage choice is pragmatic too. Segment lists can live as JSON, which keeps the model simple without forcing a join table for every small targeting list.

Audit Logs Turn Every Toggle Into a Record

Audit logging is the feature that moves this from hobby project to operations tool. Every mutation writes old and new values, so the system keeps a paper trail of what changed and when.

CapabilitySimple flag toolfeature-flag-engine
Mutation historyOften omitted or implicitExplicit audit log with old and new values
Operator trustHarder to reconstruct after the factEach change is traceable
Production readinessDepends on external toolingBuilt into the control plane
Rollback confidenceManual and uncertainFaster to inspect and reason about

That matters because flags tend to become blame magnets. When an incident happens, a good audit trail turns a guessing game into a reviewable sequence of events.

What It Is Competing Against

The right comparison is not a checklist of features. It is a philosophy of operation. Unleash, Flagsmith, Flipt, and GrowthBook are broader platforms with browser-first workflows and mature ecosystems. This repo is narrower, more terminal-centric, and more explicit about deterministic evaluation plus live propagation.

ProjectAdmin experienceRollout modelReal-time syncSelf-hostingDistinctive angle
feature-flag-engineTerminal-first Textual dashboardDeterministic hashing plus segmentsWebSocket fan-outYesCompact control plane with clean boundaries
UnleashBrowser-firstAdvanced targeting and rollout rulesAvailable through integrationsYesBroad enterprise feature management
FlagsmithBrowser-firstTargeting and environmentsSupportedYesProduct-focused flag management
FliptWeb UI and APISimple flag evaluationSupportedYesLean self-hosted flag service
GrowthBookBrowser-firstFlags plus experimentationSupportedYesExperimentation-led feature management

That positioning is the point. This repo is not trying to out-platform the incumbents. It is trying to make the control plane smaller, clearer, and easier to reason about.