# How Does the OpenAI Codex Plugin Communicate with Codex via the App Server Protocol?

> Discover how the OpenAI Codex plugin communicates with Codex using the App Server protocol via STDIN/STDOUT for seamless request-response and notification handling.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: internals
- Published: 2026-08-05

---

**The Codex plugin communicates with Codex through a JSON-RPC-based App Server protocol implemented by the `CodexAppServerClient` class, using STDIN/STDOUT transport for bidirectional request-response and notification handling.**

The **openai/codex-plugin-cc** repository implements a lightweight, line-delimited JSON-RPC protocol that enables plugin commands to spawn Codex processes, execute turns, run code reviews, and receive asynchronous updates. This article explains the protocol architecture, transport mechanisms, and implementation details drawn directly from the source code.

## App Server Protocol Architecture

The protocol stack consists of three layers: the **TypeScript interface definitions**, the **core client implementation**, and **high-level workflow helpers**. This design separates protocol semantics from transport concerns, allowing the same client code to work with both spawned processes and brokered connections.

### Protocol Definition Layer

All method signatures, parameter shapes, and notification types are defined in **[`plugins/codex/scripts/lib/app-server-protocol.d.ts`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server-protocol.d.ts)**. This file serves as the contract between the plugin and the Codex service.

Key RPC methods include:

- **`initialize`** — Handshake with `InitializeParams` and `InitializeResponse`
- **`thread/start`** — Begin a conversation thread
- **`turn/start`** — Execute a normal Codex turn with `TurnStartParams`
- **`turn/interrupt`** — Cancel an in-progress turn
- **`review/start`** — Initiate a code review session

The protocol also defines `AppServerNotification` for server-pushed updates like progress events and message deltas.

## Core Client Implementation

The `CodexAppServerClient` class in **`plugins/codex/scripts/lib/app-server.mjs`** encapsulates all communication logic. It extends `AppServerClientBase`, which handles raw I/O operations.

### Connection Establishment

The static `connect()` method determines whether to spawn a new app-server process or connect through a persistent broker:

```typescript
export class CodexAppServerClient {
  // Decision point: spawn vs. broker
  // https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L335
}

```

Broker mode activates when the environment variable `CODEX_COMPANION_APP_SERVER_ENDPOINT` is set. The broker lifecycle management resides in **`plugins/codex/scripts/lib/broker-lifecycle.mjs`**, while endpoint parsing is handled by **`broker-endpoint.mjs`**.

### Transport and Framing

`AppServerClientBase` manages low-level communication over the child process's **STDIN/STDOUT** streams:

- Each request receives a monotonically increasing numeric `id` via `nextId`
- Pending requests are tracked in `this.pending` as promise resolvers
- Responses are matched to pending requests by `id`
- Notifications route to a handler set via `setNotificationHandler`

```typescript
class AppServerClientBase {
  // JSON-RPC framing and pending-request map
  // https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L57-L66
}

```

### Request and Notification API

The client exposes two primary methods for communication:

```typescript
client.request(method, params)   // Returns Promise<RpcResult>
client.notify(method, params)    // Fire-and-forget notification

```

These serialize payloads to line-delimited JSON, write to STDIN, and parse responses:

```typescript
// https://github.com/open/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L80-L98

```

## High-Level Workflow Helpers

The companion script **`plugins/codex/scripts/codex-companion.mjs`** provides convenience wrappers that plugin commands invoke directly.

### runAppServerTurn

Executes a standard Codex turn with automatic client lifecycle management:

```typescript
export async function runAppServerTurn(cwd, options = {}) {
  return withAppServer(cwd, async (client) => {
    // https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L1095-L1101
  });
}

```

### runAppServerReview

Initiates a code review session with streaming updates:

```typescript
export async function runAppServerReview(cwd, options = {}) {
  return withAppServer(cwd, async (client) => {
    // https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L1002-L1008
  });
}

```

