Interactive Explainer

Embedding OpenAI Codex:
The App Server and SDKs

OpenAI Codex is not one tool with one front door. Under the CLI, the IDE extensions, and the cloud sits a single agent you can drive yourself, and there are two ways to do it: the app-server, a JSON-RPC protocol you speak directly, and the SDK built on top of it. This is a tour of both: the loop the agent actually runs, the two controls that keep it in bounds, the protocol that defines what talking to Codex means, and why the TypeScript and Python SDKs are not the same thing underneath.

July 2026 · 17 min read · 5 interactive figures

Ways to run and build on Codex

Ask how to run Codex and the honest answer is: it depends who is asking. A developer living in the terminal reaches for the CLI. Someone on a design team opens the VS Code extension instead. An engineering lead points at the desktop app, or at the cloud dashboard where a dozen tasks run unattended overnight. A platform engineer wiring up CI mutters something about codex exec. And if you are folding Codex into your own product, you will hear two more names entirely: the SDK, and something called the app-server.

None of these people are wrong. Codex is not one tool with one front door. It is a single agent runtime, exposed through multiple entry points, each shaped for a different kind of user.

Some of those entry points are for people who just want the work done, no protocol required:

  • The Codex CLI is a terminal interface for inspecting code, making changes, and running commands without leaving your shell, with sandbox and approval settings you tune per session.
  • The IDE extension, for VS Code and JetBrains, pulls your open files and selections straight into the prompt, lets you review edits in place, and can hand a longer task off to the cloud without breaking your flow.
  • The desktop app, folded into the ChatGPT desktop app as of July 9, works like a command center for running several projects or agents at once.
  • Codex cloud skips your machine entirely, running tasks in isolated cloud environments you dispatch from the web, GitHub, Linear, or Slack, and check on whenever the result is ready.

The others are for people who want to build on Codex, not just click through it:

  • codex exec turns the CLI into a single scriptable command for CI pipelines and automation, streaming its progress as a line-delimited JSON feed. (The Codex GitHub Action is that same command wearing a CI hat, not a separate interface: it drops into a workflow and runs codex exec headlessly to review a pull request.)
  • The Codex SDK, shipped for both Python and TypeScript, runs Codex as a local subprocess - the same codex binary on your own machine, launched and supervised by the SDK - and lets your code start and resume conversations with it, so you can wire Codex into a CI/CD system or build a custom agent host.
  • codex app-server sits underneath both the IDE extension and the Python SDK. It is the JSON-RPC interface, a lightweight convention for exchanging named method calls and their results as JSON messages, that actually defines what talking to Codex means, and the path to reach for when you need a custom UI, your own approval logic, or a host that is not Node or Python.
  • A separate, explicitly experimental mode, codex mcp-server, exposes Codex as an MCP tool so other agents can call it like any other tool.

This article follows two of them all the way down: the app-server and the SDK. The rest each earn their place - codex exec for a one-shot CI run, the GitHub Action for pull-request review, the cloud for work you dispatch and leave, the MCP server for handing Codex to another agent - but the app-server and the SDK are the two you reach for to embed a live, interactive agent, and the two most often mistaken for each other.

From here the article builds up from the inside: first the loop the agent runs, then the two controls that keep it in bounds, then the app-server protocol, and finally the SDKs that wrap it. The map below lays out every entry point side by side; pick a use case and the doors that fit it light up.

Figure 1 - The Ways Into Codex
Use case:
The entry points in three bands: interactive surfaces you drive by hand, programmatic ones you build on, and automation you fire and forget. Pick a use case and the matching doors highlight. Most use cases fit more than one; the map is about tradeoffs, not a single right answer. Hover any door for what it is.

The one distinction that matters for this article: the app-server and the SDK are both embedding paths, and they are easy to conflate. One is a raw protocol you speak; the other is a pre-built client that speaks it for you. Everything below is about telling them apart, and knowing when the pre-built client is enough.

The agent loop

Before you can embed Codex, you have to know what you are embedding. Every surface in that map, from the CLI to the cloud, wraps the same thing: an agent loop. Once you can picture that loop, the app-server protocol later on stops looking like an arbitrary set of methods: each one maps onto a step of that loop.

