Summary
Any Codex thread that has used sub-agents stops working as soon as it is routed to a
non-forward Responses destination. The destination rejects the whole request body with
422 {"error":"Failed to deserialize the JSON body into the target type: input[31]: unknown item type \"agent_message\"; expected one of: message, reasoning, function_call, function_call_output, shell_call, shell_call_output, web_search_call, file_search_call, code_interpreter_call, mcp_call, custom_tool_call, image_generation_call, compaction"}
agent_message is Codex's private multi-agent input item and exists only in the ChatGPT
Codex backend's schema. Codex writes every sub-agent reply into the rollout, so it is
replayed in the input of every later turn of that thread — the failure is not a
one-off, the thread is dead until the history is dropped.
422 is a client error, so nothing fails over: attempts has a single entry and the status
reaches the client verbatim.
Reproduction
Minimal, against a running ocx (the destination below is the built-in xai Responses
route; any non-forward Responses destination behaves the same):
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:10100/v1/responses \
-H 'content-type: application/json' -H 'authorization: Bearer local' \
-d '{"model":"xai/grok-4.6","stream":false,"store":false,
"instructions":"Answer with the single word OK.",
"input":[
{"type":"message","role":"user","content":[{"type":"input_text","text":"hello"}]},
{"type":"agent_message","author":"/root/child","recipient":"/root",
"content":[{"type":"input_text","text":"child reported: done"}]},
{"type":"message","role":"user","content":[{"type":"input_text","text":"Say OK."}]}]}'
input[1] |
status |
message (role user) |
200 |
agent_message, plaintext content only |
422 |
agent_message with encrypted_content |
422 |
Replacing only input[1] with an equivalent message item makes the same request succeed,
so the item type alone is the trigger.
Observed in normal use
12 consecutive failures in one conversation over 15 minutes, every one of them:
provider: xai model: grok-4.6 adapter: openai-responses status: 422
routeKind: combo requestedModel: auto attempts: 1
The combo route had picked xai/grok-4.6 because the other candidates were on cooldown.
Nothing about the failure is specific to that provider — it is specific to the item type.
Current behaviour
src/adapters/openai-responses.ts converts the item only for the OpenCode Go destination:
if (!forward && isOpenCodeGo(provider.baseUrl)) outBody = normalizeOpenCodeGoAgentMessages(outBody);
and the docs state the scope explicitly
(docs-site/src/content/docs/reference/configuration/providers.md):
This conversion is scoped to that destination, including renamed provider entries;
other Responses destinations keep their input unchanged.
Keeping the item "unchanged" for other destinations is a guarantee of a 422: no third-party
Responses implementation accepts a private OpenAI item type.
Proposal
Apply the existing plaintext conversion to every routed (authMode !== "forward")
Responses destination, leaving the rest untouched:
forward destinations keep agent_message exactly as today — that is the only schema
that understands it.
encrypted_content and unknown part types keep their current fail-closed path. The
encrypted v2 task surface already owns those through unreadable_encrypted_agent_task
and the opt-in recovery route, and this issue does not propose changing it.
The narrow scope looks like an artifact of the PR that introduced the helper (a Go-specific
carry) rather than a decision about other destinations; the conversion itself is already
lossless for plaintext content and preserves author/recipient as readable text.
PR to follow.
Summary
Any Codex thread that has used sub-agents stops working as soon as it is routed to a
non-
forwardResponses destination. The destination rejects the whole request body withagent_messageis Codex's private multi-agent input item and exists only in the ChatGPTCodex backend's schema. Codex writes every sub-agent reply into the rollout, so it is
replayed in the
inputof every later turn of that thread — the failure is not aone-off, the thread is dead until the history is dropped.
422 is a client error, so nothing fails over:
attemptshas a single entry and the statusreaches the client verbatim.
Reproduction
Minimal, against a running ocx (the destination below is the built-in
xaiResponsesroute; any non-
forwardResponses destination behaves the same):input[1]message(roleuser)agent_message, plaintext content onlyagent_messagewithencrypted_contentReplacing only
input[1]with an equivalentmessageitem makes the same request succeed,so the item type alone is the trigger.
Observed in normal use
12 consecutive failures in one conversation over 15 minutes, every one of them:
The combo route had picked
xai/grok-4.6because the other candidates were on cooldown.Nothing about the failure is specific to that provider — it is specific to the item type.
Current behaviour
src/adapters/openai-responses.tsconverts the item only for the OpenCode Go destination:and the docs state the scope explicitly
(
docs-site/src/content/docs/reference/configuration/providers.md):Keeping the item "unchanged" for other destinations is a guarantee of a 422: no third-party
Responses implementation accepts a private OpenAI item type.
Proposal
Apply the existing plaintext conversion to every routed (
authMode !== "forward")Responses destination, leaving the rest untouched:
forwarddestinations keepagent_messageexactly as today — that is the only schemathat understands it.
encrypted_contentand unknown part types keep their current fail-closed path. Theencrypted v2 task surface already owns those through
unreadable_encrypted_agent_taskand the opt-in recovery route, and this issue does not propose changing it.
The narrow scope looks like an artifact of the PR that introduced the helper (a Go-specific
carry) rather than a decision about other destinations; the conversion itself is already
lossless for plaintext content and preserves author/recipient as readable text.
PR to follow.