# Route drivers

Codex MMO is model-maker neutral, but protocols and access products are not interchangeable. In MMO generation 8, an executable `route` selects one driver and records its API operator, access product, wire protocol, billing mode, transport capabilities, credential names, and resource group.

The filename remains `PROVIDER_DRIVERS.md` for documentation organization only; the public catalog object is `routes`, not the old provider-shaped schema.

## Driver matrix

| Driver | Execution path | Typical use | Managed gateway |
|---|---|---|:---:|
| `codex_builtin` | Codex reserved provider | ChatGPT-backed Codex models | no |
| `codex_custom` | Direct Codex Responses endpoint | Conforming private/third-party Responses API | no |
| `codex_oss` | Codex native local integration | Ollama or LM Studio | no |
| `switchyard` | Immutable snapshot-specific route | Chat, Responses, Anthropic Messages, compatible local APIs | yes |
| `catalog_only` | Metadata only | Media/service endpoint or unsupported transport | no execution |

## Common route identity

```toml
[routes.example_openai_chat]
name = "Example API"
api_operator = "example"
access_product = "example_api"
wire_protocol = "openai_chat"
billing_mode = "api"
driver = "switchyard"
base_url = "https://models.example.test/v1"
credential_envs = ["EXAMPLE_API_KEY"]
resource_group = "example_pool"
transport_modalities = ["text"]
transport_output_modalities = ["text"]
tool_calling = true
parallel_tool_calls = false
max_retries = 1
```

Required identity fields are never inferred from a route key. A recognized field that does not apply to the selected driver is rejected instead of retained inertly.

## `codex_builtin`

```toml
[routes.codex_chatgpt_builtin]
name = "Built-in Codex with ChatGPT authentication"
api_operator = "openai"
access_product = "chatgpt_codex"
wire_protocol = "codex_builtin"
billing_mode = "chatgpt_subscription"
driver = "codex_builtin"
provider_id = "openai"
auth = "chatgpt"
resource_group = "chatgpt_subscription"
transport_modalities = ["text", "image"]
tool_calling = true
parallel_tool_calls = true
```

This preserves Codex’s native authentication, entitlement, tools, images, and active model catalog. A generated home uses the configured base home’s file-backed `auth.json` according to `auth_link_mode`; Codex 0.149 keyring-only state cannot be transferred to another `CODEX_HOME`.

Account-visible models and capabilities remain authoritative. The bundled catalog is a release baseline, not an entitlement claim.

## `codex_custom`

```toml
[routes.private_responses]
name = "Private Responses"
api_operator = "example"
access_product = "private_responses"
wire_protocol = "openai_responses"
billing_mode = "api"
driver = "codex_custom"
base_url = "https://llm.example.internal/v1"
wire_api = "responses"
credential_envs = ["PRIVATE_LLM_API_KEY"]
transport_modalities = ["text", "image"]
preserves_tool_media = true
tool_result_modalities = ["text", "image"]

[routes.private_responses.http_headers]
"X-Static-Tenant" = "engineering"

[routes.private_responses.env_http_headers]
"X-Private-Token" = "PRIVATE_HEADER_VALUE"
```

Use direct mode only when the endpoint faithfully implements Responses and direct transport is preferable to translation. The Codex process necessarily receives declared credential/header variables, so place sensitive work in a suitable OS boundary.

`wire_api` is currently `responses`. Direct retry fields are `request_max_retries`, `stream_max_retries`, and `stream_idle_timeout_ms`. Codex interprets zero as immediate expiry, not “disabled,” so route validation rejects it. Generated homes raise the effective idle floor to at least one hour and twice the role's warning-only stall interval. That value detects a genuinely broken stream; it is not a task deadline, goal budget, or instruction for the model to keep time.

## `codex_oss`

```toml
[routes.ollama_codex_oss]
name = "Codex native Ollama OSS mode"
api_operator = "local"
access_product = "ollama"
wire_protocol = "codex_oss"
billing_mode = "local"
driver = "codex_oss"
provider_id = "ollama"
resource_group = "local_gpu_0"
transport_modalities = ["text"]
```

Supported `provider_id` values are `ollama` and `lmstudio`. Switchyard variants also exist for compatible local Chat endpoints when common routing and telemetry matter more than native transport.

## `switchyard`

Switchyard 0.2.0 supports these route protocols:

- `openai_chat`;
- `openai_responses`;
- `anthropic_messages`.

