Flux-CLI

Core Components

Flux-CLI is built from several core components that work together to create a powerful AI coding agent.

1. LLM Client (client/llm_client.py)

The LLM Client is a wrapper around the AsyncOpenAI client that provides:

LLM Client Interface

The main interface for LLM interactions.

class LLMClient:
  async def chat_completion(
      self,
      messages: list[dict],
      tools: list[dict] | None = None,
      stream: bool = True,
  ) -> AsyncGenerator[StreamEvent, None]:
      # Build kwargs with model, messages, stream
      # If tools are provided, convert to OpenAI function schemas
      # Handle retries with exponential backoff
      # Yield stream events (TEXT_DELTA, TOOL_CALL_*, MESSAGE_COMPLETE)
      ...

Retry Strategy

Error TypeRetry Behavior
RateLimitErrorRetry 3 times with 2^attempt exponential backoff
APIConnectionErrorRetry 3 times with 2^attempt exponential backoff
APIErrorFail immediately, no retry

Why Lazy Initialization?

The OpenAI client is not created until the first API call to:

  1. Avoid unnecessary API key validation at startup
  2. Allow configuration changes before the first API call
  3. Reduce startup time — Creating the client is fast, but not needed if just showing help

2. Agent Engine (agent/agent.py)

The Agent Engine is the core orchestrator. It implements the multi-turn agentic loop.

Agent Loop Structure

The agent loop runs for max_turns, handling tool calls each turn.

class Agent:
  async def _agentic_loop(self) -> AsyncGenerator[AgentEvent, None]:
      for turn_num in range(max_turns):
          # 1. Check context compression
          # 2. Get tool schemas
          # 3. Call LLM with streaming
          # 4. Collect tool calls
          # 5. Execute tools (with approval)
          # 6. Check loop detection
          # 7. Update context
          ...

Why Async Generator?

The agent uses async for event in agent.run(message): instead of returning a complete response. This design choice:

  1. Enables real-time streaming — Users see tokens as they're generated
  2. Allows incremental UI — The TUI can render tool calls as they happen
  3. Supports cancellation — The loop can be interrupted gracefully
  4. Provides visibility — Every step of the agent's reasoning is visible

3. Context Manager (context/manager.py)

The Context Manager handles conversation history, token tracking, and automatic compression.

Context Manager Interface

The context manager maintains conversation history with automatic compression.

class ContextManager:
  def add_user_message(self, content: str) -> None
  def add_assistant_message(self, content: str, tool_calls: list) -> None
  def add_tool_result(self, tool_call_id: str, content: str) -> None
  def get_messages(self) -> list[dict]
  def needs_compression(self) -> bool
  def replace_with_summary(self, summary: str) -> None
  def prune_tool_outputs(self) -> int
  def clear(self) -> None

Compression Trigger

Compression is triggered when the total token count exceeds 80% of the context window:

def needs_compression(self) -> bool:
    context_limit = self.config.model.context_window
    current_tokens = self._count_tokens()
    return current_tokens > (context_limit * 0.8)

Why 80%?

The 80% threshold is a deliberate design choice:

  1. Buffer room — Leaves room for the response and tool calls
  2. Avoids hitting limits — Prevents context window overflow during a long response
  3. Proactive not reactive — Compresses before it's needed, not after the window is full

4. Tool Registry (tools/registry.py)

The Tool Registry manages all tools and handles invocation with validation and approval.

Tool Registry Interface

The registry manages all tools and handles invocation with validation.

class ToolRegistry:
  def register(self, tool: Tools) -> None
  def register_mcp_tool(self, tool: Tools) -> None
  def get(self, name: str) -> Tools | None
  def get_tools(self) -> list[Tools]
  def get_schemas(self) -> list[dict]
  async def invoke(self, name, params, cwd, hook_system, approval_manager) -> ToolResult

5. Safety & Approval (safety/approval.py)

The safety system implements multi-layered protection:

class ApprovalPolicy(str, Enum):
    ON_REQUEST = "on-request"    # Default: ask for confirmation
    ON_FAILURE = "on-failure"    # Auto-approve, but ask on failure
    AUTO = "auto"                # Auto-approve all
    AUTO_EDIT = "auto-edit"      # Auto-approve safe, confirm edits
    NEVER = "never"              # Never auto-approve
    YOLO = "yolo"                # Approve everything

Command Safety Detection

DANGEROUS_PATTERNS = [
    r"rm\s+(-rf?|--recursive)\s+[/~]",  # rm -rf /
    r"dd\s+if=",                         # Disk destroyer
    r"mkfs",                             # Format filesystem
    r":\(\)\s*\{\s*:\|:&\s*\}\s*;",     # Fork bomb
    ...
]

SAFE_PATTERNS = [
    r"^(ls|dir|pwd|cd|echo|cat)(\s|$)",  # Info commands
    r"^git\s+(status|log|diff|show)(\s|$)",  # Git read-only
    ...
]

6. Hook System (hooks/hook_system.py)

The hook system executes shell commands at specific lifecycle events.

Hook System Interface

The hook system triggers shell commands at lifecycle events.

class HookSystem:
  async def trigger_before_agent(self, user_message: str)
  async def trigger_after_agent(self, user_message: str, agent_response: str | None)
  async def trigger_before_tool(self, tool_name: str, tool_params: dict)
  async def trigger_after_tool(self, tool_name: str, tool_params: dict, tool_result: ToolResult)
  async def trigger_on_error(self, error: Exception)

7. Terminal UI (ui/tui.py)

The TUI engine provides a Rich-powered interface with:

Theme Colors

The TUI uses a consistent color palette derived from the ASCII logo:

#e7aafb (lavender pink)  →  tool names, warnings
#a191f8 (slate blue)     →  user input, highlights
#8bcefc (sky blue)       →  info, read tools
#7fe4eb (cyan)           →  success, network tools

On this page