How OpenClaude Manages Agent Colors in the UI: Theme-Driven Color Architecture

OpenClaude assigns each agent type a distinct color by mapping identifiers through getAgentColor() in agentColorManager.ts, resolving them against a centralized Theme object, and injecting the resulting values into Ink-based UI components.

The OpenClaude terminal interface uses a sophisticated color management system to visually distinguish between different AI agents and sub-agents. By decoupling agent type identifiers from actual color values through a theme-driven lookup layer, the codebase maintains consistent visual identity while supporting dynamic agent registration.

The Agent Color Architecture

The color resolution pipeline follows a three-tier lookup strategy. First, the system maps a string identifier (e.g., "claude" or "planMode") to a theme key via the AgentColorMap. Second, the theme key resolves to an actual color value (ANSI or RGB) defined in the Theme object. Third, Ink components consume these values to render colored backgrounds, borders, and text.

This architecture ensures that agent colors remain consistent across progress indicators, swarm banners, and tool output displays while allowing the entire palette to shift based on the active theme.

Core Implementation Files

agentColorManager.ts: The Primary Lookup Interface

The src/tools/AgentTool/agentColorManager.ts file exports the main entry point for color retrieval. The getAgentColor(agentType) function performs the initial mapping from agent identifier to theme key.

import { getAgentColorMap } from '../../bootstrap/state.js';

export function getAgentColor(agentType: string): AgentColorName | undefined {
  const colorMap = getAgentColorMap();
  return colorMap.get(agentType);
}

This function returns values like "purple_FOR_SUBAGENTS_ONLY" for the "claude" agent type or "planMode" for planning agents, which serve as keys into the Theme object rather than raw color values.

state.ts: The AgentColorMap Registry

The src/bootstrap/state.ts file maintains the AgentColorMap as a Map<string, AgentColorName> populated during application initialization. The getAgentColorMap() accessor exposes this registry to the color manager.

The map is populated at startup with predefined mappings such as:

  • "claude" → "purple_FOR_SUBAGENTS_ONLY"
  • "planMode" → "planMode" (self-referencing key)

When new sub-agent types register themselves (such as custom skills defining unique subagent_type values), they inject entries into this map, enabling immediate color availability throughout the UI without component updates.

theme.ts: Color Value Resolution

The src/utils/theme.ts file defines the Theme interface and concrete implementations containing the Agent colors section. This section maps the logical color names to actual display values:

const agentColors = {
  claude: 'purple_FOR_SUBAGENTS_ONLY',
  planMode: 'rgb(51,102,102)',
  // Additional agent-specific mappings
};

The helper function color(colorKey, themeName) transforms these keys into ANSI escape sequences that the Ink rendering engine can process, separating the logical color identity from the actual visual representation.

UI Integration Points

Agent Progress Lines

The src/components/AgentProgressLine.tsx component receives resolved colors via the descriptionColor prop. It determines whether to apply a custom color based on the subagent type:

const descriptionColor = isCustomSubagentType(subagentType)
  ? getAgentColor(subagentType) as keyof Theme | undefined
  : undefined;

This allows visual differentiation of custom sub-agents while maintaining default styling for standard agent types.

Swarm Banner Backgrounds

In src/components/PromptInput/useSwarmBanner.ts, the system uses getAgentColor to determine the background color for swarm operation banners:

bgColor: getAgentColor(task.agentType) ?? 'cyan_FOR_SUBAGENTS_ONLY',

This provides immediate visual context for which agent type is currently managing the swarm, falling back to a default cyan color for unregistered agent types.

Tool Display Components

The src/tools/AgentTool/AgentTool.tsx file imports getAgentColor and propagates color information through the component tree:

<AgentPromptDisplay prompt={prompt} theme={theme} />
<AgentResponseDisplay content={content} theme={theme} />

While the display components receive the theme context, the color resolution typically occurs upstream in progress indicators or container components that manage the agent lifecycle visualization.

Dynamic Color Registration

The system supports runtime color registration for extensibility. When a new skill or sub-agent type initializes, it can register itself with the AgentColorMap during the bootstrap phase. This registration pattern ensures that:

  • Custom agents receive consistent visual treatment immediately upon registration
  • The UI requires no manual updates to support new agent types
  • Color assignments persist for the session duration through the centralized state

Practical Implementation Examples

Retrieving an Agent Color

import { getAgentColor } from './src/tools/AgentTool/agentColorManager.js';

// Resolve color key for planning operations
const planColorKey = getAgentColor('planMode');
// Returns: 'planMode' (which maps to rgb(51,102,102) in the theme)

Rendering Colored Progress Indicators

import { AgentProgressLine } from './src/components/AgentProgressLine.js';
import { getAgentColor } from './src/tools/AgentTool/agentColorManager.js';

function renderAgentStatus(agentType: string, description: string) {
  const colorKey = getAgentColor(agentType) as keyof Theme | undefined;
  
  return (
    <AgentProgressLine
      agentType={agentType}
      description={description}
      descriptionColor={colorKey}
    />
  );
}

Implementing Color-Aware Banner Components

import { getAgentColor } from './src/tools/AgentTool/agentColorManager.js';
import { Box, Text } from 'ink';

function AgentBanner({ agentType, message }: { agentType: string; message: string }) {
  const backgroundColor = getAgentColor(agentType) ?? 'cyan_FOR_SUBAGENTS_ONLY';
  
  return (
    <Box backgroundColor={backgroundColor} paddingX={1}>
      <Text>{message}</Text>
    </Box>
  );
}

Summary

  • OpenClaude uses a three-layer color architecture: Agent type identifiers map to theme keys via AgentColorMap, which resolve to actual color values in the Theme object.
  • getAgentColor() in agentColorManager.ts serves as the primary API for retrieving agent-specific color keys throughout the application.
  • AgentColorMap in state.ts maintains runtime registrations, allowing dynamic addition of new agent types without UI modifications.
  • Theme resolution happens in theme.ts, converting logical keys like "purple_FOR_SUBAGENTS_ONLY" into renderable ANSI/RGB values.
  • UI components such as AgentProgressLine and useSwarmBanner consume these colors to provide visual differentiation of agent activities in the terminal interface.

Frequently Asked Questions

How does OpenClaude assign colors to new agent types?

New agent types register themselves with the AgentColorMap during initialization by calling the registration functions exposed from src/bootstrap/state.ts. This injects a mapping from the new agent type string to a theme color key, making the color immediately available to all UI components without requiring recompilation or manual theme updates.

What happens if an agent type doesn't have a registered color?

Components fall back to default colors defined locally. For example, useSwarmBanner.ts uses the nullish coalescing operator (??) to default to 'cyan_FOR_SUBAGENTS_ONLY' when getAgentColor() returns undefined for an unregistered agent type.

Can agent colors be customized per theme?

Yes. Because getAgentColor() returns theme keys (like "purple_FOR_SUBAGENTS_ONLY") rather than hardcoded values, different Theme implementations in src/utils/theme.ts can map the same key to different color values. Switching themes automatically updates all agent colors across the interface.

Where is the color actually converted to ANSI escape codes?

The conversion from theme keys to displayable ANSI escape sequences occurs in the color utility functions within src/utils/theme.ts. The color() helper function takes the key returned by getAgentColor() and the current theme name, then returns the appropriate escape sequence for the Ink rendering engine to process.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →