# How OpenMAIC Manages Feature Flags: A Centralized TypeScript Pattern

> Learn how OpenMAIC manages feature flags using a centralized TypeScript pattern. Discover how predicate functions enable consistent feature gating across your application.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: architecture
- Published: 2026-09-10

---

**OpenMAIC controls optional functionality through a centralized feature-flags module located at [`lib/config/feature-flags.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/config/feature-flags.ts), where pure predicate functions evaluate environment variables at runtime to enable consistent feature gating across API routes, React components, and test suites.**

The **THU-MAIC/OpenMAIC** repository implements a strict separation between flag definition and consumption. Instead of scattering `process.env` checks throughout the application, the codebase relies on a single configuration module that exports boolean predicates. This architecture ensures that features like agent runtime, AI chat interfaces, and advanced editor renderers can be toggled atomically via environment variables while maintaining type safety and testability.

## Centralized Configuration in lib/config/feature-flags.ts

The feature flag system centers on [`lib/config/feature-flags.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/config/feature-flags.ts), which encapsulates all environment variable logic. This module exports pure functions that compare runtime environment values against expected activation strings, returning strict booleans that the rest of the application consumes.

```typescript
// lib/config/feature-flags.ts
export const isAgentRuntimeConfigured = (): boolean =>
  process.env.MAIC_AGENT_RUNTIME === 'enabled';

export const isProWorkbenchEnabled = (): boolean =>
  process.env.MAIC_PRO_WORKBENCH === 'true';

export const isPiChatEnabled = (): boolean =>
  process.env.MAIC_PI_CHAT === 'true';

export const isEditorRendererEnabled = (): boolean =>
  process.env.MAIC_EDITOR_RENDERER === 'true';

```

Each predicate is a zero-arity function that reads `process.env` at invocation time. This lazy evaluation ensures that flags respond to runtime environment changes during server-side rendering or API execution without requiring application restarts in containerized environments.

## Server-Side Usage Patterns

API routes use these predicates to conditionally expose endpoints or abort requests early when dependencies are unavailable. In [`app/api/stages/route.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/app/api/stages/route.ts), the application guards agent-specific functionality by checking `isAgentRuntimeConfigured()` before executing heavy operations.

```typescript
// app/api/stages/route.ts
import { isAgentRuntimeConfigured } from '@/lib/config/feature-flags';

export async function GET(req: Request) {
  if (!isAgentRuntimeConfigured()) {
    return new Response('Agent runtime not configured', { status: 404 });
  }
  
  // Proceed with agent runtime logic...
  const stages = await fetchAgentStages();
  return Response.json(stages);
}

```

This pattern prevents uninitialized subsystems from receiving traffic, reducing error rates in deployments where specific microservices or experimental features remain disabled.

## Client-Side Implementation

React components and custom hooks import the same predicates to branch between feature variants. The slide editor surface in [`components/edit/surfaces/slide/use-slide-surface.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/components/edit/surfaces/slide/use-slide-surface.ts) evaluates `isEditorRendererEnabled()` to determine whether to mount the new renderer or fall back to legacy implementations.

```typescript
// components/edit/surfaces/slide/use-slide-surface.ts
import { isEditorRendererEnabled } from '@/lib/config/feature-flags';

export function useSlideSurface() {
  const useNewRenderer = isEditorRendererEnabled();
  
  return {
    renderer: useNewRenderer ? 'modern' : 'legacy',
    // ... other surface configuration
  };
}

```

For UI gating, components can conditionally render entire feature blocks:

```tsx
// components/chat/ChatBox.tsx
import { isPiChatEnabled } from '@/lib/config/feature-flags';
import { PiChat } from './PiChat';
import { LegacyChat } from './LegacyChat';

export function ChatBox() {
  return isPiChatEnabled() ? <PiChat /> : <LegacyChat />;
}

```

## Testing Strategy with Mocked Predicates

The test suite leverages Vitest's module mocking to verify behavior under different flag states without modifying actual environment variables. By mocking `@/lib/config/feature-flags` directly, tests achieve deterministic execution paths for both enabled and disabled scenarios.

```typescript
// Example test file
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import { ChatBox } from '@/components/chat/ChatBox';

vi.mock('@/lib/config/feature-flags', () => ({
  isPiChatEnabled: () => true,
  isProWorkbenchEnabled: () => false,
}));

describe('ChatBox', () => {
  it('renders PiChat when feature flag is enabled', () => {
    render(<ChatBox />);
    expect(screen.getByTestId('pi-chat')).toBeInTheDocument();
  });
});

```

This mocking strategy extends to server-side tests, where API routes can be verified to return 404 responses when `isAgentRuntimeConfigured` returns `false`, ensuring that security and availability constraints function correctly regardless of deployment environment.

## Benefits of the OpenMAIC Feature Flag Architecture

Implementing flags as centralized predicates provides three critical advantages for the OpenMAIC codebase:

1.  **Consistency.** Every component and API route references the same source of truth for feature state, eliminating synchronization bugs where one module checks `process.env.MAIC_PI_CHAT` directly while another uses a different conditional.

2.  **Ease of Migration.** Toggling features requires updating only the corresponding environment variable or predicate default value. The entire application responds uniformly without requiring find-and-replace operations across dozens of files.

3.  **Deterministic Testing.** Pure predicate functions can be stubbed at the module boundary, allowing unit tests to exhaustively verify both code paths (enabled and disabled) without relying on complex environment setup or external configuration management.

## Summary

-   OpenMAIC manages feature flags through [`lib/config/feature-flags.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/config/feature-flags.ts), a centralized module exporting pure predicate functions.
-   Environment variables like `MAIC_AGENT_RUNTIME`, `MAIC_PRO_WORKBENCH`, `MAIC_PI_CHAT`, and `MAIC_EDITOR_RENDERER` are evaluated at runtime within these predicates.
-   Server routes in files like [`app/api/stages/route.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/app/api/stages/route.ts) use predicates to guard endpoints and return early for disabled features.
-   Frontend components import predicates to conditionally render modern features or legacy fallbacks.
-   The testing suite mocks the entire feature-flags module to simulate different states deterministically using `vi.mock('@/lib/config/feature-flags', ...)`.

## Frequently Asked Questions

### How do I add a new feature flag to OpenMAIC?

Create a new predicate function in [`lib/config/feature-flags.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/config/feature-flags.ts) that checks a unique environment variable. Export the function, then import it into any API routes, React components, or hooks that need to gate behavior. Remember to add corresponding test mocks in any test files covering the new feature path.

### Why does OpenMAIC use functions instead of constant exports for feature flags?

Using zero-arity functions instead of exported constants allows for **lazy evaluation** and easier mocking. Functions can be spied upon or completely replaced in test environments using `vi.mock`, whereas exported constants would require more complex module re-injection strategies. Additionally, functions can encapsulate logic beyond simple equality checks if flag evaluation becomes more complex in the future.

### Can feature flags be changed without restarting the OpenMAIC server?

In practice, most Node.js deployments cache `process.env` at startup. While the predicate functions technically read `process.env` at invocation time, standard containerized environments require a restart to propagate new environment variable values. For hot-swappable flags without redeployment, the architecture would need extension to support external configuration stores like Redis or feature flag services, which OpenMAIC does not currently implement according to the source code.

### How does OpenMAIC handle feature flag type safety?

The TypeScript compiler ensures that only exported predicates from [`lib/config/feature-flags.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/config/feature-flags.ts) are used throughout the application. By constraining flag checks to this module, the codebase maintains strict typing—predicates always return `boolean`, preventing truthy/falsy edge cases that might arise from direct string comparisons scattered across files.