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 green 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 different meanings and have different effects on the system state.
Lifecycle Operations¶
| Op | Semantics | Effect on System | Contains | Key Characteristic |
|---|---|---|---|---|
Op::UserTurn | "User wants to accomplish a task" | Creates new Task, aborts currently running Task | user message, cwd, approval policy, sandbox policy, model selection | Destructive - replaces ongoing work |
Op::Interrupt | "User wants to immediately stop" | Preempts running operation immediately | (none) | Highest priority, cannot be ignored |
Approval Operations¶
| Op | Semantics | Effect on System | Contains | Responds To |
|---|---|---|---|---|
Op::ExecApproval | "User's decision on safety check" | Allows or blocks command execution | decision (Allow/Deny), command ID | EventMsg::ExecApprovalRequest |
Op::PatchApproval | "User's decision on code modification" | Allows or blocks patch application | decision (Allow/Deny), patch ID | EventMsg::ApplyPatchApprovalRequest |
Response Operations¶
| Op | Semantics | Effect on System | Contains | Purpose |
|---|---|---|---|---|
Op::UserInputAnswer | "User provides requested information" | Resumes paused Turn with provided data | user's response to EventMsg::RequestUserInput | Task completion (different from approval) |
EventMsg Semantics¶
Events from Daemon to UI indicate state changes and provide information about ongoing operations.
Lifecycle Events¶
| EventMsg | Meaning | UI Should Display | Indicates | Contains |
|---|---|---|---|---|
TurnStarted | A Turn has begun | "Processing..." indicator | Work in progress | turn_id, model_context_window |
TurnComplete | A Turn finished successfully | Hide "Processing..." indicator | Turn succeeded, may lead to another | response_id (for resuming) |
TurnAborted | A Turn was stopped | "Interrupted" message | User stopped or error occurred | (none) |
Error | Something went wrong | Error message | Task cannot continue | error details |
Warning | Non-fatal issue | Warning message | Task continues | warning details |
Output Events¶
| EventMsg | Meaning | UI Should Display | Indicates |
|---|---|---|---|
AgentMessageDelta | Streaming text fragment | Append to display | More content coming |
AgentMessage | Complete message | Replace accumulated deltas | Message complete |
AgentReasoningDelta | Streaming thinking fragment | Append to reasoning display | More reasoning coming |
AgentReasoning | Complete reasoning | Replace accumulated deltas | Reasoning complete |
ExecCommandBegin | Command execution starts | Show "Running..." indicator | Command in progress |
ExecCommandOutputDelta | Command output fragment | Append to output | More output coming |
ExecCommandEnd | Command execution ends | Hide "Running..." indicator | Command finished |
PatchApplyBegin | Patch application starts | Show "Applying..." indicator | Patch in progress |
PatchApplyEnd | Patch application ends | Hide "Applying..." indicator | Patch finished |
Request Events¶
| EventMsg | Meaning | UI Should Display | Purpose |
|---|---|---|---|
ExecApprovalRequest | Daemon requests approval for operation | Approval dialog | Safety check |
RequestUserInput | LLM requests information from user | Input prompt | Task completion |
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.