The compiler emits only routes used by one immutable snapshot. A profile with no Switchyard binding starts no gateway. Managed ingress is restricted to a loopback IP literal because the generated gateway has no client-auth layer.

Bundled Switchyard routes include:

- OpenCode Go Chat, Responses, and Anthropic Messages through `OPENCODE_API_KEY`;
- OpenCode Zen Chat, Responses, and Anthropic Messages through the same `OPENCODE_API_KEY`;
- OpenRouter Chat through `OPENROUTER_API_KEY`;
- Z.AI Coding Plan Chat and Responses through `ZAI_CODING_API_KEY`;
- Z.AI General API through the separately explicit `ZAI_API_KEY`;
- a bundled llama.cpp model binding;
- direct OpenAI/Anthropic and LM Studio/Ollama route definitions for explicit
  user catalog extensions. A route without a model record is not an executable
  bundled model binding.

Switchyard receives `extra_headers`, not environment-backed per-header mappings. API authentication uses `credential_envs`. MMO emits `max_retries = 1` by default, meaning one additional attempt; the accepted range is 0–10.

### Temporary Codex MCP namespace bridge

Codex 0.149 represents MCP servers as Responses `namespace` tools. Switchyard 0.2.0's Responses decoder retains top-level function and ID tools but does not retain that namespace shape. For a gateway whose recorded executable version is exactly `0.2.0`, MMO therefore asks the same Codex app-server for its granted MCP tool schemas, registers equivalent flat `mmo_mcp__SERVER__TOOL` dynamic functions when a thread is first created, and routes each dynamic call back through Codex's `mcpServer/tool/call`. The MMO-owned prefix is deliberate because Codex rejects dynamic declarations using its reserved native `mcp__` prefix. Codex remains the MCP connection owner and enforces its configured server/tool allowlists; the bridge does not read supervisor files or connect to MCP servers independently. MCP text, image, audio, error state, and non-redundant structured content are preserved; a structured value is omitted only when an existing text block parses to that exact value (including the common single-key result wrapper), avoiding duplicate context without discarding distinct information. Durable Codex threads retain the dynamic tool declarations, while every replacement host reconstructs only the in-memory routing map.

This is a release-scoped compatibility shim, not a second permanent MCP architecture. It is disabled for every Switchyard version other than `0.2.0`. The baseline/bridge guard test deliberately fails when the reviewed Switchyard baseline changes. Before accepting a newer baseline:

1. prove with the exact candidate binary that Codex 0.149 namespace tools reach both the Go and Zen model routes and that a real MCP call completes;
2. prove new-thread, resumed-thread, worker, allowlist, error, and cancellation behavior without `dynamicTools`;
3. remove the bridge constants, dynamic schema discovery/call routing, root/worker injection, and their compatibility tests;
4. update this section, the architecture/veracity records, changelog, package manifest, and installed runtime.

An operator-supplied newer Switchyard is never assigned the 0.2.0 shim merely because the profile uses the `switchyard` driver. Its native namespace path must pass live qualification before production use.

#### Open removal tracker

