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:#fffComponent Overview
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:
- Lazy init — client on first use
- Exponential backoff —
2^attemptonRateLimit/APIConnection - Streaming default —
TEXT_DELTA,TOOL_CALL_START/DELTA/COMPLETE,MESSAGE_COMPLETE(Shiki-highlighted code blocks) - Thought signatures — preserves
extra_content.google.thought_signaturefor Gemini 3 strict tool calling
4. Tool Registry — tools/registry.py
Single source for built-in, MCP, custom, and sub-agent tools.
- Two-tier lookup —
_toolsvs_mcp_tools - Allowed tools — whitelist via config
- Validation — Pydantic schema → errors before exec
- Approval — delegates to
ApprovalManager
5. Safety & Approval — safety/approval.py
Multi-layered protection:
- Pattern detection —
rm -rf /, fork bomb,mkfs - Safe whitelist —
ls,git statusauto-approved - Path validation — CWD escape → confirm
- 6 policies —
on-request(default) …yolo
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 resultsEvent System
Lifecycle is fully event-driven. AgentEvent covers the contract:
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
| Principle | Rationale |
|---|---|
| Async everywhere | Agent loop, tools, hooks — avoids blocking the TUI |
| Event-driven | Decoupled, testable, hookable at every stage |
| Lazy init | LLM & MCP created on first use → faster --help, config changes before connect |
| Multi-level config | System → project → CLI, later overrides earlier |
| Safety by default | on-request requires confirmation for mutating shell/write |
Next
See Core Components for API details and Execution Flow for the full sequence.