Controlling Codex via JSON-RPC with Codex App Server
DEV Community

Controlling Codex via JSON-RPC with Codex App Server

Starting Codex App Server

Have you heard of Codex App Server? Instead of interacting with Codex through the CLI, Codex App Server lets you control a Codex agent over JSON-RPC. In other words, you can build your own GUI, TUI, task queue, or orchestration layer on top of Codex.

This article focuses on keeping a fast pace, so if you're looking for precise protocol details and specifications, please refer to the official README: https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md

Starting Codex App Server is as simple as running:

codex app-server

That's it. The server will begin listening on standard input. From there, you simply send JSON requests that follow the protocol (technically JSONL, so each request must be on a single line). This allows your own applications to launch and control Codex agents programmatically through Codex App Server.

Conversation Flow

A typical interaction looks like this:

  • Send an initialize request to initialize the server.
  • Start an agent conversation with thread/start.
  • Give the agent work using turn/start.
  • Wait until you receive turn/completed.
  • Repeat steps 3-4 until the task is finished.
  • Done.

Understanding Thread and Turn

When I first started learning the protocol, I found it difficult to picture the relationship between Thread and Turn. The following example helped everything click:

Thread
โ”œโ”€โ”€ Turn 1
โ”‚   User:
โ”‚     "Please investigate this issue."
โ”‚   Agent:
โ”‚     - Search the codebase
โ”‚     - Read relevant files
โ”‚     - Identify the root cause
โ”‚   turn/completed
โ”œโ”€โ”€ Turn 2
โ”‚   User:
โ”‚     "Please fix it."
โ”‚   Agent:
โ”‚     - Edit files
โ”‚     - Run tests
โ”‚   turn/completed
โ”œโ”€โ”€ Turn 3
โ”‚   User:
โ”‚     "Create a pull request."
โ”‚   Agent:
โ”‚     - Commit changes
โ”‚     - Push to Git
โ”‚     - Create a PR
โ”‚   turn/completed
โ””โ”€โ”€ End of Thread

A Thread is roughly equivalent to a Codex CLI session, while a Turn represents a single exchange between the user and the agent.

Hands-on

By default, Codex App Server accepts JSON-RPC messages through standard input. If you copy and paste the following JSON into a running server (after converting it into a single-line JSONL message), you'll be able to interact with a persistent Codex agent.

{
  "id": 1,
  "method": "initialize",
  "params": {
    "clientInfo": {
      "name": "tutorial",
      "title": "Tutorial",
      "version": "1.0"
    }
  }
}

After sending it, you'll receive a response similar to this:

% codex app-server
{"id":1,"method":"initialize","params":{"clientInfo":{"name":"tutorial","title":"Tutorial","version":"1.0"}}}
{"id":1,"result":{"userAgent":"tutorial/0.144.5 (Mac OS 26.5.0; arm64) xterm-256color (tutorial; 1.0)","codexHome":"~/.codex","platformFamily":"unix","platformOs":"macos"}}
{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"xxx.local","installationId":"2124e102-01b2-4b60-94c3-dabb22d2913f","environmentId":null}}

At this point, the server has been successfully initialized.

Next, start a conversation with an agent by sending a thread/start request:

{"id":2,"method":"thread/start","params":{}}

The response will look something like this:

