Multi-Agent V2 sends OpenAI-specific agent_message items to external Responses providers
What issue are you seeing?
Codex Multi-Agent V2 sends child-agent instructions as the OpenAI-specific Responses item type agent_message. External Responses-compatible providers such as Ollama do not recognize this item type and cannot decrypt its encrypted_content when present.
This prevents a V2 parent from spawning a native Codex agent whose configured model_provider is external. The failure is at the provider request boundary, before the external model can correctly receive the delegated task.
A captured comparison showed:
- Working V1 Kimi/GLM requests: every input item had
type: "message", with noencrypted_content. - Failing V2 GLM request: the final input item had
type: "agent_message". Even when that captured request had no encrypted payload, Ollama Responses still could not process the non-standard item type.
External-provider routing itself works under V1. The important distinction is that the Multi-Agent version is selected and stored by the root task's first parent turn, then inherited by the whole spawned agent tree:
- If a fresh task sends its first message using a V1-capable parent model such as Luna, the root task resolves to V1 and every subsequently spawned agent also uses V1.
- Switching models after the first message does not change the already stored Multi-Agent version.
- Starting the task with a catalog-forced V2 parent such as Sol or Terra therefore keeps the whole tree on V2, including external-provider children.
This is related to, but distinct from, #33284. That issue concerns plaintext visibility to local hooks before execution. This issue concerns wire-format compatibility with non-OpenAI model providers after dispatch.
What steps can reproduce the bug?
- Configure an external Responses provider, for example an Ollama Responses endpoint, in Codex configuration.
- Configure a native agent role in
~/.codex/agents/*.tomlwhosemodel_providerpoints to that external provider. - Enable Multi-Agent and Multi-Agent V2.
- Start a fresh task using a parent model for which the model catalog selects V2, and send the first message with that model.
- Invoke
spawn_agentfor the external-provider role. - Inspect the request sent to the external Responses endpoint.
The child instruction is serialized as an input item resembling:
{
"type": "agent_message",
"encrypted_content": "..."
}
or as a plaintext agent_message item. Ollama cannot decrypt the encrypted form, and its Responses implementation does not accept the agent_message item type in either form.
Disabling the multi_agent_v2 feature flag alone may not avoid the issue when the model catalog forces V2 for the selected parent model.
The current workaround requires all of the following:
- Create a new task.
- Select a V1-capable parent model such as Luna before sending the first message.
- Set
multi_agent = trueandmulti_agent_v2 = false. - Keep using that task after its first turn has resolved to V1.
This makes the entire task and every child agent use V1. It is not a provider-specific fallback, and it does not allow an OpenAI parent to retain V2 while only an external-provider child uses a compatible transport.
Relevant implementation areas appear to include:
codex-rs/core/src/tools/handlers/multi_agents_spec.rscodex-rs/core/src/tools/handlers/multi_agents_v2.rscodex-rs/protocol/src/protocol.rs(InterAgentCommunication::to_model_input_item)codex-rs/core/src/client.rs(Responses request construction)
What is the expected behavior?
Multi-Agent V2 should remain usable when either endpoint in an inter-agent route uses a non-OpenAI provider.
The requested behavior is not to downgrade external agents to V1 inside an otherwise V2 task. The root task and the entire agent tree should retain V2 lifecycle and coordination semantics. Only the wire representation at a non-OpenAI provider boundary should be adapted.
A provider-aware compatibility behavior would be:
- Preserve the existing encrypted
agent_messagepath for OpenAI-to-OpenAI routes. - For a route involving a non-OpenAI provider, keep the task text plaintext and serialize the outbound instruction as a standard Responses
messageitem, for example:
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "..."
}
]
}
- Apply the same provider-aware conversion to initial spawn instructions, follow-up/
send_messageinstructions, and child return messages as needed. - Preserve V2 lifecycle behavior such as task identity, graph state, wait, follow-up, and completion.
- Leave existing OpenAI-provider behavior unchanged.
- Allow catalog-forced V2 parents such as Sol or Terra to spawn explicitly configured external-provider roles without requiring the root task to start under Luna/V1.
Additional information
The compatibility decision should be made using the actual caller and recipient provider identities, not only the parent model name.
The current Luna-first workaround demonstrates that the configured external provider and native agent role can function when the payload uses standard V1 message items. It also demonstrates why that workaround is insufficient: it disables V2 for the entire root/child tree rather than making V2 interoperable with external Responses providers.
8 Comments
I've also hit the same problem.
I traced this to two separate OpenAI-specific assumptions in Multi-Agent V2 and have a tested implementation available for maintainer review.
Root cause
messagearguments with the Responsesencryptedextension. A portable Responses provider cannot produce or consume that encrypted field.agent_messageinput item. External Responses-compatible providers generally accept only standardmessageitems.The second point is why merely disabling client-side encryption is insufficient.
Proposed compatibility mode
The branch adds an opt-in setting:
When omitted, it defaults to
true, so the existing OpenAI/Codex path is unchanged.When disabled:
spawn_agent,send_message, andfollowup_taskexpose ordinary string arguments without theencryptedschema extension;collaborationnamespace toagentsunless explicitly configured;agent_messageitems are converted to portable Responsesmessage/user/input_textitems;An explicit mode is needed because the parent model must see the tool argument schema before it chooses an
agent_type; the recipient provider is not yet known when that schema is generated.Implementation: https://github.com/jingx8885/codex/tree/codex/portable-inter-agent-messages
Commit: https://github.com/jingx8885/codex/commit/20f3bcc433
Validation
encryptedmarker;messageitem;agent_messageitem reaches the external provider.codex-features: 29/29 tests passed.codex-core: 2,978/3,035 tests passed. Of the remaining 57, 56 were test-environment failures caused by helper binaries not having been built at the time, and one unrelated image-budget test timed out. The helper binaries were subsequently built successfully.just fix -p codex-coreandjust fmtcompleted successfully.codex-featuresClippy run hit a transient missing Cargo fingerprint directory while rebuilding dependencies.The implementation was AI-assisted and independently reviewed against the final diff and targeted behavior.
Would a Codex maintainer confirm whether this opt-in compatibility approach matches the intended architecture and, if so, invite a pull request? I have not opened one because
docs/contributing.mdstates that uninvited external PRs are closed without review.Related desktop corroboration (external Responses 422)
Alongside the encrypted-assignment / Chat Completions case in #34833, we also hit the external Responses item-type failure on MultiAgentV2:
gpt-5.6-solon Codex desktop (CLI0.145.0-alpha.30)skills_openapiwithgrok-4.5gpt-5.6-solunblocked the work; restoringgrok-4.5reintroduces the failure.This matches the “V2 sends
agent_message/ encrypted OpenAI-specific items to external Responses providers” diagnosis here. Provider-aware conversion to a standardmessageitem at non-OpenAI boundaries (while keeping V2 lifecycle) would cover this path.Confirmed on Codex 0.144.6 / WSL2 with an OpenAI gpt-5.6-sol parent and a MiniMax-M3 child using an external Responses provider. Direct codex exec against MiniMax succeeds. Native V2 spawn and follow-up authenticate and complete, but return an empty result. The child rollout contains an empty visible Payload: followed only by encrypted_content. This reproduces the provider-boundary issue with MiniMax’s Responses-compatible endpoint.
Confirmed on codex-cli 0.146.0 with DeepSeek (
wire_api = "responses",multi_agent_version = "v2"), with wire captures:agent_messageitem whose payload lives inencrypted_content; DeepSeek drops that block, so the child sees only an emptyPayload:header.agent_messagecontent is rewritten to plaintextinput_text, DeepSeek still ignores the item in most runs and the child replies that it has no task.usermessage is consumed reliably (spawn ->PONG, followup_task ->PONG2).This suggests upstream should render agent-message deliveries as plain user messages (or V1-style
UserInput) for non-OpenAI providers, not just foldencrypted_contentinto theagent_messageitem. Our local patch does exactly that: https://github.com/CCanxue/codex-deepseek-subagent-fixWorking local workaround: proxy that rewrites
agent_message→messagefor non-OpenAI parentsEnvironment: macOS 15.2 (arm64), Codex Desktop 26.730.61639, bundled
codex-cli 0.147.0-alpha.1.2, custom DeepSeek provider (wire_api = "responses",multi_agent_version = "v2", modeldeepseek-v4-flash).Fully tested locally: subagents spawn, receive their task, execute it, and return results. Normal turns and SSE pass through unchanged.
Why it works: for a non-OpenAI parent,
encrypted_contentis plaintext — Codex stores the rawspawn_agentmessage there (InterAgentCommunication::new_encrypted()inprotocol.rs) and never rewritesagent_messageitems for non-OpenAI providers (client.rs). DeepSeek ignoresagent_messageitems, so the task never reaches the child. The proxy converts them to standardmessage/user items. Real Fernet ciphertext (OpenAI parents) always starts withgAAAAA; the proxy passes those through untouched.Setup:
Restart the session. The proxy is a single zero-dependency Node script — run it manually, wrap it as a Codex plugin, or manage it as an auto-start background process (e.g. launchd/systemd).
Two cases matched by
agent_message: ① parent → childNEW_TASKin the child's requests (critical, must be converted); ② child → parentFINAL_ANSWERreplayed in the parent's history on every later request (converted too, harmless).Limitation: works for any non-OpenAI parent → non-OpenAI child provider combination (DeepSeek, MiniMax, Kimi, etc.); OpenAI-parent ciphertext (#36376) cannot be decrypted locally.
Related:
Proxy script (Node.js, zero dependencies):
Thanks for sharing. The
gAAAAAFernet heuristic matches our finding: for non-OpenAI parents the value stored inencrypted_contentis locally plaintext, while OpenAI-parent assignments are real ciphertext that cannot be decrypted locally.We went with a core patch instead of a wire proxy: for non-OpenAI providers, V2 spawn / followup_task / send_message are delivered as plain
UserInput(standard user messages), so no request rewriting is needed for the non-OpenAI parent topology. Verified end-to-end with deepseek-v4-flash (spawn ->PONG, followup ->PONG2). The proxy is a nice zero-dependency alternative for users who cannot rebuild the CLI. Patch and evidence: https://github.com/CCanxue/codex-deepseek-subagent-fixSanitized corroboration from Windows with codex-cli 0.147.0 and a custom OpenAI-compatible Responses provider:
multi_agent_version: "v2"even thoughfeatures.multi_agent_v2 = false; the selected model catalog/session assignment controlled the active transport.spawn_agentcreated the child thread successfully.agent_messagewith aninput_textheader plus anencrypted_contentblock containing the task.invalid request body: Invalid 'input': value did not match any expected variant.encrypted_content, so changingfork_turnswould not remove this incompatibility.We have disabled both
multi_agentandmulti_agent_v2and added a fresh-process drift gate until provider-aware standardmessagedelivery or an explicit fail-fast compatibility check is available. No credentials, task text, repository paths, or ciphertext are included here.