## Summary - Stream proposed plans in Plan Mode using `<proposed_plan>` tags parsed in core, emitting plan deltas plus a plan `ThreadItem`, while stripping tags from normal assistant output. - Persist plan items and rebuild them on resume so proposed plans show in thread history. - Wire plan items/deltas through app-server protocol v2 and render a dedicated proposed-plan view in the TUI, including the “Implement this plan?” prompt only when a plan item is present. ## Changes ### Core (`codex-rs/core`) - Added a generic, line-based tag parser that buffers each line until it can disprove a tag prefix; implements auto-close on `finish()` for unterminated tags. `codex-rs/core/src/tagged_block_parser.rs` - Refactored proposed plan parsing to wrap the generic parser. `codex-rs/core/src/proposed_plan_parser.rs` - In plan mode, stream assistant deltas as: - **Normal text** → `AgentMessageContentDelta` - **Plan text** → `PlanDelta` + `TurnItem::Plan` start/completion (`codex-rs/core/src/codex.rs`) - Final plan item content is derived from the completed assistant message (authoritative), not necessarily the concatenated deltas. - Strips `<proposed_plan>` blocks from assistant text in plan mode so tags don’t appear in normal messages. (`codex-rs/core/src/stream_events_utils.rs`) - Persist `ItemCompleted` events only for plan items for rollout replay. (`codex-rs/core/src/rollout/policy.rs`) - Guard `update_plan` tool in Plan Mode with a clear error message. (`codex-rs/core/src/tools/handlers/plan.rs`) - Updated Plan Mode prompt to: - keep `<proposed_plan>` out of non-final reasoning/preambles - require exact tag formatting - allow only one `<proposed_plan>` block per turn (`codex-rs/core/templates/collaboration_mode/plan.md`) ### Protocol / App-server protocol - Added `TurnItem::Plan` and `PlanDeltaEvent` to core protocol items. (`codex-rs/protocol/src/items.rs`, `codex-rs/protocol/src/protocol.rs`) - Added v2 `ThreadItem::Plan` and `PlanDeltaNotification` with EXPERIMENTAL markers and note that deltas may not match the final plan item. (`codex-rs/app-server-protocol/src/protocol/v2.rs`) - Added plan delta route in app-server protocol common mapping. (`codex-rs/app-server-protocol/src/protocol/common.rs`) - Rebuild plan items from persisted `ItemCompleted` events on resume. (`codex-rs/app-server-protocol/src/protocol/thread_history.rs`) ### App-server - Forward plan deltas to v2 clients and map core plan items to v2 plan items. (`codex-rs/app-server/src/bespoke_event_handling.rs`, `codex-rs/app-server/src/codex_message_processor.rs`) - Added v2 plan item tests. (`codex-rs/app-server/tests/suite/v2/plan_item.rs`) ### TUI - Added a dedicated proposed plan history cell with special background and padding, and moved “• Proposed Plan” outside the highlighted block. (`codex-rs/tui/src/history_cell.rs`, `codex-rs/tui/src/style.rs`) - Only show “Implement this plan?” when a plan item exists. (`codex-rs/tui/src/chatwidget.rs`, `codex-rs/tui/src/chatwidget/tests.rs`) <img width="831" height="847" alt="Screenshot 2026-01-29 at 7 06 24 PM" src="https://github.com/user-attachments/assets/69794c8c-f96b-4d36-92ef-c1f5c3a8f286" /> ### Docs / Misc - Updated protocol docs to mention plan deltas. (`codex-rs/docs/protocol_v1.md`) - Minor plumbing updates in exec/debug clients to tolerate plan deltas. (`codex-rs/debug-client/src/reader.rs`, `codex-rs/exec/...`) ## Tests - Added core integration tests: - Plan mode strips plan from agent messages. - Missing `</proposed_plan>` closes at end-of-message. (`codex-rs/core/tests/suite/items.rs`) - Added unit tests for generic tag parser (prefix buffering, non-tag lines, auto-close). (`codex-rs/core/src/tagged_block_parser.rs`) - Existing app-server plan item tests in v2. (`codex-rs/app-server/tests/suite/v2/plan_item.rs`) ## Notes / Behavior - Plan output no longer appears in standard assistant text in Plan Mode; it streams via `PlanDelta` and completes as a `TurnItem::Plan`. - The final plan item content is authoritative and may diverge from streamed deltas (documented as experimental). - Reasoning summaries are not filtered; prompt instructs the model not to include `<proposed_plan>` outside the final plan message. ## Codex Author `codex fork 019bec2d-b09d-7450-b292-d7bcdddcdbfb`
9.2 KiB
Overview of Protocol defined in protocol.rs and agent.rs.
The goal of this document is to define terminology used in the system and explain the expected behavior of the system.
NOTE: The code might not completely match this spec. There are a few minor changes that need to be made after this spec has been reviewed, which will not alter the existing TUI's functionality.
Entities
These are entities exit on the codex backend. The intent of this section is to establish vocabulary and construct a shared mental model for the Codex core system.
Model- In our case, this is the Responses REST API
Codex- The core engine of codex
- Runs locally, either in a background thread or separate process
- Communicated to via a queue pair – SQ (Submission Queue) / EQ (Event Queue)
- Takes user input, makes requests to the
Model, executes commands and applies patches.
Session- The
Codex's current configuration and state Codexstarts with noSession, and it is initialized byOp::ConfigureSession, which should be the first message sent by the UI.- The current
Sessioncan be reconfigured with additionalOp::ConfigureSessioncalls. - Any running execution is aborted when the session is reconfigured.
- The
Task- A
TaskisCodexexecuting work in response to user input. Sessionhas at most oneTaskrunning at a time.- Receiving
Op::UserTurnstarts aTask(Op::UserInputis legacy) - Consists of a series of
Turns - The
Taskexecutes to until:- The
Modelcompletes the task and there is no output to feed into an additionalTurn - Additional user-turn input aborts the current task and starts a new one
- UI interrupts with
Op::Interrupt - Fatal errors are encountered, eg.
Modelconnection exceeding retry limits - Blocked by user approval (executing a command or patch)
- The
- A
Turn- One cycle of iteration in a
Task, consists of:- A request to the
Model- (initially) prompt + (optional)last_response_id, or (in loop) previous turn output - The
Modelstreams responses back in an SSE, which are collected until "completed" message and the SSE terminates Codexthen executes command(s), applies patch(es), and outputs message(s) returned by theModel- Pauses to request approval when necessary
- A request to the
- The output of one
Turnis the input to the nextTurn - A
Turnyielding no output terminates theTask
- One cycle of iteration in a
The term "UI" is used to refer to the application driving Codex. This may be the CLI / TUI chat-like interface that users operate, or it may be a GUI interface like a VSCode extension. The UI is external to Codex, as Codex is intended to be operated by arbitrary UI implementations.
When a Turn completes, the response_id from the Model's final response.completed message is stored in the Session state to resume the thread given the next user turn. The response_id is also returned in the EventMsg::TurnComplete to the UI, which can be used to fork the thread from an earlier point by providing it in a future user turn.
Since only 1 Task can be run at a time, for parallel tasks it is recommended that a single Codex be run for each thread of work.
Interface
Codex- Communicates with UI via a
SQ(Submission Queue) andEQ(Event Queue).
- Communicates with UI via a
Submission- These are messages sent on the
SQ(UI ->Codex) - Has an string ID provided by the UI, referred to as
sub_id Oprefers to the enum of all possibleSubmissionpayloads- This enum is
non_exhaustive; variants can be added at future dates
- This enum is
- These are messages sent on the
Event- These are messages sent on the
EQ(Codex-> UI) - Each
Eventhas a non-unique ID, matching thesub_idfrom the user-turn op that started the current task. EventMsgrefers to the enum of all possibleEventpayloads- This enum is
non_exhaustive; variants can be added at future dates - It should be expected that new
EventMsgvariants will be added over time to expose more detailed information about the model's actions.
- This enum is
- These are messages sent on the
For complete documentation of the Op and EventMsg variants, refer to protocol.rs. Some example payload types:
OpOp::UserTurn– Any input from the user to kick off aTurnOp::UserInput– Legacy form of user inputOp::Interrupt– Interrupts a running turnOp::ExecApproval– Approve or deny code executionOp::UserInputAnswer– Provide answers for arequest_user_inputtool callOp::ListSkills– Request skills for one or more cwd values (optionallyforce_reload)Op::UserTurnandOp::OverrideTurnContextaccept an optionalpersonalityoverride that updates the model’s communication style
EventMsgEventMsg::AgentMessage– Messages from theModelEventMsg::AgentMessageContentDelta– Streaming assistant textEventMsg::PlanDelta– Streaming proposed plan text when the model emits a<proposed_plan>block in plan modeEventMsg::ExecApprovalRequest– Request approval from user to execute a commandEventMsg::RequestUserInput– Request user input for a tool call (questions can include options plusisOtherto add a free-form choice)EventMsg::TurnStarted– Turn start metadata includingmodel_context_windowandcollaboration_mode_kindEventMsg::TurnComplete– A turn completed successfullyEventMsg::Error– A turn stopped with an errorEventMsg::Warning– A non-fatal warning that the client should surface to the userEventMsg::TurnComplete– Contains aresponse_idbookmark for lastresponse_idexecuted by the turn. This can be used to continue the turn at a later point in time, perhaps with additional user input.EventMsg::ListSkillsResponse– Response payload with per-cwd skill entries (cwd,skills,errors)
UserInput items
Op::UserTurn content items can include:
text– Plain text plus optional UI text elements.image/local_image– Image inputs.skill– Explicit skill selection (name,pathtoSKILL.md).mention– Explicit app/connector selection (name,pathinapp://{connector_id}form).
Note: For v1 wire compatibility, EventMsg::TurnStarted and EventMsg::TurnComplete serialize as task_started / task_complete. The deserializer accepts both task_* and turn_* tags.
The response_id returned from each turn matches the OpenAI response_id stored in the API's /responses endpoint. It can be stored and used in future Sessions to resume threads of work.
Transport
Can operate over any transport that supports bi-directional streaming. - cross-thread channels - IPC channels - stdin/stdout - TCP - HTTP2 - gRPC
Non-framed transports, such as stdin/stdout and TCP, should use newline-delimited JSON in sending messages.
Example Flows
Sequence diagram examples of common interactions. In each diagram, some unimportant events may be eliminated for simplicity.
Basic UI Flow
A single user input, followed by a 2-turn task
sequenceDiagram
box UI
participant user as User
end
box Daemon
participant codex as Codex
participant session as Session
participant task as Task
end
box Rest API
participant agent as Model
end
user->>codex: Op::ConfigureSession
codex-->>session: create session
codex->>user: Event::SessionConfigured
user->>session: Op::UserTurn
session-->>+task: start task
task->>user: Event::TurnStarted
task->>agent: prompt
agent->>task: response (exec)
task->>-user: Event::ExecApprovalRequest
user->>+task: Op::ExecApproval::Allow
task->>user: Event::ExecStart
task->>task: exec
task->>user: Event::ExecStop
task->>user: Event::TurnComplete
task->>agent: stdout
agent->>task: response (patch)
task->>task: apply patch (auto-approved)
task->>agent: success
agent->>task: response<br/>(msg + completed)
task->>user: Event::AgentMessage
task->>user: Event::TurnComplete
task->>-user: Event::TurnComplete
Task Interrupt
Interrupting a task and continuing with additional user input.
sequenceDiagram
box UI
participant user as User
end
box Daemon
participant session as Session
participant task1 as Task1
participant task2 as Task2
end
box Rest API
participant agent as Model
end
user->>session: Op::UserTurn
session-->>+task1: start task
task1->>user: Event::TurnStarted
task1->>agent: prompt
agent->>task1: response (exec)
task1->>task1: exec (auto-approved)
task1->>user: Event::TurnComplete
task1->>agent: stdout
task1->>agent: response (exec)
task1->>task1: exec (auto-approved)
user->>task1: Op::Interrupt
task1->>-user: Event::Error("interrupted")
user->>session: Op::UserTurn w/ response bookmark
session-->>+task2: start task
task2->>user: Event::TurnStarted
task2->>agent: prompt + Task1 last_response_id
agent->>task2: response (exec)
task2->>task2: exec (auto-approve)
task2->>user: Event::TurnComplete
task2->>agent: stdout
agent->>task2: msg + completed
task2->>user: Event::AgentMessage
task2->>user: Event::TurnComplete
task2->>-user: Event::TurnComplete