How Codex Router Works — Architecture & Request Flow
Codex Router is a local service between coding-agent clients and external model providers. Codex remains its primary frontend, while the same authenticated model registry can also be published to DeepSeek Harness, Gemini CLI, Cursor, Claude Code, OpenClaw, opencode, pi, omp, Command Code and Hermes Agent. This page explains the request flow and security boundaries.
Why a router is needed
The Codex App expects the Responses API and a Codex-shaped model catalog. Kimi and DeepSeek expose OpenAI-compatible Chat Completions APIs with different authentication and request details. A router is needed to bridge those contracts while leaving native GPT traffic on the normal ChatGPT Codex backend.
The four pieces
- A generated catalog places external models beside native GPT models in Codex’s
model_catalog_json. - A dispatcher chooses native or external routing by namespaced model ID — anything starting with
deepseek/,kimi-oauth/,anthropic-api/and so on goes to the external path. - LiteLLM translates Responses requests, streams and tool calls to each provider’s native protocol, including OpenAI-compatible Chat Completions and Anthropic Messages.
- Credential forwarders inject only the selected provider’s authentication and strip everything else.
Every listener binds to 127.0.0.1, so the whole service is local to your machine.
Request flow
Codex sends a Responses request to the loopback router on port 4202:
| Port | Listener | Role |
|---|---|---|
| 4202 | Router | Receives client requests and authenticates the caller capability |
| 4200 | LiteLLM | Translates Responses to each provider’s native protocol |
| 4201 | Kimi OAuth | Forwards to Kimi with a refreshed OAuth bearer |
| 4203 | API forwarder | Forwards to API-key providers with the selected key |
For a native GPT model, the router forwards the request to the ChatGPT Codex backend with allow-listed Codex headers and the native model ID — the router never sends a native request to an external provider.
For a registry model that needs protocol translation (for example kimi-oauth/k3), the router validates the caller, passes a gateway model plus an internal key to LiteLLM, which translates the request and hands it to the matching forwarder. The forwarder discards the internal key and injects only the selected provider credential. The stream flows back through LiteLLM as Responses events.
Responses-native external models now go directly through the credential-owning API forwarder, even when LiteLLM is enabled for other routes. Normal turns and remote compaction keep the same request preparation, tool identities, stream validation and credential boundaries. Routes needing translation continue through LiteLLM.
The checkout offers node src/runtime-dependency-requirements.mjs to inspect the selected route’s required services as JSON, or node src/runtime-dependency-requirements.mjs --gateway-required for required or unused. The optional compatibility-layer setting now also governs which routes are published and whether the LiteLLM gateway starts.
Optional compatibility layer
LiteLLM is enabled by default. Installations using only native GPT and Responses-native routes can persistently turn it off with node src/control.mjs compatibility-layer off; use status to inspect the setting and on to restore it. A fresh source checkout can run node src/compatibility-layer-state.mjs off before installation to skip Python setup. After changing the setting, prepare dependencies as needed, restart the router and refresh client catalogs. Chat, Messages, Vertex, OAuth and Ollama routes that need LiteLLM disappear from effective catalogs while it is off; direct requests fail with compatibility_layer_disabled. Credentials and selections remain stored. Homebrew still has a Python build dependency, even when runtime compatibility is off.
At runtime, the router now starts and probes only services required by the selected routes. A native-only or Responses-only selection can therefore leave an unused LiteLLM gateway stopped even while the compatibility layer remains enabled; selecting a route that needs translation starts it.
During a service restart, the router opens its listener while LiteLLM starts. Native turns can proceed immediately; routes that need the gateway wait for it for up to 60 seconds during startup (CODEX_ROUTER_STARTUP_HOLD_MS).
One registry, multiple clients
Codex, DeepSeek Harness, Gemini CLI, Cursor, Claude Code, OpenClaw, opencode, pi, omp, Command Code and Hermes Agent consume the same provider visibility and per-model picker state. Each publisher adapts only the outer client contract—Responses, Gemini, Anthropic Messages or Cursor’s edge—then re-enters the same routed request path. Command Code and Hermes Agent reach the same Anthropic Messages surface Claude Code uses, while opencode, pi and omp receive the authenticated loopback /v1 path. The five published-into clients have no MODEL_ROUTER_TARGET and no second service; enabling a provider or curating a model republishes them together. Native ChatGPT models are exposed to non-Codex clients only after the user explicitly enables shared-session access with chatgpt-session enable.
Credential boundaries
| Route | Incoming Codex credential | Upstream credential |
|---|---|---|
| Native GPT | Allow-listed and forwarded | Existing ChatGPT/Codex authentication |
| Kimi OAuth | Discarded | Kimi CLI OAuth bearer from ~/.kimi-code |
| Kimi API | Discarded | Kimi Platform API key |
| DeepSeek | Discarded | DeepSeek API key |
| GitHub Copilot | Discarded | Fine-grained GitHub token after Copilot entitlement and endpoint validation |
The Codex-to-router and internal-service trust boundaries use two different random keys, each stored with mode 600 or a current-user Windows ACL. External forwarders remove Codex account, installation, attestation and private headers before sending a request upstream.
GitHub Copilot is a catalog-only provider. Its fine-grained token is first validated against GitHub’s Copilot account endpoint, and the returned inference endpoint must be a GitHub-owned Copilot host. Live discovery exposes only the account-visible Responses models that support streaming and tool calls. Available models and usage limits therefore follow the user’s Copilot plan and organization policy.
Security model
- Local caller authentication. The managed base URL contains a separate random capability; the router validates it before reading a model request or contacting any upstream. Codex cannot attach a router-specific header to the built-in provider, so the capability lives in the URL path. Status, migration and support tools redact it.
- No browser access. The router requires JSON content, rejects browser-origin headers, and never grants CORS access.
- Sanitized errors. Network-facing errors are bounded and non-sensitive; raw exception text is replaced.
- No credential leakage. Diagnostics report presence and source, never values. Protected files use mode
600or a current-user ACL.
Transport and compaction
Current Codex builds may use an authenticated Responses WebSocket. The router accepts it on the same caller-capability edge, derives identity per request and preserves quota/error metadata while routing the turn through the same managed Responses path; HTTP remains supported. Request bodies may use Zstandard, gzip, deflate or Brotli, and oversized compressed frames are refused before decoding.
On that WebSocket, Codex can interrupt an active turn and continue on the same connection. The router cancels the local request, keeps completed output and observed usage, discards partial items and marks the interrupted response incomplete. A later turn is not poisoned by duplicate or late interrupts; a continuation that exceeds its bounded history requires full-context recovery.
WebSocket turns now settle at their logical completion, cancellation or failure event rather than waiting for the HTTP connection to close, preserving routed usage and continuation state.
Request delivery and generation outcome are tracked separately. A stream can deliver HTTP 200 yet end failed or incomplete; Control Center’s Dashboard and Status show completed, failed, incomplete, canceled or indeterminate outcomes for recent requests. Billed tokens remain visible for every outcome. Older records show that this outcome was not recorded rather than claiming a successful generation.
For Chat Completions tool-call continuations, streamed reasoning is cached for replay only when the whole chain reaches a supported successful finish. A truncated or failed stream cannot seed a partial reasoning replay.
External Chat Completions providers cannot create OpenAI’s opaque encrypted compaction payload, so the router asks the selected external model for a continuation summary and wraps it in a router-owned kcr1: payload. On replay it converts that payload back to a plain continuation message.
A compaction summary replaces history only after a successful completion event. A partial stream or a JSON response marked incomplete is treated as a failed attempt; observed token usage is still recorded, and a stream that may already have run does not trigger another paid attempt automatically.
Native generation POSTs now default to CODEX_ROUTER_NATIVE_RETRY_POLICY=at-most-once: the router retries only when it has positive evidence that the request was not sent and no output reached the caller, such as a DNS or connect failure, including a reset before TLS completed. An ambiguous reset or edge 5xx may follow an executed request, so it is relayed instead of automatically repeating a possibly billed turn. Operators can explicitly choose availability for bounded retries of uncertain failures before output, accepting possible duplicate execution. Native generation redirects are rejected; configure the final API endpoint. The policy governs only this router’s automatic retries, not retries by Codex or other intermediaries. Native remote-compaction streams now retain a recognizable failure when the connection breaks even if response metadata or the SSE Content-Type is absent.
API-key, routed and vision generation redirects are also refused without following or exposing the Location. The API forwarder reports a non-retryable endpoint configuration error; configure the final provider URL instead.
What stays in Codex
Commands, permissions, MCP tools, skills, agent loops and task state remain in Codex. The router handles model inference and external-model compaction; it cannot add a capability the selected model or provider does not implement.
For native-origin collaboration subagents, the router relays the exact native task payload through the already-authenticated native Codex backend — the relay requires an active ChatGPT sign-in and fails closed in login-free mode rather than forwarding unreadable ciphertext to an external provider. A route is offered as a v2 subagent when the registry carries reviewed collaboration evidence or the operator explicitly selects it with subagents mode selected and subagents set <slug> on. Local diagnostic records do not certify or automatically promote a route; the speculative subagents certify command has been removed. An explicit selection expresses intent, not proof that delegation works. Each generated routed-agent definition also writes that model’s configured model_reasoning_effort, so a low-effort parent does not silently force every child onto low effort.
On Responses-native child routes, that effort override travels in reasoning.effort; the flat reasoning_effort alias is reserved for Chat Completions compatibility. This avoids a pre-upstream HTTP 400 on delegated Responses turns.
Azure Responses routes now use Codex’s plaintext collaboration handoff when an Azure Sol parent delegates to a configured Luna subagent. The router restores the client-visible tool namespace and preserves the configured default when no child model is specified. Use the current Codex collaboration tool namespace for this supported handoff and redaction path.
When a model that looked eligible cannot be delegated to, ask why instead of spawning one and reading codex exited 1:
./bin/model-router codex subagents explain <provider/model>
The command names the first blocker plus the command that fixes it, and distinguishes a typo from an uncurated model from a native slug. It also reports whether a route’s v2 claim comes from the registry or your own selection. It is read-only and consumes no quota. Enabled generic providers are recognized too, so they no longer receive a false disabled-provider diagnosis.
Native tools and the custom-model skill pack
For routed Chat Completions and Messages models, CODEX_ROUTER_APP_CONNECTORS=none can withhold Codex app connector functions from prompts, or a comma-separated list such as airtable,gmail can keep only those connectors eager. Unset or all leaves the ordinary connector set eager. Stored calls and explicit tool choices can restore withheld definitions; Codex’s own app tools, collaboration tools and custom tools remain available. Set the variable in the router service environment and restart the service.
Routed custom models keep Codex’s native threads, automations, in-app browser and computer-use tools. Because some models need clearer tool-calling guidance, the installer adds four managed skills: codex-router, codex-app-threads, codex-in-app-browser and codex-computer-use. The installer skips any same-named skill owned by the user, the uninstaller removes only router-managed copies, and doctor verifies that the installed pack matches the checkout and current app toolset.
Tool availability still depends on the route. Azure KMAMC Responses cannot serve Codex’s image-generation extension, so the router removes its hosted and namespaced image-generation tool declarations before forwarding. Image generation remains unavailable on that route; other providers keep their declarations.
A completed routed tool call with empty arguments is accepted only if the tool schema takes no inputs. For an input-taking tool it is rejected before Codex stores it, preventing an unusable call from breaking the thread; an older empty call whose only result was Codex’s parse failure can be omitted so the thread recovers.
When projecting tool schemas for a routed model, the router now preserves every valid operation alternative and the original client execution constraints. This keeps selectable tool modes available without relaxing reference rules.
Old tool results are omitted from later model context only after an exact private snapshot and signed local readback proof exist in the same conversation and permission context. Use ./bin/control tool-result-aging read <handle> to retrieve the original text in pages. If local readback is unavailable or storage fails, the full result stays in context; a receipt never asks the agent to rerun a potentially changing tool. Streamed tool-validation rejection ends that turn instead of automatically replaying it.
Context compaction now follows the same aging policy: it keeps the newest four results intact and no longer applies separate heuristic shaping to recent tool output. A whole-history summarizer without execution tools receives exact results.
Desktop clients may wrap the signed readback proof in a tool-result JSON envelope. The router now recognizes bounded nested wrappers while still checking the signature, expiry, conversation and permission scope; an invalid or inaccessible proof keeps the full tool output.
Reads of instruction files, skills and explicit runtime documentation are preserved during tool-result aging, including Codex computer-use bootstrap instructions. Ordinary page snapshots and data results remain eligible for archiving. Given a valid archive handle, ./bin/control tool-result-aging read <handle> --scope-search --find 'literal text' searches exact retained text only within the same signed conversation and permission scope. Follow nextCursor with --cursor N --index HASH when present; complete: false means the search cannot establish absence. No source command is rerun.
Advanced opt-in settings can lower a routed model’s aging floor from the 32 KiB default to 8–32 KiB and enable a local readback notice. Native OpenAI aging remains off by default. The newest four results and results without verified local recovery remain protected; the notice is disabled by default and suppressed after a failed readback probe. Smaller histories can add probe costs or lose cache hits, so compare actual usage before enabling them. See the upstream per-model history policy.
When native aging is enabled, Responses Lite sessions can also archive eligible tool results declared incrementally in additional_tools; explicit tool restrictions and the signed local readback requirement still apply.
The native catalog is preserved
The integration preserves the built-in OpenAI provider, native GPT models, ChatGPT sign-in, profiles, MCP settings, project trust and reasoning defaults. It adds one marked root block and one inert custom-provider table to the Codex config, and disable restores the previous values exactly.
While the router service runs, it checks the signed-in account’s native model catalog every five minutes and republishes changes. CODEX_ROUTER_NATIVE_CATALOG_POLL_INTERVAL_MS can lengthen that interval to at least five minutes. Codex loads model_catalog_json at startup, so fully quit and reopen Codex to see a newly published model; ./bin/refresh-catalog remains available for manual refresh.
Related: installation, models overview, troubleshooting.