What a turn really is

A turn is not a single model call. You send one message, and the agent runs an iterative loop: the model reads your request, proposes an action, the runtime carries it out, the result feeds back, and the model decides what to do next. It keeps cycling until it has nothing left to do and emits a final message that ends the turn. A single request like diagnose the failing test and fix it can fan out into many inference calls, shell commands, and file edits along the way. Behind the scenes the loop leans on prefix caching to keep repeated context cheap and compacts history once it grows past a threshold, but the pattern is what matters: propose, execute, observe, repeat.

Run the loop below. It plays a single turn as three trips around the cycle. On each lap the model proposes an action, the runtime carries it out, and the result comes back before the next lap starts. After the third lap the model has what it needs, and ends the turn with a final message.

Figure 2 - One Turn, Many Actions
Ready
One turn against the prompt “diagnose the failing test and fix it.” The model proposes an action, the runtime executes it inside the sandbox, and the result returns for the next lap. Three laps here: run the tests, edit the code, run them again, then a final message ends the turn. One message in; many actions out.

Two thin surfaces over the loop

The plain CLI is that loop with a human at the keyboard. Run codex in a terminal and it reads your request, works, and pauses to show you what it is about to do. Drop the interactive part and you get codex exec "<prompt>": the same loop, headless. No TTY, no prompts, just a run that streams progress to stdout and exits. Add --json and the stream becomes line-delimited events, thread.started, turn.started, item.completed, turn.completed, so a CI job can react to each item as it lands.

But codex exec gives you a subprocess, not a conversation. You get stdout and an exit code. You cannot answer an approval request mid-run, because the policy is fixed the moment the process launches. You cannot start a thread, keep it open, and steer it as new information arrives. You are outside the loop, reading its exhaust. Becoming a participant in the loop, rather than a reader of its output, is what the app-server, and the SDKs built on top of it, are for. But first, the two settings that govern what the loop is even allowed to do.

Two controls: sandbox and approval

Every surface exposes the same two knobs around the loop, and they are the heart of what embedding Codex means. The two are independent. One is a fence around what the agent can touch; the other decides when it has to stop and ask before acting. Mix them up and you give the agent too much freedom or too little.

The sandbox defines technical boundaries. The approval policy decides when the agent must stop and ask before crossing them.

OpenAI Codex documentation, Agent approvals & security

The sandbox: a technical boundary

The sandbox sets what the agent is physically able to do, enforced by the runtime rather than by good behavior. It has three settings. read-only lets the agent inspect files but not edit them or run commands without crossing the boundary. workspace-write, the default for a trusted project, lets it read, edit inside the workspace, and run routine local commands there, but network access is off unless you turn it on. danger-full-access removes the fence entirely: no filesystem or network restriction. The sandbox is the same idea whether you reach it through the CLI's --sandbox flag, the config file, or the SDK.

The approval policy: when to stop and ask

The approval policy is the other axis: given the fence, when must the agent stop and ask you before it crosses? untrusted asks before anything outside a small trusted set of commands. on-request works inside the sandbox freely and asks only to go beyond it, the pragmatic middle. never asks for nothing, so anything the sandbox already forbids simply does not happen. The policy is a dial on how often the loop pauses to hand you a decision, and it means nothing without knowing where the fence is.

What the CLI picks for you. Run codex with no flags in a version-controlled folder and you get its Auto preset: workspace-write paired with on-request approvals. The agent reads, edits, and runs commands inside the workspace on its own, and stops to ask only to touch files outside it or reach the network. Start in a folder that is not under version control and Codex drops the sandbox to read-only instead.

How they compose

Because they are independent, the same action lands differently depending on the pair you pick. Reading a file is always fine. Editing one is free under workspace-write but needs a crossing under read-only. Reaching the network is beyond the fence unless you are at danger-full-access. And whether a crossing prompts you, proceeds, or is quietly refused depends entirely on the approval policy sitting next to it. The grid below lets you set both dials and watch a handful of concrete actions sort themselves into runs, asks first, or blocked.

