Summary
Budget trimming can split an assistant tool-call message from its matching tool-result message. The next OpenAI Codex request then contains a function_call_output without its parent function_call, and the provider rejects the entire turn.
Environment
- Observed with Raven v0.1.3
- Confirmed by code inspection on current
main
- Provider: OpenAI Codex OAuth using the Responses API
- Surface: a long-running gateway conversation after Curator history selection exceeds the prompt budget
Minimal reproduction
- Build session history containing a user message, an assistant message with
tool_calls, and its matching tool-result message.
- Select the complete exchange through
HistoryTrimmer.canonical_ids.
- Force
HistoryTrimmer.trim over budget so the assistant message is the first non-protected item.
- Observe that the current loop drops only the assistant message and keeps the tool result.
- Send the resulting history through the OpenAI Codex provider.
Expected behavior
An assistant tool-call message and all of its corresponding tool-result messages should be treated as one structural unit during budget trimming. The entire exchange should be kept or dropped together.
Actual behavior
The parent assistant message can be dropped while the tool result remains. Structural validation is performed after the invalid history has already been built, and the provider returns:
HTTP 400: No tool call found for function call output with call_id call_REDACTED.
The gateway remains healthy and continues receiving inbound messages, but the affected conversation cannot produce a useful reply.
Root cause
HistoryTrimmer.trim closes the initial selection over tool adjacency, then removes individual message IDs inside its budget loop without preserving that closure.
Proposed fix
- Resolve the full tool exchange for the selected message.
- Remove the assistant call and all sibling tool results atomically.
- Add a regression test that forces the budget loop to drop a tool exchange and verifies that no orphan result remains.
No user data, private paths, tokens, or conversation content are included in this report.
Summary
Budget trimming can split an assistant tool-call message from its matching tool-result message. The next OpenAI Codex request then contains a
function_call_outputwithout its parentfunction_call, and the provider rejects the entire turn.Environment
mainMinimal reproduction
tool_calls, and its matching tool-result message.HistoryTrimmer.canonical_ids.HistoryTrimmer.trimover budget so the assistant message is the first non-protected item.Expected behavior
An assistant tool-call message and all of its corresponding tool-result messages should be treated as one structural unit during budget trimming. The entire exchange should be kept or dropped together.
Actual behavior
The parent assistant message can be dropped while the tool result remains. Structural validation is performed after the invalid history has already been built, and the provider returns:
The gateway remains healthy and continues receiving inbound messages, but the affected conversation cannot produce a useful reply.
Root cause
HistoryTrimmer.trimcloses the initial selection over tool adjacency, then removes individual message IDs inside its budget loop without preserving that closure.Proposed fix
No user data, private paths, tokens, or conversation content are included in this report.