Upstream [Switchyard PR #384](https://github.com/NVIDIA-NeMo/Switchyard/pull/384), merged as commit [`c7beccd4891fa5cfe3a3b94fdd376f5765864507`](https://github.com/NVIDIA-NeMo/Switchyard/commit/c7beccd4891fa5cfe3a3b94fdd376f5765864507) on 2026-08-20, implements native Codex namespace preservation. The current `v0.2.0` tag predates that commit, and no published tag contains it as of 2026-08-23. This tracker remains open until the first official `switchyard-server` release whose tag contains that commit is available.

The release containing that commit is an upgrade/removal trigger, not permission to retain both paths. The baseline bump must be one atomic change: run the qualification matrix above against the exact released binary, delete the dynamic bridge and its version constant, then update the pin. The existing `test_temporary_switchyard_bridge_must_be_removed_when_baseline_advances` test blocks a baseline-only update. If native qualification fails, keep `0.2.0` pinned and record the failure; do not extend the bridge to the newer release.

Z.AI Coding Plan uses distinct documented bases by wire protocol: Responses/Codex uses `https://api.z.ai/api/v1`, while OpenAI-compatible Chat uses `https://api.z.ai/api/coding/paas/v4`. Switchyard appends the protocol endpoint suffix. MMO does not retry one route through the other and does not fall back to the separately credentialed General API.

The Coding Plan Anthropic-compatible endpoint remains catalog-only: its documented Bearer authentication cannot be represented by Switchyard 0.2.0’s fixed Anthropic `x-api-key` behavior. No hidden transport fallback is attempted.

## `catalog_only`

Catalog-only records preserve complete inventory information without pretending the endpoint can run a Codex agent. Examples include OCR, image/video generation, audio transcription, hosted application agents, Google-native Zen models without an installed driver, and the incompatible Coding Plan Anthropic route.

```toml
[routes.zai_general_catalog]
name = "Z.AI media and specialist API catalog"
api_operator = "zai"
access_product = "zai_general_api"
wire_protocol = "catalog_only"
billing_mode = "catalog_only"
driver = "catalog_only"
transport_modalities = ["text", "image", "video", "audio", "file"]

[models.zai_general_catalog__glm_image]
maker = "zai"
route = "zai_general_catalog"
upstream_id = "glm-image"
kind = "image_generation"
agent_compatible = false
```

## Driver-specific fields

| Field | Drivers | Meaning |
|---|---|---|
| `api_operator` | all | API operator identity |
| `access_product` | all | Plan/service identity |
| `wire_protocol` | all | Exact protocol enum |
| `billing_mode` | all | `api`, `subscription`, `chatgpt_subscription`, `local`, or `catalog_only` |
| `provider_id` | built-in/OSS | Codex-owned provider identifier |
| `base_url` | custom/Switchyard/catalog-only | Absolute HTTP(S) API root |
| `credential_envs` | custom/Switchyard/catalog-only | Ordered valid environment names |
| `wire_api` | custom | Direct Codex API; `responses` |
| `http_headers` | custom | Static Codex headers |
| `env_http_headers` | custom | Header name to environment name |
| `extra_headers` | Switchyard | Static Switchyard headers |
| `max_retries` | Switchyard | Additional attempts, 0–10 |
| `request_max_retries` | custom | Direct request retries |
| `stream_max_retries` | custom | Stream reconnections |
| `stream_idle_timeout_ms` | custom | Provider stream-idle floor (at least 1,000 ms); generated app-server configs raise it through the role's execution window plus finalization grace when that is longer |
| `transport_modalities` | all | Inputs the complete path preserves |
| `transport_output_modalities` | all | Outputs the complete path preserves |
| `preserves_tool_media` | all | Whether tool media survives transport |
| `tool_result_modalities` | all | Preserved tool-result media types |
| `supports_documents` | all | Complete file/document path |
| `openrouter_policy` | OpenRouter only | Route-wide typed serving policy |

There are no singular credential aliases or legacy header-name aliases in the v2 contract. Use exactly `credential_envs`, `http_headers`, `env_http_headers`, and `extra_headers` for their documented drivers.

`base_url` must be an absolute HTTP(S) root with a nonempty host. Userinfo, control/space characters, backslashes, queries, and fragments are rejected. Header names and values are validated before downstream configuration is generated.

## Model transport fields

```toml
[models.example_openai_chat__example_coder]
maker = "example"
route = "example_openai_chat"
upstream_id = "example-coder"
kind = "chat"
agent_compatible = true
modalities = ["text"]
output_modalities = ["text"]
tool_calling = true
parallel_tool_calls = false
structured_output = true
context_window = 200000
max_output_tokens = 32000
reasoning_levels = ["none", "high"]
default_reasoning = "high"
resource_group = "example_pool"
```

The compiler checks model capabilities against route capabilities. A maker/model name alone is never evidence that a particular transport preserves tools, images, documents, reasoning selectors, or structured output. `extra_body` is supported only for Switchyard-backed model targets and must contain finite JSON-compatible values.

## Credentials

Credential assignments belong in `~/.config/codex-mmo/credentials.env` or the launch environment. Profiles, catalogs, snapshots, and generated static configuration store names, never values. Worker/gateway environments filter conventional unrelated credential names and explicitly re-admit only the selected route and Tool MCP variables.

That filtering is name-based, not an OS sandbox or secret oracle. Use conventional secret names and sanitize unusual variables before launching untrusted work.

## Adding a route

Add a generation-8 fragment under `~/.config/codex-mmo/catalog.d/`, then run:

```bash
codex-mmo catalog verify
codex-mmo catalog routes
codex-mmo catalog models --route ROUTE_KEY
codex-mmo profile validate PROFILE
```

No runtime plugin is needed when an existing driver fits. Executable driver plugins are intentionally outside profile packs.
