--- title: Agent Configuration description: Multi-agent surfaces, delegation guidance, preferred models, fallback chains, native-default sync, and effort caps. --- Agent settings control which Codex collaboration surface is advertised and how opencodex guides, routes, and limits delegated work. ## Agent fields ### Skills catalog refresh `skills.catalog_refresh` accepts `"per_session"` (the default) or `"per_turn"` in opencodex's `config.json`. Session mode reuses the first received skills instructions for a conversation, protecting the prompt cache prefix from catalog changes between turns. Turn mode forwards the client's current catalog. See [Keeping the skills catalog stable](/guides/codex-prompt/#keeping-the-skills-catalog-stable) for configuration and snapshot lifetime details. ### Astra roster upgrade On the first start after upgrading, existing `subagentModels` lists receive `gpt-6-astra` first. The first four unique non-Astra choices are retained and the old fifth choice is dropped. If `gpt-5.5` is retained, it moves to the end. The previous default list therefore becomes Astra, Sol, Terra, Luna, 5.5. An unset legacy list receives the current defaults; an explicit empty legacy list becomes `["gpt-6-astra"]`. Existing Astra entries are not duplicated. The current default is `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-luna`. On the first start after upgrading, a stored list is cleaned of retired rows: `gpt-5.6-sol` and `gpt-5.6-luna` become `gpt-6-sol` and `gpt-6-luna` in the same position, and every other `gpt-5.5` or `gpt-5.6` model is removed. Ids with a `/` (routed `provider/model` or account-qualified choices) are left as written. A list that held only retired rows receives the current default; an empty list stays empty. GPT-6.1 Sol replaced GPT-6 Sol as the default on September 30, 2026. On the first start after that upgrade, a bare `gpt-6-sol` in a stored list becomes `gpt-6.1-sol` in the same position (without a duplicate if 6.1 Sol is already listed). GPT-6 Sol stays available: add it back afterwards and it stays. The internal `subagentModelsVersion` marker (currently `3`) makes each step a one-time upgrade. Afterwards you can reorder, remove Astra, or save an empty list without startup changing your choices again. Disabled models remain disabled. Astra availability still depends on upstream support for your account. | Field | Type | Default | Meaning | | --- | --- | --- | --- | | `multiAgentMode?` | `"v1" \| "default" \| "v2"` | `"default"` | `v1` stamps every catalog model as v1; `v2` stamps every model as v2. `default` restores upstream pins (Sol/Terra v2, Luna v1) and otherwise follows the native `multi_agent_v2` flag. Applies to new sessions. | | `keepNativeChatGptOnV1?` | `boolean` | `false` | When `multiAgentMode` is `"v2"`, disable the global V2 override, stamp ChatGPT-native rows as v1, and keep routed rows on v2. Codex resolves the global override before catalog pins, so both parts are required for a ChatGPT parent to spawn routed children without backend-encrypted tasks ([#92](https://github.com/lidge-jun/opencodex/issues/92)). Ignored in `v1` and `default`. | | `subagentModels?` | `string[]` | `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-luna` | Up to five bare native, account-qualified `/`, or routed `provider/model` ids featured first in the sub-agent picker. The dashboard offers bare native and routed ids as new choices and preserves already-configured exact account-qualified ids when saving, including ids unavailable in the current catalog. To add a new exact account-qualified id, use `ocx agent subagents set` or edit the configuration. After the [one-time Astra upgrade](#astra-roster-upgrade), an explicit empty list is preserved. | | `injectionModel?` | `string` | — | Preferred native or routed sub-agent model used in proxy-authored v2 delegation guidance. | | `injectionEffort?` | `string` | — | Preferred effort (`low` through `ultra`), meaningful only with `injectionModel`. | | `injectionPrompt?` | `string` | — | Replaces the built-in v2 guidance body. Supports `{{model}}`, `{{effort}}`, `{{roster}}`, and `{{fallback}}`. A configured `injectionModel` is sufficient to render the custom prompt. | | `multiAgentGuidanceEnabled?` | `boolean` | `true` | Controls only opencodex-authored v1/v2 developer guidance; it does not change native agent defaults, tools, routing, rosters, or effort caps. | | `syncCodexSubagentDefaults?` | `boolean` | `false` | Opt into writing `injectionModel` and optional `injectionEffort` as Codex's native defaults during sync/restart. Requires `injectionModel`. | | `subagentModelFallback?` | `string[]` | `[]` | Priority-ordered global fallback models for spawned child turns. | | `subagentModelFallbackByModel?` | `Record` | `{}` | Per-primary-model fallback chains, keyed by the requested primary model id. This is the supported home for per-role fallback metadata; `model_fallback` inside Codex agent TOML makes Codex 0.146+ skip the role (#1190). | | `subagentModelFallbackPollMs?` | `number` | `60000` | Availability-probe cache interval. Values below 1000 ms fall back to the default. | | `codexRoleTiers?` | `{ fast?, standard?, frontier?: string[] }` | — | Capability tier of named models for [role auto-assign](/guides/integrations/#auto-assign). Listed models take that tier; other models are tiered by measured score in the role's domain when two or more have one, without requiring prices. Equal scores share a tier. Otherwise price tiering excludes unpriced, unlisted models. | | `codexRoleCapability?` | `{ artificialAnalysis?: { apiKey?: string } }` | No measured source | Measured-score source for [role auto-assign](/guides/integrations/#auto-assign). Artificial Analysis uses your API key (literal, `${ENV}` or keychain reference) once per day to read its free model list, every domain scored by its Intelligence Index; scores stay out of the response. Missing or unavailable data falls back to configured tiers and prices. A retired `huggingFace` key is ignored. | | `effortCap?` | `string` | — | Hard ceiling for qualifying v2 main turns and marked spawned-child turns. Accepts `low` through `ultra`. | | `subagentEffortCap?` | `string` | — | Additional ceiling for spawned-child turns only. When both caps apply, the lower wins. | | `plaintextV2AgentMessages?` | `boolean` | — (unset) | Experimental opt-in. It runs only when explicitly set to `true` and asks eligible native ChatGPT v2 parents to emit `spawn_agent`, `send_message`, and `followup_task` message arguments as plaintext. See [Plaintext v2 agent messages](#plaintext-v2-agent-messages). | | `agentTaskRecovery?` | `object` | — | Experimental opt-in recovery for backend-encrypted v2 tasks sent to routed providers. Disabled unless `enabled: true`; see [Encrypted v2 task recovery](#encrypted-v2-task-recovery). | Manage the surface with the dashboard or `ocx v2 status|on|off|mode |keep-native-v1 |threads |mode-hint `. Mode changes apply to new sessions. `maxConcurrentThreadsPerSession` is a `PUT /api/v2` field, not a `config.json` key; `ocx v2 threads ` writes `max_concurrent_threads_per_session` under `[features.multi_agent_v2]` in Codex's `$CODEX_HOME/config.toml` after v2 is enabled. The Models page's sub-agent limit counts children. A stored V2 total of 1 is root-only and displays 0; setting one child saves a V2 total of 2. New child limits must be at least 1, and both the child count and converted API value must be safe integers (at most 9,007,199,254,740,991). With global V2 disabled, including hybrid mode, no root slot is added. The API rejects unsafe limits with 400 before changing the feature flag or configuration. **Always proactive delegation** in Subagents → Advanced (formerly **Ultra mode**) changes the delegation trigger without changing reasoning effort. Its preset preserves user instructions, authority boundaries, task scope, and tool rules. The dashboard toggle, `PUT /api/v2` field `multiAgentModeHintText`, and `ocx v2 mode-hint` write `features.multi_agent_v2.multi_agent_mode_hint_text` in Codex's `$CODEX_HOME/config.toml`. The CLI `ocx v2 mode-hint` command persists this key even when `multi_agent_v2` is disabled; it does not toggle the feature. The hint replaces codex-rs's effort-derived multi-agent policy when that native surface is active. A `null` value removes the key so the effort-derived policy (ultra = proactive, otherwise explicit) resumes; empty or whitespace-only values are rejected because a present empty override would suppress even the ultra-derived Proactive message. The Subagents dashboard's **Always proactive delegation** toggle requires both the native feature and an explicit v2 surface (`multiAgentMode: "v2"`, equivalent to `ocx v2 mode v2`); `ocx v2 on` alone does not satisfy that dashboard gate. `GET` and `PUT /api/v2` also return `multiAgentModeHintRecommendation: { text, revision }`. The dashboard uses this server-provided text when enabling or restoring the preset, with no hardcoded fallback. If an older server omits the recommendation or returns a malformed value, preset installation and restoration are unavailable; editing or clearing an existing custom hint remains available. **Restore preset** changes only the local draft; **Save** persists it. Reading settings, unrelated updates, and upgrades do not migrate a stored hint. Only an explicit hint update that matches either of the two recognized legacy OpenCodex presets byte-for-byte is replaced with the current recommendation. Other valid custom text, including whitespace variants, is preserved byte-for-byte. Mode-hint support is still checked before writing, and changes apply to new Codex sessions. The management API exposes `GET`/`PUT /api/v2`, `/api/injection-model`, `/api/effort-caps`, `/api/subagent-models`, and `/api/subagent-model-fallback`. Injection-model updates are partial; the custom prompt is the `prompt` field on that API. The Codex Auth page can also toggle Codex's own `default_mode_request_user_input` feature flag (`GET`/`PUT /api/codex-auth/features/default-mode-request-user-input`). Enabling it adds `[features] default_mode_request_user_input = true` to Codex's `$CODEX_HOME/config.toml` through the official `codex features enable|disable` CLI (format-preserving edit, removed again when disabled), which lets Codex pause a Default-mode session and ask you questions with the `request_user_input` tool. The flag is under development upstream and only applies to new sessions; the toggle fails loudly when the installed Codex build does not know the flag yet. While a Codex CLI update owns the installation, the toggle returns retryable HTTP 503 (`codex_cli_selection_busy`) before running the feature command. Retry after the update finishes; config-write failures still return 502. ## Roster and guidance The effective v2 roster is the configured, picker-visible, priority-sorted first five models that are present in the injected catalog and are not explicitly marked `"disabled"`. An explicit `"v2"` pin supports recursive workers; `"v1"`, `null`, and absent pins remain eligible as leaf workers. Excluded entries remain in configuration so they can become eligible later. Surface detection uses tool shape. A namespaced `spawn_agent` with `send_input`, `resume_agent`, or `close_agent` is v1. A flat `spawn_agent` with `send_message`, `followup_task`, `interrupt_agent`, or `list_agents` is v2. V1 guidance is proactive text only at `max` or `ultra`. V2 receives a proxy-authored developer message only when a preferred model, eligible roster, or fallback chain exists. Built-in v2 guidance has a 700-character budget and drops the roster first if necessary. Guidance is deduplicated across replay prefixes and inserted before a trailing `compaction_trigger`. Both built-in v2 subagent guidance and custom `injectionPrompt` bodies use ``, separate from Codex's native `` messages. Built-in text reports the resolved preferred model, roster, and fallback chain without prescribing delegation, model overrides, or `fork_turns`. Custom bodies retain their placeholder substitution and content. `injectionModel` and `injectionEffort` remain advisory unless native-default sync is enabled; missing custom placeholder values are still replaced with an empty string. Replay deduplication compares the latest exact text in each tag family. When both values use the new proxy family, switching custom guidance back to the built-in form appends the current value; intervening native mode changes do not duplicate unchanged proxy guidance. Existing native and legacy-tagged history is preserved. This wrapper change does not identify the author of old messages or revoke prior instructions. Mixed-version histories cannot be classified from the legacy tag alone, and transition detection across such histories is not guaranteed. ## Native Codex default sync When enabled, `syncCodexSubagentDefaults` writes marker-owned `[agents] default_subagent_model` and `default_subagent_reasoning_effort` fields. Existing unmarked user-owned target fields are treated as conflicts and remain authoritative; partial or ambiguous TOML writes fail closed. Clearing `injectionModel` also clears the opt-in. These defaults affect newly created Codex tasks and do not cause delegation by themselves. ## Fallback chain Spawned-child fallback order is: 1. the requested primary model; 2. per-model chains from `subagentModelFallbackByModel` (keyed by the primary model); then 3. global `subagentModelFallback` entries. Per-role fallback chains must live in opencodex config. Writing `model_fallback` into `$CODEX_HOME/agents/*.toml` makes Codex 0.146+ reject the whole role file as an unknown field and skip the role (#1190). A legacy `model_fallback` line in the TOML is still read for backwards compatibility, but `ocx doctor` flags it. opencodex skips disabled, unroutable, unhealthy, cooling-down, or quota-threshold candidates. The availability snapshot is cached for `subagentModelFallbackPollMs`. Encrypted child tasks restrict the chain to canonical native ChatGPT targets plus direct key-auth Responses routes explicitly trusted with `allowEncryptedV2AgentTasks: true`; if none can consume the encrypted payload, the request fails instead of routing unreadable ciphertext elsewhere. Combo routing first tries an available canonical native target; when none is selectable or their attempts are exhausted, and `agentTaskRecovery` is enabled, an encrypted `NEW_TASK` is recovered once before routed combo dispatch. Combo recovery runs only on spawned child turns; the direct routed path also recovers on a mid-thread model switch. ```json { "multiAgentMode": "v2", "subagentModels": ["gpt-6.1-sol", "anthropic/claude-sonnet-5"], "injectionModel": "gpt-6.1-sol", "injectionEffort": "high", "syncCodexSubagentDefaults": true, "subagentModelFallback": ["gpt-6-luna"], "subagentModelFallbackByModel": { "gpt-6.1-sol": ["gpt-6-luna"] }, "subagentModelFallbackPollMs": 60000, "subagentEffortCap": "high" } ``` ## Plaintext v2 agent messages `plaintextV2AgentMessages` is unset in a fresh config and runs only when explicitly set to `true`. The caller must use the Responses wire, and the final destination must use the canonical ChatGPT Codex forward transport: `adapter: "openai-responses"`, `authMode: "forward"`, and the exact base URL `https://chatgpt.com/backend-api/codex`. OpenAI API-key providers, custom OpenAI-compatible gateways, routes whose final destination is another provider, and non-Responses callers are never rewritten. For an eligible v2 request, opencodex recognizes the catalog by a top-level `collaboration` namespace with a direct `spawn_agent` child. The catalog can be in top-level `tools`, or in the first input item's developer `additional_tools` when `tools` is absent (Responses Lite). An explicit top-level catalog takes precedence; user-role and later historical catalogs do not activate the option. It removes `parameters.properties.message.encrypted: true`, when present, only from `spawn_agent`, `send_message`, and `followup_task`. ChatGPT reserves both the `collaboration` namespace and those three tool names, so the request uses fixed private aliases for all four identities. Before making that change, opencodex checks top-level and `additional_tools` catalogs, nested namespaces, `tool_search_output` declarations, `tool_choice`, and prior call items for the private namespace and fixed aliases. Any conflict leaves the entire request unchanged. OpenCodex restores only the request-scoped aliases in JSON, SSE, and WebSocket responses before Codex receives the tool call. The `encrypted_function_args: []` field is preserved so compatible Codex clients recognize the message as plaintext. This path adds no recovery request and therefore does not spend the extra ChatGPT quota used by a cache miss in `agentTaskRecovery`. It cannot change tasks that are already encrypted. If the request already declares the private alias or a conflicting reference, opencodex leaves that request unchanged; separately enabled recovery can still handle a routed task that is later encrypted. If ChatGPT rejects or ignores the modified schema, or the Codex client does not recognize the plaintext response fields, the call can fail. OpenCodex does not retry the parent request with the original schema because doing so could duplicate quota use or tool calls. Restoration uses a 10,000-identity traversal budget for each response payload. If a payload exhausts that budget, bounded JSON returns HTTP 502 and a stream returns `response.failed`; neither path sends the private aliases to Codex or saves the refused response for `previous_response_id` continuation. For successfully rewritten calls, the option removes application-layer encryption from agent message arguments. HTTPS still encrypts network transport, but message text can appear in Codex task history, routed-provider requests, `responses-state.json` or its spill files, and `usage-debug.jsonl` when debug capture is enabled. The behavior depends on undocumented ChatGPT schema and response fields and may stop working after a backend or client update. Startup prints a warning while it is enabled. ```json { "plaintextV2AgentMessages": true } ``` The equivalent CLI command is `ocx config set plaintextV2AgentMessages true`. Restart the proxy after changing the setting. ## Encrypted v2 task recovery `agentTaskRecovery` is an experimental compatibility path for backend-encrypted v2 tasks that reach a routed provider. Two request shapes qualify: a native ChatGPT parent spawning a routed v2 child, and a live thread switched from a native ChatGPT model to a routed one, whose history replays a backend-minted encrypted agent message on every later turn ([#4089](https://github.com/lidge-jun/opencodex/issues/4089)). It is disabled by default. When explicitly enabled and the final routed task contains an otherwise unreadable Fernet payload, opencodex uses a raw Responses passthrough request to the fixed `https://chatgpt.com/backend-api/codex/responses` endpoint with forward-mode authentication. ChatGPT returns the plaintext assignment through a forced function call; opencodex then converts only that task item to a standard user message before routed-provider dispatch. Direct routed recovery, cached history replay, and the unreadable-task detector recognise all four codex-rs agent-message types: `NEW_TASK`, `MESSAGE`, `FOLLOWUP_TASK`, and `FINAL_ANSWER`. Combo recovery remains limited to spawned-child turns. A `FINAL_ANSWER` envelope may omit its `Task name` line. Recovery then has no header address to compare with the item's recipient, so that single cross-check does not run; the sender comparison and the cache scope, which still binds the structured recipient, are unchanged. This is not local decryption and does not fix the Codex wire protocol. It depends on undocumented ChatGPT backend behavior and may stop working after a backend change. The recovered assignment is model output, not a cryptographically verified plaintext, so byte-for-byte fidelity is not guaranteed. A scoped cache miss may add an authenticated ChatGPT request, consume account quota, and add latency before the routed request. Concurrent requests for the same scoped task share one recovery request. Startup prints a warning whenever the feature is enabled. Admission and retention are deliberately narrow: - recovery is available only while the proxy is bound to loopback; - only a native Codex caller with a matching ChatGPT bearer/account pair is eligible. This is the credential shape used by the canonical `openai` provider with `authMode: "forward"`; recovery uses only the pair on the incoming request and never substitutes API-key authentication, another provider credential, or another Codex account; - callers using `x-opencodex-api-key`, `x-api-key`, generic API credentials, or a proxy admission secret keep the existing `unreadable_encrypted_agent_task` failure; - raw ChatGPT credentials are sent only to the hard-coded ChatGPT endpoint and are never placed in the request body, logs, cache keys, or provider request; the in-memory cache scope uses only a process-random keyed digest of the caller credential and account; - the recovery request forwards only `authorization`, the matching `chatgpt-account-id`, `originator`, and optional `openai-beta` and `user-agent` metadata; opencodex sets `content-type` and `accept` itself, and no other caller headers cross this boundary; - recovered plaintext is never logged or persisted; the process-local cache is credential-, parent- thread-, and ciphertext-scoped, expires after 15 minutes, and is bounded by both configured entry count (200 by default, 512 maximum) and 8 MiB total; - any malformed envelope, failed recovery, timeout, or validation failure preserves the existing fail-closed error; client cancellation returns 499. Neither path forwards ciphertext to the routed provider. Recovery accepts one consecutive run of up to 32 complete Fernet-shaped encrypted parts, with at most 2 MiB of combined ciphertext. Parts retain their order and boundaries in one authenticated request. Cache identity includes the sequence; the original input is revalidated before assignment replacement. HTTP failures retain the existing bounded diagnostic reason. They do not trigger an internal retry unless `retries` is set: with a value from 1 to 2, opencodex re-sends the same admitted request only on a transient upstream status (500/502/503/504/52x) or a transport failure, after a short jittered backoff, and still inside the same credential, deadline, and shared flight. Terminal statuses and invalid recovery output never retry. Split tokens are not reconstructed for recovery. A bounded run whose exact concatenation has Fernet structure stays classified as ciphertext through plaintext-slot normalization. If the task has no independent readable text, it fails closed without a recovery or routed-provider request. Independent readable text retains the existing mixed-content policy. Other fragment representations remain unsupported; this does not establish general token-split recovery or upstream multipart fidelity. ### Threat model This path assumes the local native Codex caller already holds a valid ChatGPT credential and that the fixed ChatGPT endpoint is trusted to authenticate it. It protects against generic proxy/API-key callers using the feature as a plaintext oracle, redirecting credentials to another destination, cross-account or cross-thread cache reuse, and sensitive-data logging or persistence. Admission checks token issuer, audience, Codex client, expiry/not-before bounds, and exact account match before every cache lookup; the endpoint remains the signature authority. It does not protect against another process running as the same OS user, a compromised ChatGPT backend or recovery model, prompt injection inside the encrypted task, model transcription errors, or memory inspection of the running proxy. Recovery output must therefore be treated as untrusted model output rather than authenticated plaintext. ```json { "agentTaskRecovery": { "enabled": true, "model": "gpt-5.6-sol", "timeoutMs": 45000, "cacheEntries": 200, "retries": 0 } } ``` Enable this only when the additional authenticated request, quota use, plaintext-in-process boundary, and private-backend dependency are acceptable. Prefer a native ChatGPT child or v1 heterogeneous delegation when they are not. This recovery path applies to direct-routed children and encrypted combo `NEW_TASK` spawns. At most 32 recovery requests can be active at once; additional misses fail closed. A combo with an available canonical native target still sends ciphertext directly; recovery runs only when no native target is selectable. After a stored Pool account's refresh and same-account replay are exhausted, recovery can use the incoming caller credential for one available routed target without trying another native account. Policy refusals remain terminal. Failed recovery, exhausted targets, or unavailable targets still fail closed without forwarding ciphertext to a routed provider. ## Effort caps Caps apply only to the v2 collaboration feature: a main turn qualifies when its tools expose v2, while a child qualifies when it carries exact codex-rs `x-openai-subagent: collab_spawn` or `"subagent_kind": "thread_spawn"` markers in `x-codex-turn-metadata`, even if leaf tools no longer expose collaboration. V1 main turns, `multiAgentMode: "v1"`, compaction, review, and memory-consolidation turns bypass caps. Caps only lower effort. They snap to the highest advertised rung at or below the cap. If a model has no effort control or no supported rung fits, opencodex removes the effort and lets the provider default apply. `max` and `ultra` are accepted, while the dashboard offers `low` through `xhigh`. Configured caps also apply to eligible native Chat Completions turns that carry no model effort pin. Provider wire mapping runs when a pin is applied or when a cap changes the value; a native caller value keeps its original wire spelling when neither happens. For a beginner-oriented explanation of v1, default, and v2 behavior, see [Sub-agent surfaces](/guides/sub-agent-surface/). ## Global model effort pins The optional root `modelPinnedEfforts` map fills or overrides incoming effort choices when neither a provider model pin nor a provider-wide pin is configured. For example: ```json { "modelPinnedEfforts": { "example-provider/example-model": "high" } } ``` Lookup checks the final selector before provider-prefix normalization, then the qualified `provider/model` destination, then its bare upstream model ID. Original combo aliases and synthetic effort-row selector IDs are not global pin keys; configure the concrete destination. Synthetic-row effort and combo defaults are preserved as the effective input before pinning. Each selected destination resolves its own pin, then applicable caps and wire normalization. Compaction requests are exempt. `none` means effort omission and provider-default behavior, not guaranteed reasoning disablement. For canonical OpenAI forwarding, the final model's supported effort list takes precedence: `gpt-6.1-sol` and `gpt-6.1-terra` start at `low`, so an incoming `none` or `minimal` is sent as `low`, including after a compaction model switch or combo failover. `GET /api/effort-caps` includes the map. `PUT /api/effort-caps` accepts `modelPinnedEfforts` alongside the existing caps: omitted fields stay unchanged, `null` clears the map, and a map entry set to `null` or `""` deletes only that key. Invalid combined updates leave both caps and pins unchanged. Saving a pin does not alter the featured subagent roster. ## CLI updates during v1 synchronization When an explicit `multiAgentMode: "v1"` synchronization must disable Codex's global v2 override, an ongoing Codex CLI update can temporarily refuse the synchronization. Retry after the update finishes. A home already using v1 can still synchronize during the update because no native feature command is needed.