Skip to content

fix(agent): match tool results on call_id||id in pre-request repair (#58168) - #58179

Merged
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:fix/58168-orphan-tool-compaction
Jul 4, 2026
Merged

fix(agent): match tool results on call_id||id in pre-request repair (#58168)#58179
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:fix/58168-orphan-tool-compaction

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

Valid tool results are no longer dropped as false orphans by the pre-request repair pass when the assistant tool_call uses the Codex Responses id != call_id format — closing the HTTP 400 that permanently wedged long-running sessions (#58168).

Root cause: repair_message_sequence Pass 1 built its known-id set from only tc.get("id"), then matched tool results by tool_call_id. In Codex Responses format an assistant tool_call carries both id (fc_...) and a distinct call_id (call_...), and a tool result's tool_call_id may be keyed on either depending on which builder produced it. A result keyed on call_id looked orphaned, so the pass dropped it — leaving the assistant tool_call unanswered and triggering, on strict providers (DeepSeek, Kimi): Messages with role 'tool' must be a response to a preceding message with 'tool_calls'. Sessions that persisted such a sequence re-sent the orphan every turn and could never recover.

Changes

  • agent/agent_runtime_helpers.py:
    • repair_message_sequence Pass 1 now registers both id and call_id for every assistant tool_call, so a tool result matching either key is recognized — consistent with AIAgent._get_tool_call_id_static and the compressor's _sanitize_tool_pairs.
    • _sanitize_tool_call_arguments (corrupted-args repair) now uses the same call_id || id precedence for its existing-result scan and stub insertion — it had the identical mismatch and would insert a duplicate stub that itself became an orphan.
  • tests/run_agent/test_message_sequence_repair.py: 3 regression tests for the codex id != call_id case (match on call_id; match on only call_id; match on id when both present).

Validation

Scenario Before After
assistant id=fc_1 call_id=call_1, tool tool_call_id=call_1 tool dropped → assistant orphaned → 400 pair preserved, repairs=0
assistant only call_id, tool matches call_id tool dropped preserved
assistant id+call_id, tool matches id preserved preserved
genuine orphan tool (no matching assistant) dropped dropped (unchanged)
baseline OpenAI id==tool_call_id preserved preserved (repairs=0)
corrupted-args + codex-format existing result duplicate orphan stub inserted real result found, marker prepended, no orphan
  • Targeted suites: 277 passed / 0 failed (test_message_sequence_repair, test_tool_call_args_sanitizer, test_strict_api_validation, test_provider_parity, test_context_compressor).
  • E2E verified with real imports against a temp HERMES_HOME.
  • ruff check: clean.

Fixes #58168

…ousResearch#58168)

repair_message_sequence Pass 1 registered only tc.get("id") when building
the set of known assistant tool_call ids, then matched tool results against
it by tool_call_id. In the Codex Responses format an assistant tool_call
carries both id (fc_...) and a distinct call_id (call_...); a tool result's
tool_call_id may be keyed on either depending on which builder produced it.
Registering only id made a valid tool result whose tool_call_id matched
call_id look orphaned, so the pass dropped it and left the assistant
tool_call unanswered -- producing HTTP 400 on strict providers (DeepSeek,
Kimi): 'Messages with role tool must be a response to a preceding message
with tool_calls'. Long-running sessions that persisted such a sequence were
permanently broken, re-sending the orphan every turn.

Register both id and call_id for each assistant tool_call so a result
matching either key is recognized, consistent with
AIAgent._get_tool_call_id_static and the compressor's _sanitize_tool_pairs.
Apply the same call_id||id precedence to the corrupted-args sanitizer's
existing-result scan / stub insertion, which had the identical mismatch.

Adds 3 regression tests covering the codex id!=call_id case (match on
call_id, match on only call_id, match on id when both present).
@kshitijk4poor
kshitijk4poor force-pushed the fix/58168-orphan-tool-compaction branch from 83b7dad to 12083e4 Compare July 4, 2026 09:39
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) July 4, 2026 09:40
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint codex sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state P2 Medium — degraded but workaround exists labels Jul 4, 2026
@kshitijk4poor
kshitijk4poor merged commit 88f2c0c into NousResearch:main Jul 4, 2026
29 checks passed
@kshitijk4poor
kshitijk4poor deleted the fix/58168-orphan-tool-compaction branch August 5, 2026 07:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Context compaction produces invalid message sequences (orphaned tool messages), breaking sessions permanently

2 participants