The Codex App-Server: Building Custom Integrations with the JSON-RPC Protocol

The Codex App-Server: Building Custom Integrations with the JSON-RPC Protocol
Every surface where Codex runs — the web app, the macOS desktop app, the VS Code extension, the CLI itself — is powered by the same underlying harness.1 That harness is exposed as the Codex App-Server: a long-lived process running a bidirectional JSON-RPC 2.0 interface. If you want to embed Codex into your own tooling beyond the official surfaces, this is the protocol to understand.
The App-Server graduated from being a purely internal implementation detail when OpenAI published the codex-rs/app-server source and its developer documentation.2 It is still marked experimental in places, but it is stable enough that the VS Code extension — shipped to tens of thousands of developers — relies on it exclusively.3
The Thread/Turn/Item Model
The entire protocol is organised around three nested primitives:4
graph TD
Thread["Thread\n(conversation)"] --> Turn1["Turn 1\n(user request + agent work)"]
Thread --> Turn2["Turn 2"]
Thread --> TurnN["Turn N"]
Turn1 --> Item1["Item: user message"]
Turn1 --> Item2["Item: reasoning"]
Turn1 --> Item3["Item: command execution"]
Turn1 --> Item4["Item: file change"]
Turn1 --> Item5["Item: agent message"]
- Thread — a persistent conversation. Threads survive process restarts and can be resumed by ID.
- Turn — one complete exchange: a user input followed by all the agent work that produces a response. Turns are the unit of interruption and rollback.
- Item — an atomic output unit within a turn: a message delta, a shell command, a file edit, a tool call, or a reasoning note.
This hierarchy maps directly onto what you see in the Codex UI. When Codex edits three files and runs pytest, you are watching a single turn emit multiple command and file-change items, all streamed incrementally.
Transport Options
The App-Server supports two transports:5
# Default: newline-delimited JSON over stdio
codex app-server --listen stdio://
# Experimental: WebSocket
codex app-server --listen ws://127.0.0.1:4500
Stdio is the production-grade choice. The client spawns the codex app-server subprocess and reads/writes JSONL on its stdout/stdin. This is how the VS Code extension operates.6
WebSocket is currently experimental and unsupported for production workloads.7 It becomes useful when the agent process runs on a separate machine (e.g. a remote dev box) and you need to forward over SSH. The --remote flag on the Codex CLI accepts ws:// or wss:// addresses so you can attach a TUI to a remote App-Server instance:
# Attach TUI to a remote app-server
codex --remote wss://codex.example.com/socket --no-alt-screen
WebSocket authentication uses either a capability token or an HMAC-signed JWT. Non-loopback listeners should have auth configured explicitly — unauthenticated remote listeners are only suitable during controlled rollout.8
Overloaded servers respond with JSON-RPC error code -32001 (“Server overloaded; retry later”). Clients must implement exponential backoff with jitter rather than tight-loop retries.
Initialisation Handshake
Every client must complete a two-step handshake before issuing any other request:9
// Step 1: initialize request
proc.stdin.write(JSON.stringify({
method: "initialize",
id: 0,
params: {
clientInfo: { name: "my-tool", version: "1.0.0" },
capabilities: {
experimentalApi: true, // opt-in to gated methods
optOutNotificationMethods: [] // suppress noisy events per-connection
}
}
}) + "\n");
// Step 2: initialized notification (no id = notification)
proc.stdin.write(JSON.stringify({
method: "initialized",
params: {}
}) + "\n");
The server responds to initialize with the user-agent string, the codexHome directory, and platform metadata. Requests arriving before the handshake completes receive a “Not initialized” error.
capabilities.experimentalApi: true is required to access gated methods such as dynamic tools and the extended filesystem RPC set. optOutNotificationMethods lets high-throughput clients drop event types they do not consume — useful for suppressing item/commandExecution/outputDelta spam in CI integrations that only care about turn completion.
Core RPC Methods
Thread Lifecycle
// Start a fresh conversation
{ method: "thread/start", id: 1, params: { cwd: "/my/project" } }
// Resume from a previous session
{ method: "thread/resume", id: 2, params: { threadId: "thr_abc123" } }
// Branch a conversation without mutating the original
{ method: "thread/fork", id: 3, params: { threadId: "thr_abc123" } }
// Drop the last N turns (undo)
{ method: "thread/rollback", id: 4, params: { threadId: "thr_abc123", count: 1 } }
thread/list returns paginated thread history with filters for model provider, source (CLI vs app vs IDE), archived status, and working directory — enough to build a meaningful thread browser UI.
Turn Control
// Begin a turn
{ method: "turn/start", id: 5, params: {
threadId: "thr_abc123",
userInput: "Refactor the auth module to use JWT",
sandboxPolicy: "workspaceWrite"
}}
// Append additional context while the turn is running
{ method: "turn/steer", id: 6, params: {
threadId: "thr_abc123",
turnId: "turn_xyz",
input: "Actually, use RS256 not HS256"
}}
// Cancel an in-flight turn
{ method: "turn/interrupt", id: 7, params: {
threadId: "thr_abc123",
turnId: "turn_xyz"
}}
turn/steer is particularly powerful for agentic UIs — it allows users to course-correct mid-execution without cancelling and restarting.
Event Notification Stream
After turn/start the server streams notifications until the turn completes:10
sequenceDiagram
Client->>Server: turn/start
Server-->>Client: turn/started
Server-->>Client: item/started (reasoning)
Server-->>Client: item/agentMessage/delta (×N)
Server-->>Client: item/started (commandExecution)
Server-->>Client: item/commandExecution/outputDelta (×N)
Server-->>Client: item/completed (commandExecution)
Server-->>Client: item/started (fileChange)
Server-->>Client: item/completed (fileChange)
Server-->>Client: turn/diff/updated
Server-->>Client: turn/completed
Every item follows the lifecycle: item/started → zero or more deltas → item/completed. The turn/diff/updated notification carries a consolidated diff of all file changes in the turn — useful for diff views without parsing individual file-change items.
Approval Flow
Commands and file changes may require approval depending on the session’s sandbox policy. The server initiates a JSON-RPC request to the client — this is the bidirectional aspect of the protocol:11
// Server → Client (server-initiated request)
{
"method": "serverRequest/approval",
"id": "sreq_001",
"params": {
"type": "commandExecution",
"command": "rm -rf dist/",
"threadId": "thr_abc123"
}
}
// Client → Server (response)
{
"id": "sreq_001",
"result": { "decision": "acceptForSession" }
}
Valid decisions for command execution: accept, acceptForSession, acceptWithExecpolicyAmendment, applyNetworkPolicyAmendment, decline, cancel. After the client responds, the server emits a serverRequest/resolved notification confirming the outcome.
UIs should render approval requests inline with the active turn so the decision context is visible to the user.
Filesystem RPCs
The v2 App-Server adds a filesystem RPC layer independent of the agent’s tool execution:12
// Read a file
{ method: "fs/readFile", params: { path: "/my/project/src/auth.ts" } }
// Write a file (respects sandbox policy)
{ method: "fs/writeFile", params: { path: "/tmp/output.json", content: "..." } }
// Watch a directory for changes
{ method: "fs/watch", params: { path: "/my/project/src" } }
// Server emits: fs/changed notifications when files are modified
// Enumerate directory contents
{ method: "fs/readDirectory", params: { path: "/my/project" } }
These methods are separate from the tool calls that the agent itself makes during a turn. They let the client perform filesystem operations — for example, reading the current file open in an editor to inject as context before starting a turn.
Filesystem watch enables reactive UIs that update their file tree as the agent works, without polling.
Schema Generation and Tooling
For typed client development, the App-Server can generate its own schema:13
# TypeScript definitions matching the running Codex version
codex app-server generate-ts
# Full JSON Schema bundle
codex app-server generate-json-schema
# Include gated/experimental fields
codex app-server generate-ts --experimental
The generated artefacts are version-pinned: they reflect the exact Codex binary that produced them. Always regenerate after a Codex upgrade to catch breaking changes in the protocol.
Practical Integration Patterns
Embedding Codex in a Custom IDE
Spawn codex app-server as a subprocess in your IDE plugin. Use thread/resume on startup to restore the user’s last session. Stream item/agentMessage/delta into your output panel and turn/diff/updated into an inline diff view. Register approval handlers for serverRequest/approval requests so users can approve commands from within your UI.
Headless CI Integration
In CI, spawn App-Server with sandboxPolicy: "workspaceWrite" and auto-approve decisions. Listen only for turn/completed and error — suppress the rest with optOutNotificationMethods. Use thread/rollback if a turn produces a failing diff before committing.
Remote Development
Run codex app-server --listen ws://0.0.0.0:4500 on a beefy remote box. Connect your local TUI with codex --remote wss://your-box.example.com/socket. This gives you the full Codex experience against remote compute without SSH session management.
Caveats
The App-Server carries explicit stability warnings: the WebSocket transport is experimental, the filesystem RPC set is a v2 addition, and some methods require opting into experimentalApi.14 Pin your Codex version in production integrations, regenerate schemas after updates, and treat -32001 overload errors as transient.
Citations
-
OpenAI, “App Server – Codex Developer Docs”, https://developers.openai.com/codex/app-server ↩
-
GitHub, openai/codex – codex-rs/app-server/README.md, https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md ↩
-
OpenAI, “Codex Changelog”, https://developers.openai.com/codex/changelog ↩
-
GitHub, openai/codex – app-server README (Thread/Turn/Item model), https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md ↩
-
OpenAI, “CLI Reference – Codex”, https://developers.openai.com/codex/cli/reference ↩
-
Releasebot.io, “Codex by OpenAI - Release Notes - March 2026”, https://releasebot.io/updates/openai/codex ↩
-
OpenAI, “CLI Reference –
--listenflag”, https://developers.openai.com/codex/cli/reference ↩ -
GitHub, openai/codex – app-server README (WebSocket Auth), https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md ↩
-
OpenAI, “App Server – Codex Developer Docs (Initialisation)”, https://developers.openai.com/codex/app-server ↩
-
OpenAI, “App Server – Codex Developer Docs (Event Notifications)”, https://developers.openai.com/codex/app-server ↩
-
OpenAI, “App Server – Codex Developer Docs (Approval Flow)”, https://developers.openai.com/codex/app-server ↩
-
Releasebot.io, “Codex by OpenAI – v2 filesystem RPCs”, https://releasebot.io/updates/openai/codex ↩
-
OpenAI, “CLI Reference – app-server generate-ts”, https://developers.openai.com/codex/cli/reference ↩
-
GitHub, openai/codex – app-server README (stability notes), https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md ↩