Agent Configuration
Agent settings control which Codex collaboration surface is advertised and how opencodex guides, routes, and limits delegated work.
Agent fields
Section titled “Agent fields”Astra roster upgrade
Section titled “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 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.
Roster and guidance
Section titled “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
<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.
Native Codex default sync
Section titled “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
Section titled “Fallback chain”Spawned-child fallback order is:
- the requested primary model;
- per-model chains from
subagentModelFallbackByModel(keyed by the primary model); then - global
subagentModelFallbackentries.
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"}Plaintext v2 agent messages
Section titled “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. 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.
Encrypted v2 task recovery
Section titled “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). 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
openaiprovider withauthMode: "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 existingunreadable_encrypted_agent_taskfailure; - 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 matchingchatgpt-account-id,originator, and optionalopenai-betaanduser-agentmetadata; opencodex setscontent-typeandacceptitself, 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.
Threat model
Section titled “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.
{ "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.
Effort caps
Section titled “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.
Global model effort pins
Section titled “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:
{ "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.