Both helpers use `withAppServer()` to ensure proper cleanup regardless of success or failure.

## Handling Asynchronous Notifications

Codex pushes progress updates, message fragments, and state changes via notifications. The plugin registers a handler to process these:

```typescript
client.setNotificationHandler((msg) => {
  if (msg.method === "item/agentMessage/delta") {
    console.log("Agent message:", msg.params);
  }
});

```

The `msg` type is `AppServerNotification` from the protocol definition:

```typescript
// Notification type definitions
// https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server-protocol.d.ts#L74-L75

```

## Complete Usage Example

Below is a minimal implementation showing initialization, turn execution, and notification handling:

```typescript
import { CodexAppServerClient } from "./lib/app-server.mjs";

async function startTurn(workspaceRoot: string) {
  // Establish connection (spawn or broker)
  const client = await CodexAppServerClient.connect(workspaceRoot);
  
  // Protocol handshake
  const init = await client.request("initialize", {
    clientInfo: {
      title: "My Plugin",
      name: "MyAgent",
      version: "1.0.0"
    },
    capabilities: {
      experimentalApi: false,
      requestAttestation: false,
      optOutNotificationMethods: []
    }
  });
  
  // Start the turn
  const turn = await client.request("turn/start", {
    /* TurnStartParams */
  });
  
  console.log("Turn started:", turn);
}

```

## Key Source Files

| File | Purpose |
|------|---------|
| `plugins/codex/scripts/lib/app-server.mjs` | Core `CodexAppServerClient` implementation, transport layer, RPC framing |
| [`plugins/codex/scripts/lib/app-server-protocol.d.ts`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server-protocol.d.ts) | TypeScript definitions for all protocol methods and types |
| `plugins/codex/scripts/codex-companion.mjs` | High-level helpers: `runAppServerTurn`, `runAppServerReview` |
| `plugins/codex/scripts/lib/broker-endpoint.mjs` | Environment variable parsing for broker connections |
| `plugins/codex/scripts/lib/broker-lifecycle.mjs` | Persistent broker session management |

## Summary

- **JSON-RPC over STDIN/STDOUT** forms the transport substrate for all Codex plugin communication
- **`CodexAppServerClient.connect()`** abstracts away spawn vs. broker connection modes
- **Request/response matching** uses numeric IDs with a pending promise map
- **Notifications** enable asynchronous server-pushed updates via configurable handlers
- **High-level helpers** in `codex-companion.mjs` provide the interface that plugin commands actually invoke

## Frequently Asked Questions

### What triggers broker mode instead of spawning a new process?

Broker mode activates when the `CODEX_COMPANION_APP_SERVER_ENDPOINT` environment variable is set. This variable specifies the broker address, causing `CodexAppServerClient.connect()` to establish a persistent connection rather than spawning a fresh app-server process. Broker mode reduces startup latency when multiple plugin instances run concurrently.

### How does the client handle malformed or unexpected JSON-RPC messages?

The `AppServerClientBase` implementation validates incoming JSON against expected structures. Messages lacking required fields or containing mismatched response IDs are logged and discarded. The pending request map ensures that orphaned promises eventually timeout rather than hanging indefinitely.

### Can multiple concurrent requests be in flight simultaneously?

Yes. The client maintains `this.pending` as a Map from request ID to promise resolver, allowing multiple overlapping requests. Each request receives a unique ID from `nextId`, and responses are dispatched to the correct resolver by matching that ID. This enables parallel operations like initializing while streaming notifications.

### Where are notification handler types defined?

Notification handler signatures and the `AppServerNotification` discriminated union are declared in **[`app-server-protocol.d.ts`](https://github.com/openai/codex-plugin-cc/blob/main/app-server-protocol.d.ts)**. The runtime handler is registered via `client.setNotificationHandler()`, and the TypeScript compiler ensures type safety for the callback parameter based on the protocol definitions.