Figure 3 - Sandbox × Approval
Sandbox:
Approval:
Five actions the agent might attempt, sorted by the two dials. Green runs freely, amber asks first and pauses the turn, grey is blocked by the sandbox with no prompt. Set workspace-write + on-request and edits run while a network fetch asks; set read-only + never and everything but reading is refused outright. The outcomes illustrate the documented definitions of the two controls.

The app-server protocol

Run codex app-server and nothing prints. It is waiting for you to talk to it. This is the interface Codex uses to power its rich clients, the VS Code and JetBrains IDE extensions among them: a long-lived process you drive over JSON-RPC, and a first-class embedding path in its own right. Where codex exec hands you exhaust, the app-server hands you a seat inside the loop.

Opening a connection

By default the app-server speaks over stdio, reading requests on stdin and writing responses and notifications on stdout as newline-delimited JSON. (Other transports exist behind --listen, including an experimental WebSocket, but stdio is the path the official clients use.) The protocol is JSON-RPC 2.0 in shape, close enough to the Model Context Protocol that it will feel familiar if you have written an MCP client.

One quirk to know going in: Codex leaves the "jsonrpc": "2.0" version field off each message, so the stream is JSON-RPC in structure rather than to the letter, and a strict off-the-shelf parser may need loosening. You do not have to reverse-engineer the message shapes, either: codex app-server generate-ts emits TypeScript types for every message and generate-json-schema emits the JSON Schema, both pinned to the exact build you have installed, so your host can track the protocol as it changes rather than chase a separate spec.

Every connection opens with a handshake. Your host sends one initialize request carrying its clientInfo (name, title, version); the server replies with its user-agent and details like codexHome and the platform. You then send an initialized notification, and only now will the server accept anything else. Too early a call gets Not initialized; a repeated initialize gets Already initialized. This happens once per connection, not once per request.

Threads, turns, and items

Three primitives carry everything after the handshake. A thread is a conversation. A turn is one round within it, triggered by a user input. An item is a single thing the agent produces inside a turn: a reasoning step, a shell command, a file edit, a message. You open a thread with thread/start (or thread/resume to reopen, thread/fork to branch), and the server confirms with thread/started. Threads persist, so a host can disconnect and pick the same conversation back up later by id.

To make the agent work, send turn/start against a thread id with the user's input. The response returns immediately with status: "inProgress": accepted, not finished. From here your host stops sending requests and starts reading notifications. The server emits turn/started, then a stream of item events: item/started, item-specific deltas like item/agentMessage/delta as text streams in token by token, and item/completed. Every item follows that same sequence, so a host can render progress live instead of waiting for the whole turn. The turn ends with turn/completed and its token usage.

The approval round-trip

The most important moment in that stream is the one where the server asks you a question. When the agent wants to run a command the approval policy will not wave through, the app-server does not just proceed. It sends your host a request, item/commandExecution/requestApproval (or item/fileChange/requestApproval for an edit), carrying the command, its working directory, and a reason. The turn blocks here: nothing happens until your host replies with a decision, a single payload like { "decision": "accept" }, or decline, or acceptForSession. Once you answer, the server sends serverRequest/resolved and the item runs to item/completed. Of everything the app-server gives you that codex exec cannot, live item streaming, resumable threads, a UI you render yourself, this is the sharpest: a live seat at the point of decision. The figure below steps through one full turn, approval and all. Its Host toggle switches who is speaking the protocol: Custom host is your own code, sending each request and answering the approval itself, while Python SDK as host is that SDK speaking the very same protocol on your behalf, folding the whole exchange into a single call and answering the approval from a fixed policy instead of asking you.

Figure 4 - One Turn, Over the Wire
Step 1 / 15 Host:
One full app-server turn between your host, the app-server, and the agent core. Step through it: the amber approval round-trip is where the turn blocks until your host answers - and flipping the host to the Python SDK is what decides who answers. The names are the app-server's own JSON-RPC methods.

The two SDKs

Most of the time you do not want to write a JSON-RPC host by hand. You want to call a function, pass a prompt, and get an answer back. That is what the Codex SDK is: a pre-built host so your code does not have to speak the protocol itself. It ships for both TypeScript and Python, and the two share a surface, start a thread, run a prompt, resume by id, but they are not the same thing underneath, and the difference decides what you can build.

