The Restaurant Kitchen Analogy
Imagine a restaurant. A busy one. On one side, waiters take orders from diners. On the other side, chefs work at stations: prep, grill, plating. In the middle: a ticket system.
The waiter doesn't shout across the kitchen. They don't interrupt the chef mid-flip. Instead, they write an order ticket and place it in the pass. The ticket has everything the chef needs: what to make, when it was ordered, any special requests.
When the dish is ready, the chef rings a bell. The food waits in the window. A runner picks it up and delivers it to the table.
The kitchen doesn't know which diner ordered what. The diner doesn't see the chaos of the kitchen. The ticket system—the nervous system—connects them without coupling them.
Codex works exactly like this. And the protocol that implements it? It's called SQ/EQ: the Submission Queue and the Event Queue.
Submissions: The Order Tickets
When you send a command to Codex, you're not directly talking to the core engine. You're writing a Submission.
pub struct Submission {
pub id: String, // Unique ID to correlate with responses
pub op: Op, // The operation (the "order")
pub trace: Option<W3cTraceContext>, // Distributed tracing
}
Three fields. But they pack a punch. The id is a UUID tying submissions to events. The trace carries W3C trace context for end-to-end observability. And the op field is an enum representing every possible action:
The Op Enum: Orders in the Kitchen
rm -rf /—approve?" Respond Approved or Rejected.The beauty of this enum-based design is extensibility. Need a new operation? Add a variant to Op. The protocol doesn't change at the transport layer. New and old clients coexist.
Events: The Kitchen Responds
While submissions flow in, events flow out. Here's the critical insight: events are not responses to submissions. They're not synchronous. The kitchen emits a stream of events as work progresses.
pub enum EventMsg {
TurnStarted(TurnStartedEvent),
AgentMessage(AgentMessageEvent),
AgentReasoning(AgentReasoningEvent),
ExecCommandBegin(ExecCommandBeginEvent),
ExecCommandOutputDelta(...),
ExecCommandEnd(ExecCommandEndEvent),
ExecApprovalRequest(ExecApprovalRequestEvent),
TurnComplete(TurnCompleteEvent),
// ... dozens more
}
You submit a UserTurn with id abc-123, and within milliseconds you get: TurnStarted → AgentMessage (streaming, delta by delta) → ExecCommandBegin → ExecApprovalRequest → your approval → ExecCommandEnd → more agent output → TurnComplete. All of it streaming in real-time.
See It In Action
Click "Run Simulation" to watch a real SQ/EQ exchange. Left: submissions go in. Right: events come out.
The Hierarchy: Threads, Turns, Items
Codex introduces a nested structure mirroring how conversations work:
A Thread is a conversation. A Turn is a single round trip. An Item is a discrete piece of work within a turn. Each item has its own ID and lifecycle. This hierarchy lets the UI render conversation in a granular way—stream text character by character, show shell output as it arrives, toggle reasoning collapsed.
The Async Channels: The Real Magic
// Frontend sends submissions here
let (tx_sub, rx_sub) = mpsc::channel::<Submission>();
// Core sends events here
let (tx_event, rx_event) = mpsc::channel::<EventMsg>();
// The core engine loop
tokio::spawn(async move {
while let Some(submission) = rx_sub.recv().await {
match submission.op {
Op::UserTurn { items, cwd, model, ... } => {
tx_event.send(EventMsg::TurnStarted(...)).await?;
// Process the turn, call the model, execute commands...
tx_event.send(EventMsg::TurnComplete(...)).await?;
}
Op::Interrupt => {
tx_event.send(EventMsg::TurnAborted(...)).await?;
}
// ... handle other operations
}
}
});
The frontend and core are decoupled by channels. The frontend doesn't wait for a response—it sends a submission and listens on the event stream. Multiple frontends can connect to the same core. The web interface, the TUI, a CI/CD pipeline—all listening to the same event stream.