In an async, event-driven architecture like Agent Air, errors originate in different tasks and contexts. “Bubbling up” isn’t just about returning Result; it involves crossing channel boundaries and thread contexts.

The Propagation Path

  1. Origin: An error occurs (e.g., reqwest fails in the LLMClient).
  2. Mapping: It is converted to a domain error (LlmError::Network).
  3. Controller: The LLMController catches this error.
    • If it’s a tool error, it might be fed back to the LLM as a “Tool Result”.
    • If it’s a fatal system error, it’s converted to a ControllerEvent::Error.
  4. Event Loop: The main event loop receives the ControllerEvent::Error.
  5. TUI: The TUI converts this event into a UiMessage::Error.
  6. Display: The error is rendered in the ChatView (often in red) or as a popup/status bar message.

Cross-Task Propagation

When a background task (like a tool execution) fails critically (panics or unrecoverable error), we use tokio::sync::oneshot or mpsc channels to signal the failure back to the main controller task, ensuring the agent doesn’t just silently hang.