Which door to pick

Reach for an SDK when you want a programmatic caller and are content to let a policy handle approvals: a CI job, a batch script, an internal tool that starts a task and reads the result. Prefer the Python SDK if you want typed sandboxing and the raw event stream; the TypeScript SDK if you are already in Node and want the lightest wrapper.

Reach past both to the app-server directly the moment you need any of three things: to render the agent's work live, item by item, in your own interface; to put a human in front of each approval decision; or to run on a host that is not Node or Python. Those three needs are exactly what the SDKs fold away, and the rest of this section shows where each one draws the line.

TypeScript: a wrapper over codex exec

import { Codex } from "@openai/codex-sdk"; const codex = new Codex(); const thread = codex.startThread(); const turn = await thread.run("Diagnose the test failure and propose a fix"); console.log(turn.finalResponse); console.log(turn.items);

Clean as that looks, the TypeScript SDK does not speak the app-server protocol at all. Its own README is explicit: it wraps the codex CLI, spawning codex exec --experimental-json and reading that command's line-delimited events. So the events it surfaces are the codex exec schema, the dotted thread.started, item.completed, turn.completed names from the baseline, not the app-server's slash-named protocol. Its runStreamed() gives you those structured events, but there is no approval-request event in that schema at all: approval behavior is fixed when the process spawns. Sandbox control is likewise the CLI's --sandbox flag, not a typed enum. The TypeScript SDK is, in effect, the ergonomic version of the baseline you already met.

Python: the protocol, wrapped

from openai_codex import Codex, Sandbox with Codex() as codex: thread = codex.thread_start(sandbox=Sandbox.workspace_write) result = thread.run("Explain this repository in three bullets.") print(result.final_response)

The Python SDK is a different animal. Its CodexClient describes itself as a JSON-RPC client for codex app-server over stdio, and its start() spawns exactly that. This is the real protocol, wrapped. It gives you a first-class Sandbox enum (read_only, workspace_write, full_access), an ApprovalMode, and, through thread.turn(...) and its .stream(), the raw protocol notifications rather than the coarser codex exec events. When the figure above is in Python-SDK mode, this is the SDK it means: the one whose thread.run() collapses the entire exchange, the handshake, the turn, the streaming items, into one blocking call that returns a TurnResult.

When an SDK is enough, and when it is not

That collapse is the tradeoff, and it is worth being precise about, because it is what tells you which door to pick. Both SDKs absorb the connection lifecycle, the message correlation, and the version pinning you would otherwise own. What they also absorb is the one message you might most want to hold: neither SDK's public API lets your code answer an individual approval request. The Python SDK auto-answers from a fixed ApprovalMode policy; the TypeScript SDK never sees an approval event at all.

Figure 5 - Same Surface, Different Depths
SDK:
The same six questions, asked of each SDK. The TypeScript SDK spawns codex exec and inherits its limits; the Python SDK speaks the app-server protocol directly and exposes far more of it. The last row, approval, is the limit they share. A greyed cell is a capability that surface does not give you.

One runtime, one protocol

Step back from the protocol details and the idea is simple. Codex is not one tool with one front door; it is a single agent runtime behind many of them - CLI, IDE, desktop, cloud, and the two you build on. This whole article has been about those two. The SDK is the convenience: a pre-built client that resolves the approval for you. The app-server is the thing itself: the raw protocol where that decision stays yours.

Where the runtime is heading. In June 2026 OpenAI moved to make the cloud a first-class place to build, announcing it would acquire Ona (formerly Gitpod) for its secure cloud execution technology: persistent, checkpointed environments that let Codex “take on longer-running work, even when laptops are closed.” What moves is where the agent executes, not how your code talks to it.

The interface, though, is the constant. The thread and turn model, the item stream, the approval round-trip stay put whether the agent runs on your laptop or in a datacenter you never see. Learn the protocol once, at the app-server, and that same mental model carries whether the agent is running in your terminal, behind your SDK call, or in a cloud environment you never had to keep awake.