This document describes how Codex processes events from the model API, updates session state, and propagates changes to UI layers. Event processing is the bridge between streaming model responses and the persistent conversation state that drives UI updates, rollout persistence, and turn lifecycle management.
For information about the protocol-level Op/Event message definitions, see Protocol Layer (Submission/Event System). For details about how turns are spawned and executed, see Turn Execution and Prompt Construction. For conversation history storage and compaction, see Conversation History Management.
Events flow from the model API through several layers before reaching UI components. The core processing pipeline transforms streaming ResponseEvent messages into protocol Event messages, updates SessionState, persists to rollout files, and broadcasts to subscribers.
Sources: codex-rs/core/src/codex.rs274-540 codex-rs/tui/src/chatwidget.rs1-50 codex-rs/protocol/src/protocol.rs1-100
Codex maintains three distinct state layers with different lifecycles and locking granularity.
Sources: codex-rs/core/src/codex.rs522-540 codex-rs/core/src/state/session.rs17-32 codex-rs/core/src/state/turn.rs20-33
SessionState persists across turns and holds conversation history, token counters, and configuration snapshots. Access is serialized via Mutex<SessionState>.
| Field | Type | Purpose |
|---|---|---|
session_configuration | SessionConfiguration | Frozen config snapshot (cwd, approval policy, sandbox policy) |
history | ContextManager | Conversation items for prompt construction |
latest_rate_limits | Option<RateLimitSnapshot> | Most recent rate limit state from API |
server_reasoning_included | bool | Whether server sent reasoning tokens in this session |
dependency_env | HashMap<String, String> | Environment variables for skill dependencies |
previous_model | Option<String> | Model used by last regular turn (for model-switch detection) |
startup_regular_task | Option<RegularTask> | Pre-created task for first turn (with prewarm) |
active_mcp_tool_selection | Option<Vec<String>> | Active MCP tool filter |
active_connector_selection | HashSet<String> | Active connector filter |
Sources: codex-rs/core/src/state/session.rs17-50
ActiveTurn exists only while tasks are running. Each task entry tracks cancellation tokens, timers, and turn context. TurnState buffers input and approval channels until resolved.
| Field | Type | Purpose |
|---|---|---|
tasks | IndexMap<String, RunningTask> | Map from submission ID to running task metadata |
turn_state | Arc<Mutex<TurnState>> | Mutable state shared with task executors |
RunningTask fields:
| Field | Type | Purpose |
|---|---|---|
done | Arc<Notify> | Signaled when task completes |
kind | TaskKind | Regular / Review / Compact |
task | Arc<dyn SessionTask> | Trait object for task logic |
cancellation_token | CancellationToken | Abort signal for graceful shutdown |
handle | Arc<AbortOnDropHandle<()>> | Tokio task handle (aborts on drop) |
turn_context | Arc<TurnContext> | Immutable context (model, tools, policies) |
_timer | Option<codex_otel::Timer> | Telemetry timer (dropped when task completes) |
TurnState fields:
| Field | Type | Purpose |
|---|---|---|
pending_approvals | HashMap<String, oneshot::Sender<ReviewDecision>> | Channels awaiting user approval responses |
pending_user_input | HashMap<String, oneshot::Sender<RequestUserInputResponse>> | Channels awaiting request_user_input responses |
pending_dynamic_tools | HashMap<String, oneshot::Sender<DynamicToolResponse>> | Channels awaiting dynamic tool responses |
pending_input | Vec<ResponseInputItem> | Buffered input items (for steer / retry scenarios) |
Sources: codex-rs/core/src/state/turn.rs20-76 codex-rs/core/src/tasks/mod.rs75-113
The handle_response_event function is the central dispatcher for model responses. It updates state, emits protocol events, and coordinates approval flows.
Sources: codex-rs/core/src/codex.rs2700-3200 codex-rs/core/src/stream_events_utils.rs1-100
Event processing follows a consistent pattern: parse event, update local state, record to rollout, emit protocol event. This ordering ensures persistence before broadcasting.
Example: Agent Message Delta Processing
Sources: codex-rs/core/src/codex.rs2800-2900 codex-rs/core/src/stream_events_utils.rs40-80
Example: Output Item Done Processing
Sources: codex-rs/core/src/stream_events_utils.rs100-200 codex-rs/core/src/codex.rs3000-3100
SessionState::record_items() appends finalized items to ContextManager, which maintains the conversation history used for prompt construction. Items are filtered by truncation policy to decide whether they survive compaction.
Sources: codex-rs/core/src/state/session.rs53-59 codex-rs/core/src/context_manager.rs1-100
Token usage is updated incrementally as OutputItemDone events arrive with per-item usage breakdowns. SessionState::update_token_info_from_usage() accumulates totals and broadcasts TokenCountEvent when thresholds are crossed.
Token Update Logic:
TokenUsage from OutputItemDone eventSessionState and call update_token_info_from_usage(usage)TokenCountEvent if totals changed significantlyRateLimitSnapshot if included in responseSources: codex-rs/core/src/state/session.rs95-150 codex-rs/core/src/codex.rs3050-3150
An ActiveTurn is created when the first task starts and destroyed when the last task completes. Tasks are tracked by submission ID to support parallel execution (e.g., collab sub-agents).
Sources: codex-rs/core/src/codex.rs1550-1650 codex-rs/core/src/tasks/mod.rs116-180
Spawn Sequence:
Completion Sequence:
Sources: codex-rs/core/src/tasks/mod.rs116-180 codex-rs/core/src/codex.rs1650-1750
TurnState holds channels for in-flight approval requests and buffered input items. When approval events arrive, the corresponding channel is removed from the map and the decision is sent to the waiting tool executor.
Sources: codex-rs/core/src/state/turn.rs69-100 codex-rs/core/src/codex.rs1900-2000
RolloutRecorder writes events to JSONL files as they occur. Persistence is gated by EventPersistenceMode (Limited vs Extended) to control rollout file size.
| Event Type | Limited Mode | Extended Mode |
|---|---|---|
SessionConfigured | ✓ | ✓ |
UserMessage | ✓ | ✓ |
AgentMessage | ✓ | ✓ |
AgentMessageDelta | ✗ | ✓ |
ExecCommandBegin | ✓ | ✓ |
ExecCommandEnd | ✓ | ✓ |
ExecCommandOutputDelta | ✗ | ✓ |
ItemCompleted | ✓ | ✓ |
TurnComplete | ✓ | ✓ |
TurnAborted | ✓ | ✓ |
TokenCount | ✗ | ✓ |
Limited mode is the default; extended mode is enabled via persist_extended_history flag.
Sources: codex-rs/core/src/rollout/policy.rs1-80
Sources: codex-rs/core/src/rollout/mod.rs1-100 codex-rs/core/src/rollout/policy.rs15-80
The tx_event channel broadcasts protocol Event messages to all subscribers (TUI, app-server, MCP server). Subscribers receive events in order and update their local state independently.
Sources: codex-rs/core/src/codex.rs274-293 codex-rs/tui/src/chatwidget.rs2000-2500 codex-rs/app-server/src/bespoke_event_handling.rs1-100 codex-rs/mcp-server/src/codex_tool_runner.rs1-100
When a user submits input while a turn is active (steer scenario), the input is buffered in TurnState::pending_input and injected into the model stream when the current agentic loop pauses. This allows mid-turn corrections without aborting the entire task.
Steer Flow:
Op::UserTurn while active_turn.is_some()handle_user_turn() detects active turnturn_state.push_pending_input(items) buffers inputSources: codex-rs/core/src/codex.rs1100-1200 codex-rs/core/src/state/turn.rs131-143
Event processing in Codex is a pipeline that:
ResponseEvent messages from ModelClientSession during turn executionhandle_response_event() for per-event-type processingSessionState (history, tokens, rate limits) under lockRolloutRecorderEvent messages to UI subscribers via tx_eventActiveTurn (spawn, track, complete, abort)TurnState for async resolutionState is partitioned into session-scoped (SessionState), turn-scoped (ActiveTurn), and task-scoped (TurnState) layers with appropriate locking granularity. This design supports parallel task execution, mid-turn input buffering, and consistent persistence-before-broadcast ordering.
Refresh this wiki