{"id":2,"result":{"thread":{"id":"019f7960-95a5-7140-8749-a3cf59e0ffab","extra":null,"sessionId":"019f7960-95a5-7140-8749-a3cf59e0ffab","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"historyMode":"legacy","modelProvider":"openai","createdAt":1784447800,"updatedAt":1784447800,"recencyAt":1784447800, ...}
{"method":"thread/started","params":{"thread":{"id":"019f7960-95a5-7140-8749-a3cf59e0ffab","extra":null,"sessionId":"019f7960-95a5-7140-8749-a3cf59e0ffab", ...}
{"method":"mcpServer/startupStatus/updated","params":{"threadId":"019f7960-95a5-7140-8749-a3cf59e0ffab","name":"codex_apps","status":"starting","error":null,"failureReason":null}}
{"method":"mcpServer/startupStatus/updated","params":{"threadId":"019f7960-95a5-7140-8749-a3cf59e0ffab","name":"context7","status":"starting","error":null,"failureReason":null}}
{"method":"mcpServer/startupStatus/updated","params":{"threadId":"019f7960-95a5-7140-8749-a3cf59e0ffab","name":"notion-mcp-server","status":"starting","error":null,"failureReason":null}}
{"method":"mcpServer/startupStatus/updated","params":{"threadId":"019f7960-95a5-7140-8749-a3cf59e0ffab","name":"notion-mcp-server","status":"ready","error":null,"failureReason":null}}
{"method":"mcpServer/startupStatus/updated","params":{"threadId":"019f7960-95a5-7140-8749-a3cf59e0ffab","name":"codex_apps","status":"ready","error":null,"failureReason":null}}
{"method":"mcpServer/startupStatus/updated","params":{"threadId":"019f7960-95a5-7140-8749-a3cf59e0ffab","name":"context7","status":"ready","error":null,"failureReason":null}}

The output may look a little noisy, but you'll notice events such as thread/started, followed by the startup of MCP servers. If you've used the codex command before, this should feel very familiar-the same initialization process is happening behind the scenes.

Next, let's send a task to the agent using turn/start. For this example, we'll ask it to introduce itself with the following prompt:

{"id":3,"method":"turn/start","params":{"threadId":"019f7960-95a5-7140-8749-a3cf59e0ffab","input":[{"type":"text","text":"Hello! Please introduce yourself."}]}}

The response is quite long, so I'll omit most of it here. The message worth paying attention to is this one:

{
  "method": "item/completed",
  "params": {
    "item": {
      "type": "agentMessage",
      "id": "msg_0e30b319d2fc7416016a5c854b85a0819192efde569032bd15",
      "text": "Hello! I'm Codex. \n\n I can help with code investigation, implementation, testing, code reviews, Git operations, pull request preparation, and more within this workspace. My general approach is to understand the existing architecture and repository conventions before making small, safe changes. \n\n For this repository, I'll follow your instructions and respond primarily in Japanese.",
      "phase": "final_answer",
      "memoryCitation": null
    },
    "threadId": "019f7960-95a5-7140-8749-a3cf59e0ffab",
    "turnId": "019f7968-a116-7f13-bf24-c56fddd4373d",
    "completedAtMs": 1784448333474
  }
}

The response is divided into several types of events. The most important one is item/agentMessage/delta, which streams the agent's response in small chunks as it is generated. Once the response is complete, the full message is available in the item/completed event.

At this point, you've seen the complete interaction flow. From here on, you simply continue sending turn/start requests to assign new tasks. By repeating this process until all work is finished, you can drive the agent through an entire workflow programmatically.

Why Use Codex App Server?

When working with Codex day to day, you typically launch the interactive console with the codex command. However, if you keep a Codex App Server running locally and send JSONL requests to it instead, you unlock a number of interesting possibilities:

  • Run Codex agents in headless mode.
  • Send tasks directly to the App Server without opening a new codex session and manually entering prompts for each task.
  • Build your own custom clients, such as GUIs, TUIs, or web applications.

OpenAI has also introduced Codex Symphony, a specification for orchestrating multiple coding agents. Building your own orchestration layer on top of Codex App Server would be an interesting project.

Since Codex App Server continuously emits events that reflect the agent's state, you can also monitor its progress in real time. This opens the door to building tools similar to Herdr, with features for tracking and visualizing the state of long-running agent workflows.

Tasq: An Open-Source Project I Built

As a bit of self-promotion, I'd also like to introduce Tasq, an open-source project I built on top of Codex App Server. If you're curious about how it works, I recommend checking out the demo video in the README-it gives a good sense of the overall workflow and user experience.

Tasq is built around local issue management. Once you add implementation plans created by tools like Claude Code or Codex to your backlog, Tasq can automatically drive coding agents through the entire workflow-from implementation all the way to creating a pull request.

Thanks for reading! If Tasq sounds interesting, I'd love for you to give it a try. Feedback, issues, and contributions are always welcome.

Comments

No comments yet. Start the discussion.