Flux-CLI

Architecture

Event-driven, multi-tiered — LLM, tools, hooks, safety, and TUI working in concert.

Flux-CLI is built as an event-driven, layered system that orchestrates streaming LLM calls, multi-tool execution, lifecycle hooks, safety approvals, and terminal rendering.

At a glance

CLI (Click + Rich) → Agent → Context / Tools / LLM Client → Hooks / Safety / TUI — all async, all event-driven.

High-Level Architecture

graph TB
    CLI[CLI Entry Point<br/>main.py] --> AGENT[Agent Engine<br/>agent/agent.py]
    AGENT --> HOOK[Hook System<br/>hooks/hook_system.py]
    AGENT --> CONTEXT[Context Engine<br/>context/manager.py]
    AGENT --> TOOLS[Tool Registry<br/>tools/registry.py]
    AGENT --> LLM[LLM Client<br/>client/llm_client.py]
    
    TOOLS --> BUILTIN[Built-in Tools<br/>tools/builtin/]
    TOOLS --> MCP[MCP Tools<br/>tools/mcp/]
    TOOLS --> SUBAGENT[Sub-Agents<br/>tools/subagent.py]
    TOOLS --> DISCOVERY[Custom Discovery<br/>tools/discovery.py]
    
    CONTEXT --> COMPACT[Chat Compactor<br/>context/compaction.py]
    CONTEXT --> LOOP[Loop Detector<br/>context/loop_detector.py]
    
    LLM --> API[LLM Provider API<br/>OpenAI-compatible]
    
    HOOK --> SHELL[Shell Commands<br/>Hooks]
    
    subgraph "Safety Layer"
        SAFETY[Approval Manager<br/>safety/approval.py]
        TOOLS --> SAFETY
    end
    
    subgraph "UI Layer"
        TUI[Terminal UI<br/>ui/tui.py]
        CLI --> TUI
    end
    
    style AGENT fill:#a191f8,stroke:#8bcefc,color:#fff
    style CLI fill:#e7aafb,stroke:#8bcefc,color:#fff
    style LLM fill:#8bcefc,stroke:#7fe4eb,color:#fff
    style TOOLS fill:#7fe4eb,stroke:#8bcefc,color:#fff

Component Overview

main.py
CLI Entry Point
Click + Rich · REPL + slash commands
agent/agent.py
Agent Engine
Async generator · multi-turn loop
client/llm_client.py
LLM Client
AsyncOpenAI · streaming · retries
tools/registry.py
Tool Registry
Built-in + MCP + discovery

1. CLI Entry Point — main.py

Click for parsing, Rich for rendering. Handles args, config loading, session lifecycle, REPL vs one-shot, and slash commands.

2. Agent Engine — agent/agent.py

Core orchestrator — multi-turn loop as async generator (async for event in agent.run()). Manages context compression, tool schema fetch, streaming LLM calls, approvals, hooks, and loop detection.

3. LLM Client — client/llm_client.py

Wrapper around AsyncOpenAI:

4. Tool Registry — tools/registry.py

Single source for built-in, MCP, custom, and sub-agent tools.

5. Safety & Approval — safety/approval.py

Multi-layered protection:

Data Flow

sequenceDiagram
    participant User
    participant CLI as CLI (main.py)
    participant Agent as Agent Engine<br/>agent.py
    participant LLM as LLM Client<br/>llm_client.py
    participant Registry as Tool Registry<br/>registry.py
    participant Tool as Tool<br/>execute()
    
    User->>CLI: Types prompt
    CLI->>Agent: run(message)
    Agent->>Agent: yield AGENT_START
    Agent->>Agent: Add user message to context
    
    loop For each turn (max_turns)
        Agent->>Agent: Check context compression
        Agent->>Registry: get_schemas()
        Registry-->>Agent: Tool schemas
        Agent->>LLM: chat_completion(messages, tools)
        
        loop For each stream event
            LLM-->>Agent: TEXT_DELTA
            Agent-->>CLI: yield TEXT_DELTA
            LLM-->>Agent: TOOL_CALL_START/DELTA/COMPLETE
        end
        
        LLM-->>Agent: MESSAGE_COMPLETE (usage)
        
        alt Tool calls received
            loop For each tool call
                Agent->>Agent: Check approval policy
                Agent->>Registry: invoke(name, params)
                Registry->>Tool: execute()
                Tool-->>Registry: ToolResult
                Registry-->>Agent: ToolResult
                Agent-->>CLI: yield TOOL_CALL_COMPLETE
            end
            Agent->>Agent: Add tool results to context
            Agent->>Agent: Check loop detection
        else No tool calls
            Agent->>Agent: Finalize response
            Agent-->>CLI: yield TEXT_COMPLETE
            Agent-->>CLI: yield AGENT_END
        end
    end
    
    CLI->>CLI: Render response via TUI
    CLI-->>User: Display results

Event System

Lifecycle is fully event-driven. AgentEvent covers the contract:

agent/events.py
class AgentEventType(Enum):
    AGENT_START        = "agent_start"
    AGENT_END          = "agent_end"
    AGENT_ERROR        = "agent_error"
    TEXT_DELTA         = "text_delta"
    TEXT_COMPLETE      = "text_complete"
    TOOL_CALL_START    = "tool_call_start"
    TOOL_CALL_COMPLETE = "tool_call_complete"

Why async generators?

async for event in agent.run(message): enables live streaming, incremental TUI updates, and graceful cancellation — every reasoning step stays visible.

Configuration Pipeline

flowchart LR
    A[System Config<br/>~/.config/flux-cli/config.toml] --> C[Merge Configs]
    B[Project Config<br/>.flux-cli/config.toml] --> C
    C --> D[Construct Pydantic Config]
    E[AGENT.md Files] --> D
    D --> F[Validated Config]
    F --> G[Agent Session]

Validated via Pydantic — API key presence, model temperature 0..2, MCP must have command xor url, hooks need command or script.

Key Design Decisions

PrincipleRationale
Async everywhereAgent loop, tools, hooks — avoids blocking the TUI
Event-drivenDecoupled, testable, hookable at every stage
Lazy initLLM & MCP created on first use → faster --help, config changes before connect
Multi-level configSystem → project → CLI, later overrides earlier
Safety by defaulton-request requires confirmation for mutating shell/write

Next

See Core Components for API details and Execution Flow for the full sequence.

On this page