2. Event System¶
Communication between UI and Daemon
Getting start introduces UI and Daemon communicates with custom protocol.
This passage focuses on the event system between UI and Daemon communication, and skip any details of the custom protocol, as the highlighted areas in the diagram.
sequenceDiagram
autonumber
actor User
participant Daemon
participant Model
rect rgb(200, 220, 255)
User ->>+ Daemon: Query Go LOC
end
Note over Daemon, Model: Interact with Model<br>......<br> get results.
rect rgb(200, 220, 255)
Daemon -->>- User: Deliver final message
end Event System¶
The event system defines the communication protocol between UI and Daemon using two types of messages:
-
Operations (Op): Messages sent from UI to Daemon, for example:
Op::UserTurn- user sends inputOp::ExecApproval- user approves command execution
-
Events (EventMsg): Messages sent from Daemon to UI
EventMsg::AgentMessage- model's responseEventMsg::ExecApprovalRequest- daemon requests approval
Communication Channel Characteristics¶
The UI-Daemon communication is full-duplex with two independent channels:
- Submission Queue (SQ): UI → Daemon (carries
Opmessages) - Event Queue (EQ): Daemon → UI (carries
EventMsgmessages)
Each channel operates independently:
- UI sends
Opmessages through SQ at any time - Daemon pushes
EventMsgmessages through EQ at any time - Neither channel blocks the other
Ordering guarantee:
- Each channel preserves message order (FIFO)
Op::UserTurn(A)sent beforeOp::UserTurn(B)→ Daemon receives A before BEventMsg::Xsent beforeEventMsg::Y→ UI receives X before Y
Sequential Op behavior: When UI sends Op::A immediately followed by Op::B:
| Scenario | Protocol-level behavior | Implementation detail |
|---|---|---|
| General case | Daemon receives A, then receives B | How Daemon processes the queue depends on Op types |
Op::Interrupt followed by any Op | Interrupt received first, takes effect immediately | Interrupt preempts current operation |
Multiple Op::UserTurn | Received in order | Subsequent turns typically abort previous work (Daemon's choice) |
Key distinction: The protocol guarantees delivery order (A then B), but how they affect the system state depends on the Daemon's processing logic for each specific Op type.
Scope boundary: This section describes protocol-level guarantees:
- Message delivery and ordering are guaranteed by the transport layer
- How Daemon processes queued
Opmessages is an implementation detail - How UI handles received
EventMsgmessages is an implementation detail
Why Event System?¶
Unlike traditional HTTP request-response models where a client sends a request and receives a complete response, Codex interacts with the Model via Server-Sent Events (SSE) protocol.
The Model returns a stream of events instead of a single response:
event: response.output_text.delta
data: {"delta": "I'll"}
event: response.output_text.delta
data: {"delta": " analyze"}
event: response.output_item.done
data: {"type": "function_call", ...}
This means the Daemon natively receives and processes events from the Model, and consequently pushes events to the UI. Conversely, the UI pushes events (operations) to the Daemon to control the flow—approving actions, providing input, or interrupting execution.
Therefore, the event system is used for UI-Daemon communication not as a design pattern for abstraction, but because it fundamentally matches the Model's streaming architecture.
Event Semantics¶
Op Semantics¶
Operations from UI to Daemon carry intent, not implementation details. High-level categories:
- Lifecycle: start work (
UserTurn) or interrupt it (Interrupt). - Approvals: user decisions on safety checks (
ExecApproval,PatchApproval). - Responses: user supplies missing info (
UserInputAnswer).
Full definition and exhaustive list live in the protocol enum: enum Op at codex-rs/protocol/src/protocol.rs:L167.
EventMsg Semantics¶
Events from Daemon to UI represent lifecycle changes, streaming output, and user prompts. At a high level:
- Lifecycle: turn started/completed/aborted; errors or warnings.
- Output: streaming deltas and final items (assistant message, tool output, patch events).
- Requests: prompts for approvals or missing user input.
Full definition and exhaustive list live in the protocol enum EventMsg at codex-rs/protocol/src/protocol.rs.
Task Examples¶
Example 1: Simple Task¶
User asks a simple question, LLM responds directly without tool execution.
UI Op EventMsg Daemon
─── ──────────── ──────────── ────
Op::UserTurn →
← EventMsg::TurnStarted
← EventMsg::AgentMessageDelta*
← EventMsg::AgentMessage
← EventMsg::TurnComplete
Example 2: Task with Tool Execution¶
User requests file deletion, LLM requires approval, executes command, then confirms.
UI Op EventMsg Daemon
─── ──────────── ──────────── ────
Op::UserTurn →
← EventMsg::TurnStarted
← EventMsg::AgentMessageDelta*
← EventMsg::ExecApprovalRequest
Op::ExecApproval →
← EventMsg::ExecCommandBegin
← EventMsg::ExecCommandOutputDelta*
← EventMsg::ExecCommandEnd
← EventMsg::AgentMessage
← EventMsg::TurnComplete
Example 3: Task with Follow-up Questions¶
User asks to refactor code, LLM needs to know which function, asks user, then continues.
UI Op EventMsg Daemon
─── ──────────── ──────────── ────
Op::UserTurn →
← EventMsg::TurnStarted
← EventMsg::AgentMessage
← EventMsg::RequestUserInput
Op::UserInputAnswer →
← EventMsg::AgentMessage
← EventMsg::TurnComplete
Example 4: Interrupted Task¶
User realizes they asked the wrong question, interrupts current task and starts over.
UI Op EventMsg Daemon
─── ──────────── ──────────── ────
Op::UserTurn (Task A) →
← EventMsg::TurnStarted
Op::Interrupt →
← EventMsg::TurnAborted
Op::UserTurn (Task B) →
← EventMsg::TurnStarted
← ...
Concurrent Op Handling¶
The Daemon maintains an internal state machine that determines how to handle concurrent or sequential Op messages. Different Op types trigger different state transitions.
Scenario: Multiple Op in sequence
| Op sequence | Daemon behavior | State transition |
|---|---|---|
UserTurn(A) → UserTurn(B) | Abort Task A, start Task B | Running → Aborted → Running |
UserTurn → Interrupt | Immediate preemption, no graceful shutdown | Running → Aborted |
ExecApprovalRequest → Interrupt | Abort the pending approval | Waiting → Aborted |
RequestUserInput → Interrupt | Cancel the information request | Waiting → Aborted |
Key principle: Only one Task runs at a time. New UserTurn or Interrupt always affects the current state.
Note: The detailed state machine implementation and concurrency control mechanisms will be covered in the next chapter "User Request Lifecycle". This section focuses on the protocol-level behavior—what happens when multiple Op messages are sent in sequence.