The app server (codex-app-server) provides a JSON-RPC 2.0 protocol interface that enables IDE extensions and other rich clients to interact with the Codex agent. It acts as a bridge between external clients and the core agent system, translating bidirectional messages and managing thread lifecycle state. This document covers the protocol design, transport options, request dispatching, event translation, and thread/turn management APIs.
For information about the core agent event loop and session management that the app server wraps, see Core Agent System. For terminal user interface implementation, see Terminal User Interface (TUI). For headless execution mode, see Headless Execution Mode (codex exec).
The app server enables external applications (primarily IDE extensions like VS Code and Cursor) to drive Codex conversations programmatically. Unlike the TUI which presents a terminal interface, or exec mode which runs non-interactively, the app server:
Event messages into protocol-compliant ServerNotification messagesThe app server binary can be invoked as codex app-server and defaults to stdio transport with newline-delimited JSON (JSONL) messages.
Sources: codex-rs/app-server/README.md1-19
Architecture Overview
The app server uses a layered architecture where transport mechanisms are decoupled from protocol logic. The CodexMessageProcessor dispatches incoming ClientRequest messages to appropriate handlers, while OutgoingMessageSender queues ServerNotification and ServerRequest messages for transmission. The bespoke_event_handling module subscribes to core Event streams and transforms them into protocol-compliant notifications.
Sources: codex-rs/app-server/src/codex_message_processor.rs335-370 codex-rs/app-server/src/bespoke_event_handling.rs1-110 Diagram 5 from high-level overview
The protocol defines four message categories:
Requests sent from the client to the server. Each request includes a method field (discriminator), an id field for request tracking, and a params object. Common methods:
thread/start, thread/resume, thread/fork, thread/archive, thread/unarchive, thread/listturn/start, turn/steer, turn/interruptconfig/read, config/value/write, config/batchWriteaccount/login/start, account/logout, account/readreview/startOne-way notifications from server to client. These stream real-time updates during turn execution:
item/started, item/completed, item/agentMessage/deltaturn/started, turn/completedthread/started, thread/archived, thread/status/changed, thread/name/updatedmodel/rerouteditem/commandExecution/outputDelta, item/fileChange/outputDeltaRequests from server to client (blocking). Used for approvals and user input:
commandExecution/requestApproval - Command execution approvalfileChange/requestApproval - File change approvaltool/requestUserInput - Tool-initiated user input (experimental)chatgptAuthTokens/refresh - Token refresh (external auth mode)One-way notifications from client to server:
initialized - Sent after successful initialize response to complete handshakeSources: codex-rs/app-server-protocol/src/protocol/common.rs80-486
stdio (default): Newline-delimited JSON (JSONL) over standard input/output. Each JSON-RPC message is serialized on a single line terminated by \n. This is the primary transport for IDE extensions that spawn codex app-server as a child process.
WebSocket (experimental): One JSON-RPC message per WebSocket text frame. Enabled via --listen ws://IP:PORT. This mode is experimental and unsupported; clients should not rely on it for production.
All clients must complete an initialization handshake before invoking any other method:
initialize request with clientInfo (name, version) and optional capabilitiesInitializeResponse containing server user agent stringinitialized notification to acknowledgeRequests sent before initialization are rejected with a "Not initialized" error. Duplicate initialize calls on the same connection receive an "Already initialized" error.
Sources: codex-rs/app-server/README.md69-95 codex-rs/app-server-protocol/src/protocol/common.rs177-181
The CodexMessageProcessor struct is the central request handler. It receives deserialized ClientRequest messages and routes them to appropriate handlers based on the method discriminator.
CodexMessageProcessor {
auth_manager: Arc<AuthManager>,
thread_manager: Arc<ThreadManager>,
outgoing: Arc<OutgoingMessageSender>,
config: Arc<Config>,
cli_overrides: Vec<(String, TomlValue)>,
cloud_requirements: Arc<RwLock<CloudRequirementsLoader>>,
thread_state_manager: ThreadStateManager,
thread_watch_manager: ThreadWatchManager,
active_login: Arc<Mutex<Option<ActiveLogin>>>,
pending_fuzzy_searches: Arc<Mutex<HashMap<String, Arc<AtomicBool>>>>,
fuzzy_search_sessions: Arc<Mutex<HashMap<String, FuzzyFileSearchSession>>>,
feedback: CodexFeedback,
}
The process_request method (codex-rs/app-server/src/codex_message_processor.rs527-831) matches on the ClientRequest enum and dispatches to type-specific handlers:
thread_start, thread_resume, thread_fork, thread_archive, thread_unarchive, thread_list, thread_readturn_start, turn_steer, turn_interruptlogin_v2, logout_v2, get_accountreview_startEach handler is an async method that:
ThreadManager, AuthManager, etc.)Sources: codex-rs/app-server/src/codex_message_processor.rs335-424 codex-rs/app-server/src/codex_message_processor.rs527-831
Creates a new thread and auto-subscribes the connection to its events. Parameters allow overriding configuration settings (model, cwd, approval_policy, sandbox_policy, etc.).
Handler implementation: codex-rs/app-server/src/codex_message_processor.rs1111-1276
Reopens an existing thread by loading its rollout from disk and restoring session state. The thread becomes active and subsequent turn/start calls append to it.
Handler implementation: codex-rs/app-server/src/codex_message_processor.rs1278-1405
Creates a new thread by copying the rollout history from an existing thread. Emits thread/started for the new thread and auto-subscribes the connection.
Handler implementation: codex-rs/app-server/src/codex_message_processor.rs1407-1512
Adds user input to a thread and begins agent generation. The request accepts:
input: Array of UserInput items (text, image, localImage, skill, mention)outputSchema: Optional JSON Schema to constrain the final assistant messagecollaborationMode: Optional mode overrideReturns immediately with an in-progress Turn object, then streams turn/started, item/*, and turn/completed notifications.
Handler implementation: codex-rs/app-server/src/codex_message_processor.rs1806-2191
Appends additional user input to the currently active turn without starting a new turn. Requires expectedTurnId to ensure no race conditions. If the expected turn is not active, the request fails.
Handler implementation: codex-rs/app-server/src/codex_message_processor.rs2193-2278
Requests cancellation of an in-flight turn. The server signals the core agent to stop execution, then emits turn/completed with status: "interrupted".
Handler implementation: codex-rs/app-server/src/codex_message_processor.rs2280-2323
Sources: codex-rs/app-server/README.md160-455 codex-rs/app-server/src/codex_message_processor.rs1111-2323
The apply_bespoke_event_handling function (codex-rs/app-server/src/bespoke_event_handling.rs125-703) subscribes to core Event streams from CodexThread and transforms them into protocol-compliant notifications. This module bridges the gap between the core agent's internal event representation and the external protocol.
TurnStarted → turn/started notification
When the core emits EventMsg::TurnStarted, the handler notes this in ThreadWatchManager to update thread status, then constructs a TurnStartedNotification with the turn's initial state.
AgentMessageDelta → item/agentMessage/delta notification
Core EventMsg::AgentMessageDelta events carry incremental text updates. These are wrapped in AgentMessageDeltaNotification and streamed to the client.
ExecApprovalRequest → CommandExecutionRequestApproval (ServerRequest)
When the agent requests approval for command execution, the handler:
ThreadWatchManagerCommandExecutionRequestApprovalParams with command, cwd, reason, network contextServerRequest via OutgoingMessageSenderCommandExecutionApprovalDecision to core Op::ExecApproval and submits to the threadApplyPatchApprovalRequest → FileChangeRequestApproval (ServerRequest)
Similar flow for file change approvals. The handler:
item/started with a FileChange item (if first time)FileChangeRequestApprovalParams as a ServerRequestOp::ApplyPatchApprovalExecCommandEnd → item/completed notification
When command execution finishes, the handler:
ThreadState to mark the command completeItemCompletedNotification with the CommandExecution item's final status and outputTurnComplete → turn/completed notification
When the turn finishes, the handler:
TurnSummary from ThreadState to extract error infoTurn object with all items and final statusTurnCompletedNotificationThreadWatchManager to update thread statusSources: codex-rs/app-server/src/bespoke_event_handling.rs125-703 codex-rs/app-server/src/thread_state.rs1-200
The ThreadWatchManager (codex-rs/app-server/src/thread_status.rs1-300) tracks the operational status of loaded threads and emits thread/status/changed notifications when status transitions occur.
NotLoaded: Thread is not currently loaded in memory. This is the default state for threads returned by thread/list or thread/read when not resumed.
Idle: Thread is loaded but no turn is active and no errors have occurred.
Active: Thread has an active turn. The activeFlags array indicates what operation is in progress:
running - Turn is executingpermissionRequested - Waiting for approvaluserInputRequested - Waiting for tool-initiated user inputSystemError: Thread encountered a system error (e.g., rollout corruption, auth failure). Contains error details.
The manager uses guard objects (ThreadWatchActiveGuard, ThreadWatchPermissionGuard) to automatically revert active flags when operations complete (via RAII pattern).
Sources: codex-rs/app-server/src/thread_status.rs1-300 codex-rs/app-server/src/bespoke_event_handling.rs142-152
The ThreadStateManager maintains mutable state for each active thread, stored in a ThreadState struct. This state is used during event translation to track item lifecycle and accumulate turn summaries.
ThreadState {
turn_summary: TurnSummary {
last_error: Option<TurnError>,
file_change_started: HashSet<String>,
command_execution_started: HashSet<String>,
mcp_tool_call_started: HashSet<String>,
plan_item_started: bool,
},
}
Item Tracking: When item/started is emitted for a FileChange, CommandExecution, or McpToolCall, the item ID is added to the corresponding HashSet. This prevents duplicate item/started notifications if the core emits multiple approval requests for the same item.
Error Recording: When EventMsg::TurnComplete includes an error, it's stored in turn_summary.last_error so the final TurnCompletedNotification can include error details.
State Lifecycle: Each thread has its own ThreadState instance, created when the thread is first subscribed. The state is reset at the start of each new turn (in handle_turn_started callback).
Sources: codex-rs/app-server/src/thread_state.rs1-100 codex-rs/app-server/src/bespoke_event_handling.rs202-223
The OutgoingMessageSender (codex-rs/app-server/src/outgoing_message.rs1-400) queues notifications and requests for transmission to clients. It maintains per-connection channels and handles connection-scoped vs. thread-scoped delivery.
Notifications are added to a bounded queue. If the queue is full (backpressure), the send operation awaits until capacity is available. The transport layer drains the queue and serializes messages.
Server-initiated requests return a oneshot::Receiver that resolves when the client replies. The app server uses this for:
commandExecution/requestApproval, fileChange/requestApproval)tool/requestUserInput)chatgptAuthTokens/refresh in external auth mode)ThreadScopedOutgoingMessageSender: Wraps OutgoingMessageSender with a thread_id so all messages automatically include the thread context. Used within apply_bespoke_event_handling to simplify call sites.
Connection-Scoped: Used for global notifications like account/loginCompleted, app/listUpdated, deprecationNotice.
Sources: codex-rs/app-server/src/outgoing_message.rs1-400 codex-rs/app-server/src/bespoke_event_handling.rs125-150
Returns the effective configuration after merging all layers (MDM, system, user, project, session flags, requirements). Supports includeLayers: true to include layer-by-layer breakdown and cwd to resolve project config from a specific directory.
Handler: codex-rs/app-server/src/config_rpc.rs1-300
Writes configuration to disk (defaults to user's config.toml). Supports optimistic concurrency via expectedVersion (file hash). Returns WriteStatus::Ok or WriteStatus::OkOverridden if a higher-precedence layer overrides the written value.
Handlers: codex-rs/app-server/src/config_rpc.rs100-500
Initiates authentication flow. Supports multiple modes:
ApiKey: Direct API key loginChatgpt: Managed OAuth flow (Codex handles token refresh)ChatgptAuthTokens: External auth mode where client supplies tokensReturns immediately with LoginAccountResponse containing login state. For ChatGPT OAuth, emits account/loginCompleted notification when browser flow finishes.
Handler: codex-rs/app-server/src/codex_message_processor.rs833-900
Clears stored authentication credentials and emits account/updated notification.
Handler: codex-rs/app-server/src/codex_message_processor.rs3500-3550
Returns current account state including auth mode, plan type, and rate limit snapshots. Supports refreshToken: true to proactively refresh tokens before returning.
Handler: codex-rs/app-server/src/codex_message_processor.rs3400-3480
Sources: codex-rs/app-server/README.md155-159 codex-rs/app-server/src/codex_message_processor.rs833-3550
The review/start endpoint initiates Codex's automated code reviewer. Parameters:
threadId: Target thread for the reviewtarget: What to review (uncommitted changes, base branch, specific commit, custom instructions)delivery: Where to run the review:
inline: Run as a new turn on the existing threaddetached: Fork a new review thread from the parentThe review process emits special items:
enteredReviewMode: Signals review start with target descriptionexitedReviewMode: Contains formatted review findings as plain textFor inline delivery, reviewThreadId equals the original threadId and no new thread/started notification is emitted.
Sources: codex-rs/app-server/README.md457-526 codex-rs/app-server/src/codex_message_processor.rs2325-2500
The protocol supports experimental API gating via the capabilities.experimentalApi flag during initialization. Methods and fields marked with #[experimental("reason")] are:
experimentalApi: true-32601 (Method not found) otherwiseMethod-level gating: Entire request methods can be experimental (e.g., collaborationMode/list, thread/backgroundTerminals/clean, mock/experimentalMethod).
Field-level gating: Some methods are stable but accept experimental parameters. The ExperimentalApi trait's experimental_reason() method inspects params to determine if a request uses experimental features.
Example experimental methods:
thread/backgroundTerminals/clean - Terminate background terminalscollaborationMode/list - List collaboration mode presetsfuzzyFileSearch/sessionStart - Start interactive fuzzy file search sessionClients must opt in via InitializeCapabilities to access these features.
Sources: codex-rs/app-server-protocol/src/experimental_api.rs1-100 codex-rs/app-server-protocol/src/protocol/common.rs44-174
The server uses bounded queues between transport ingress, request processing, and outbound writes. When request ingress saturates, new requests are rejected with JSON-RPC error code -32001 and message "Server overloaded; retry later." Clients should treat this as retryable and use exponential backoff with jitter.
When invoked with --single-client, the app server:
Sources: codex-rs/app-server/README.md36-41 codex-rs/app-server/src/message_processor.rs1-200
The protocol types are defined in Rust with derives for serde::Serialize, serde::Deserialize, schemars::JsonSchema, and ts_rs::TS. This enables:
Generates TypeScript type definitions for all protocol messages. The output is guaranteed to match the running Codex version's protocol.
Generates JSON Schema documents for protocol validation. These schemas include descriptions, constraints, and discriminator metadata.
Generated artifacts are version-specific. Clients should regenerate schemas when upgrading Codex versions to catch protocol changes at compile time.
Sources: codex-rs/app-server/README.md43-49 codex-rs/app-server-protocol/src/export.rs1-200
Refresh this wiki