Skip to content

Agent Configuration

Agent settings control which Codex collaboration surface is advertised and how opencodex guides, routes, and limits delegated work.

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 list receives those same defaults; an explicit empty legacy list becomes ["gpt-6-astra"]. Existing Astra entries are not duplicated.

The internal subagentModelsVersion: 1 marker makes this 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). Ignored in v1 and default.
subagentModels? string[] gpt-6-astra, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5 Up to five bare native, account-qualified <selector>/<native-openai-model>, or routed provider/model ids featured first in the sub-agent picker. The dashboard offers only bare native and routed ids and omits exact account-qualified choices when it saves; use ocx agent subagents set or edit the configuration for exact choices. After the one-time Astra 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<string, string[]> {} 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.
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.
agentTaskRecovery? object Experimental opt-in recovery for backend-encrypted v2 tasks sent to routed providers. Disabled unless enabled: true; see Encrypted v2 task recovery.

Manage the surface with the dashboard or ocx v2 status|on|off|mode <v1|default|v2>|keep-native-v1 <on|off>|threads <n>|mode-hint <text|--clear>. Mode changes apply to new sessions. maxConcurrentThreadsPerSession is a PUT /api/v2 field, not a config.json key; ocx v2 threads <n> writes max_concurrent_threads_per_session under [features.multi_agent_v2] in Codex’s $CODEX_HOME/config.toml after v2 is enabled.

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.

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 <opencodex_subagent_guidance>, separate from Codex’s native <multi_agent_mode> 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.

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.

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.

{
"multiAgentMode": "v2",
"subagentModels": ["gpt-5.5", "anthropic/claude-sonnet-5"],
"injectionModel": "gpt-5.5",
"injectionEffort": "high",
"syncCodexSubagentDefaults": true,
"subagentModelFallback": ["gpt-5.6-luna"],
"subagentModelFallbackByModel": {
"gpt-5.5": ["gpt-5.6-luna"]
},
"subagentModelFallbackPollMs": 60000,
"subagentEffortCap": "high"
}

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. 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.

{
"plaintextV2AgentMessages": true
}

The equivalent CLI command is ocx config set plaintextV2AgentMessages true. Restart the proxy after changing the setting.

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). 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.

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 and do not trigger an internal 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.

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.

{
"agentTaskRecovery": {
"enabled": true,
"model": "gpt-5.6-sol",
"timeoutMs": 45000,
"cacheEntries": 200
}
}

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.

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.

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:

{
"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.

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.