Architecture Overview

Agent Air is a modular TUI framework for building terminal-based agents powered by large language models. The architecture follows a layered design with clear separation of concerns, using message-driven communication between components.

Layered Architecture

The system is organized into four distinct layers, each with specific responsibilities:

┌─────────────────────────────────────────────────────────────────┐
│                    TUI Application Layer                         │
│  App, Widgets, Commands, Key Handlers, Themes, Layouts          │
└────────────────────────────┬────────────────────────────────────┘
                             │ Channels (UiMessage)
┌────────────────────────────▼────────────────────────────────────┐
│                    Agent Infrastructure Layer                    │
│  AgentAir, InputRouter, Logger, Configuration                  │
└────────────────────────────┬────────────────────────────────────┘
                             │ Channels (ControllerInputPayload)
┌────────────────────────────▼────────────────────────────────────┐
│                    Controller Layer                              │
│  LLMController, SessionManager, ToolExecutor, Registries        │
└────────────────────────────┬────────────────────────────────────┘

┌────────────────────────────▼────────────────────────────────────┐
│                    Client Layer                                  │
│  LLMClient, Anthropic Provider, OpenAI Provider                 │
└─────────────────────────────────────────────────────────────────┘

TUI Application Layer

The topmost layer handles all user-facing concerns:

  • App: The main application struct that runs the terminal event loop
  • Widgets: Reusable UI components (ChatView, TextInput, StatusBar, panels)
  • Commands: Slash command system for user actions
  • Key Handlers: Keyboard input processing with customizable bindings
  • Themes: Visual styling with 45+ built-in themes
  • Layouts: Flexible widget arrangement templates

Agent Infrastructure Layer

This layer provides the orchestration and lifecycle management:

  • AgentAir: Central struct that initializes and coordinates all components
  • InputRouter: Routes messages from TUI to the controller
  • Logger: Tracing-based logging infrastructure
  • Configuration: YAML config loading and environment variable handling

Controller Layer

The controller layer manages LLM interactions and tool execution:

  • LLMController: Main event loop processing six channels concurrently
  • LLMSessionManager: Manages multiple concurrent LLM sessions
  • ToolExecutor: Executes tools in parallel with result aggregation
  • Registries: Tool, permission, and user interaction registries

Client Layer

The bottom layer handles HTTP communication with LLM providers:

  • LLMClient: Provider-agnostic client interface
  • Anthropic Provider: Claude API implementation with streaming
  • OpenAI Provider: GPT API implementation with streaming

Component Interaction

The layers communicate through typed channels using Tokio’s MPSC primitives:

┌─────────────┐                              ┌──────────────────┐
│     App     │──── ToControllerTx ─────────▶│   InputRouter    │
│   (TUI)     │                              │                  │
│             │◀─── FromControllerRx ────────│                  │
└─────────────┘                              └────────┬─────────┘


                                             ┌──────────────────┐
                                             │  LLMController   │
                                             │                  │
                                             │  - from_llm_rx   │
                                             │  - input_rx      │
                                             │  - batch_rx      │
                                             │  - tool_rx       │
                                             │  - interact_rx   │
                                             │  - perm_rx       │
                                             └────────┬─────────┘

                    ┌─────────────────────────────────┼─────────────────────────────────┐
                    │                                 │                                 │
                    ▼                                 ▼                                 ▼
           ┌──────────────┐                  ┌──────────────┐                  ┌──────────────┐
           │ LLMSession   │                  │ ToolExecutor │                  │  Registries  │
           │              │                  │              │                  │              │
           │ - streaming  │                  │ - parallel   │                  │ - tools      │
           │ - compaction │                  │   execution  │                  │ - perms      │
           └──────────────┘                  └──────────────┘                  │ - interact   │
                    │                                                          └──────────────┘

           ┌──────────────┐
           │  LLMClient   │
           │              │
           │ - Anthropic  │
           │ - OpenAI     │
           └──────────────┘

Design Principles

Message-Driven Architecture

All inter-component communication uses typed message channels. This provides:

  • Decoupling: Components only know about message types, not implementations
  • Non-blocking: Async channels prevent one component from blocking another
  • Backpressure: Bounded channel buffers (default 100) prevent memory exhaustion

Async-First Design

The entire system runs on Tokio’s async runtime:

  • Single runtime instance created by AgentAir
  • Background tasks spawned for controller, router, and event forwarding
  • CancellationToken enables coordinated graceful shutdown
  • block_on bridges sync entry points to async internals

Trait-Based Extensibility

Core behaviors are defined as traits, allowing customization without modifying framework code:

TraitPurpose
AgentConfigAgent configuration (paths, prompts, name)
ExecutableTool implementation
WidgetTUI component
SlashCommandUser command
KeyHandlerKeyboard input processing
LlmProviderCustom LLM backend
CompactorContext window compaction strategy

Shared State with Arc

Components that need shared access use Arc<T> for thread-safe reference counting:

controller: Arc<LLMController>
user_interaction_registry: Arc<UserInteractionRegistry>
permission_registry: Arc<PermissionRegistry>
tool_registry: Arc<ToolRegistry>

Interior mutability is handled with RwLock for read-heavy data (conversation history) and Mutex for exclusive access (channel receivers in select loops).

Data Flow Summary

  1. User Input: User types in TextInput widget
  2. Message Creation: App creates ControllerInputPayload with content and turn ID
  3. Routing: InputRouter forwards payload to LLMController
  4. Session Handling: Controller routes to appropriate LLMSession
  5. API Call: Session uses LLMClient to call provider API with streaming
  6. Response Processing: Streamed chunks emit ControllerEvent via callback
  7. Event Conversion: Events convert to UiMessage for TUI consumption
  8. Display: App receives messages and updates ChatView

Tool execution follows a similar pattern but with additional steps for parallel execution and result aggregation before sending results back to the LLM.

Key Structs at Each Layer

LayerKey Structs
TUIApp, ChatView, TextInput, Theme, LayoutTemplate
AgentAgentAir, InputRouter, Logger, LLMRegistry
ControllerLLMController, LLMSession, ToolExecutor, ToolRegistry
ClientLLMClient, AnthropicProvider, OpenAIProvider

Next Steps