This page documents the App struct's main event loop, initialization sequence, and event dispatch mechanism in the TUI. The App serves as the top-level orchestrator that coordinates the terminal UI, manages multiple conversation threads, and routes events between the UI layer and the core agent system.
For details about the chat surface rendering and protocol event handling, see ChatWidget and Conversation Display. For input handling and composer state, see BottomPane and Input System.
The App struct owns the top-level TUI state and coordinates interactions between the terminal UI, core business logic, and external systems. It maintains references to shared services, manages active chat widgets, and processes both terminal input events and internal application events.
| Field | Type | Purpose |
|---|---|---|
server | Arc<ThreadManager> | Manages multiple conversation threads and coordinates core agents |
chat_widget | ChatWidget | Active chat surface for the current conversation |
auth_manager | Arc<AuthManager> | Handles authentication state and token refresh |
config | Config | Resolved configuration from all layers (MDM, system, user, project, CLI) |
app_event_tx | AppEventSender | Channel for emitting internal UI events |
file_search | FileSearchManager | Manages asynchronous file search for @ mentions |
transcript_cells | Vec<Arc<dyn HistoryCell>> | Committed conversation history for transcript overlay |
overlay | Option<Overlay> | Full-screen overlay (transcript view, diff viewer) |
thread_event_channels | HashMap<ThreadId, ThreadEventChannel> | Event channels for all threads (active and inactive) |
active_thread_id | Option<ThreadId> | Currently displayed thread |
backtrack | BacktrackState | Esc-backtracking state machine |
Sources: codex-rs/tui/src/app.rs517-583
Diagram: App component relationships and event flow
The App owns a single ChatWidget instance for the active thread. When switching threads, App creates a new ChatWidget and replays buffered events from the target thread's ThreadEventStore.
Sources: codex-rs/tui/src/app.rs517-583 codex-rs/tui/src/chatwidget.rs468-624
The TUI initialization follows a multi-stage sequence that handles onboarding, authentication, configuration resolution, session selection, and model migration prompts.
Diagram: TUI initialization sequence from entry to main loop
Sources: codex-rs/tui/src/app.rs1184-1476
For first-run users or when authentication is missing, the TUI presents an onboarding flow that guides the user through:
The onboarding state is tracked in codex-rs/cli/src/onboarding.rs and invoked from codex-rs/tui/src/app.rs1255-1278 After successful onboarding, the TUI proceeds to session creation.
Sources: codex-rs/tui/src/app.rs1255-1278
When authenticated, the user is prompted to either start a new session or resume an existing one. The resume picker loads the session index from $CODEX_HOME/session_index.jsonl and displays thread metadata (name, last updated timestamp).
If resuming, the App reconstructs the session by:
$CODEX_HOME/sessions/<thread_id>.jsonl)ChatWidget historySources: codex-rs/tui/src/app.rs1279-1336
After model list refresh, the App checks whether the configured model has an upgrade path (for example, gpt-4 → gpt-5.1). If an upgrade is available and has not been previously acknowledged, the TUI displays a migration prompt with upgrade details.
The user can:
config.model and config.model_reasoning_effort, persist the selection, and record the migration acknowledgmentMigration state is tracked in config.notices.model_migrations (a map of from_model → to_model) to avoid re-prompting.
Sources: codex-rs/tui/src/app.rs412-515 codex-rs/tui/src/model_migration.rs
The App main loop alternates between polling terminal input events (TuiEvent), processing internal application events (AppEvent), and handling commit animation ticks for streaming output.
Diagram: Event sources feeding into the App main loop
Sources: codex-rs/tui/src/app.rs1484-1629
TuiEvent represents raw terminal input (key presses, paste events, window resizes, focus changes) and custom events like commit animation ticks. The App dispatches these events based on the current UI state:
| State | TuiEvent Routing |
|---|---|
| Overlay active | Forward KeyEvent to the overlay's handle_key_event; ignore other events |
| Normal chat mode | Forward KeyEvent to ChatWidget, handle window resize, toggle focus state |
| Commit tick | Forward CommitTick to ChatWidget for streaming animation |
Key events are processed by ChatWidget::handle_key_event, which may emit AppEvents for actions that require app-level coordination (e.g., opening the resume picker, exiting the app).
Sources: codex-rs/tui/src/app.rs1631-1709
AppEvent is the internal message bus for UI actions that span multiple components or require coordination with shared services. ChatWidget, BottomPane, and App itself emit AppEvents to request actions like:
The App processes AppEvents via handle_app_event, which dispatches to specialized handlers based on the event variant. High-frequency events (e.g., InsertHistoryCell, CommitTick) are handled inline, while complex flows (e.g., OpenResumePicker, ForkCurrentSession) invoke dedicated async methods.
Sources: codex-rs/tui/src/app.rs1711-2490
The main event loop is implemented in run_event_loop:
Diagram: Main event loop processing flow
The loop continues until one of the handlers returns AppRunControl::Exit, at which point the App performs cleanup (stop commit animation, drain final events) and returns an AppExitInfo struct with token usage and exit reason.
Sources: codex-rs/tui/src/app.rs1484-1629
The App supports multiple concurrent conversation threads via the ThreadManager and a per-thread event buffering system. This allows users to switch between threads (Ctrl+A) without losing event history.
Each thread (active or inactive) has a dedicated ThreadEventChannel:
| Field | Purpose |
|---|---|
sender | mpsc::Sender<Event> for the thread's codex-core agent |
receiver | Option<mpsc::Receiver<Event>> (owned by active thread) |
store | Arc<Mutex<ThreadEventStore>> buffering events when inactive |
When a thread is active, its receiver is owned by App::active_thread_rx, and events flow directly to the ChatWidget. When a thread is inactive, events are buffered in the ThreadEventStore (up to THREAD_EVENT_CHANNEL_CAPACITY = 32768 events).
Sources: codex-rs/tui/src/app.rs245-348
Switching threads involves:
active_thread_rx back into the current thread's ThreadEventChannelOp::Shutdown and remove the thread from ThreadManagerThreadEventStore snapshot and replay SessionConfigured + buffered events into a fresh ChatWidgetThreadManagerThe suppress_shutdown_complete flag prevents the shutdown event from being misinterpreted as an unexpected agent death.
Sources: codex-rs/tui/src/app.rs2492-2636
Diagram: Event routing for active vs inactive threads
The buffering system keeps a bounded queue of events per thread, dropping the oldest UserMessage events first when the capacity is exceeded. This ensures that tool outputs and assistant responses are preserved during inactive periods.
Sources: codex-rs/tui/src/app.rs245-319
The App implements specialized handlers for categories of AppEvents. These handlers coordinate state changes across multiple components and shared services.
| Event | Handler | Actions |
|---|---|---|
NewSession | handle_new_session | Shutdown current agent, open onboarding for new thread |
OpenResumePicker | handle_open_resume_picker | Shutdown current agent, display resume picker overlay |
SelectAgentThread | handle_select_agent_thread | Switch to the selected thread, replay buffered events |
ForkCurrentSession | handle_fork_current_session | Submit Op::Fork, create new thread channel |
Sources: codex-rs/tui/src/app.rs1903-1991 codex-rs/tui/src/app.rs2492-2636
| Event | Handler | Actions |
|---|---|---|
UpdateModel | handle_update_model | Update config.model, reload ChatWidget |
UpdateAskForApprovalPolicy | handle_update_approval_policy | Update config.permissions.approval_policy, emit Op::SetPermissions |
UpdateSandboxPolicy | handle_update_sandbox_policy | Update config.permissions.sandbox_policy, emit Op::SetPermissions |
UpdateFeatureFlags | handle_update_feature_flags | Apply feature updates, persist to config |
PersistModelSelection | handle_persist_model_selection | Write model and effort to appropriate config layer |
Sources: codex-rs/tui/src/app.rs2073-2240
| Event | Handler | Actions |
|---|---|---|
StartFileSearch | handle_start_file_search | Spawn file search task, send results via AppEvent::FileSearchResult |
FileSearchResult | handle_file_search_result | Forward results to ChatWidget::on_file_search_result |
DiffResult | handle_diff_result | Open static overlay with diff content |
RefreshConnectors | handle_refresh_connectors | Fetch connector state, send via AppEvent::ConnectorsLoaded |
Sources: codex-rs/tui/src/app.rs1845-1878 codex-rs/tui/src/app.rs2644-2755
| Event | Handler | Actions |
|---|---|---|
Exit(ShutdownFirst) | handle_exit | Set pending_shutdown_exit_thread_id, submit Op::Shutdown, wait for ShutdownComplete |
Exit(Immediate) | handle_exit | Return AppRunControl::Exit immediately |
FatalExitRequest(msg) | handle_fatal_exit | Display error overlay, return AppRunControl::Exit with fatal reason |
The ShutdownFirst mode ensures that codex-core has a chance to flush the rollout file and clean up child processes before the TUI exits. The pending_shutdown_exit_thread_id field prevents the shutdown event from triggering thread failover logic.
Sources: codex-rs/tui/src/app.rs1992-2022 codex-rs/tui/src/app.rs569-573
The App maintains consistency between the ChatWidget state and the shared services via synchronization hooks:
App rebuilds the config from disk and reloads the ChatWidget with the refreshed configModelsManager::refresh, the updated model list is forwarded to ChatWidget::set_available_modelsAppEvent::StatusLineBranchUpdated, which updates the ChatWidget's status line stateThese hooks ensure that UI state reflects the latest shared state without requiring the ChatWidget to directly poll services.
Sources: codex-rs/tui/src/app.rs645-670 codex-rs/tui/src/app.rs2299-2318
The App event loop coordinates terminal input, internal application events, and protocol events from codex-core. Initialization handles onboarding, session selection, and model migrations before entering the main loop. Multi-thread support enables switching between conversations via event buffering and replay. Specialized event handlers orchestrate state changes across components and shared services.
Refresh this wiki