hermes - 💡(How to fix) Fix Tool-message orphaning after message drops causes Moonshot AI / Kimi 400

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

Error from provider: Provider returned error

Root Cause

When an assistant message with tool_calls gets separated from its matching tool results—e.g. because a thinking-only assistant turn in between is dropped by _drop_thinking_only_and_merge_users(), or because a context-compressor boundary shift leaves a tool result dangling after a non-assistant message—the provider rejects the request with:

Fix Action

Fix

Replace the global set check with a single forward pass that tracks the most recent assistant's tool_call_ids and marks a tool message orphaned if its tool_call_id is not in that active set. Any non-assistant, non-tool message (user, system, etc.) resets the active set, so a tool message can only survive when it directly follows its parent assistant.

Code Example

Error from provider: Provider returned error
Invalid request: messages with role 'tool' must be a response to a preceeding message with 'tool_calls'

---

system
user
assistant (tool_calls=[call_A])   ← owns call_A
tool (tool_call_id=call_A)        ← immediate child of above assistant
user                              ← breaks the chain
assistant (thinking-only)         ← dropped by _drop_thinking_only_and_merge_users
tool (tool_call_id=call_A)        ← now follows a user message; provider rejects
RAW_BUFFERClick to expand / collapse

Bug

Hermes' pre-flight sanitizer (_sanitize_api_messages in run_agent.py) and the context compressor's sanitizer (_sanitize_tool_pairs in agent/context_compressor.py) both rely on a global set check to decide whether a tool message is orphaned:

  1. Collect every tool_call_id that appears in any assistant message into a set.
  2. Drop tool messages whose tool_call_id is not in that set.

This is insufficient for strict providers such as Moonshot AI (Kimi), which require that a tool message be immediately preceded by an assistant message whose tool_calls array contains that exact tool_call_id.

Failure mode

When an assistant message with tool_calls gets separated from its matching tool results—e.g. because a thinking-only assistant turn in between is dropped by _drop_thinking_only_and_merge_users(), or because a context-compressor boundary shift leaves a tool result dangling after a non-assistant message—the provider rejects the request with:

Error from provider: Provider returned error
Invalid request: messages with role 'tool' must be a response to a preceeding message with 'tool_calls'

Reproducer scenario

system
user
assistant (tool_calls=[call_A])   ← owns call_A
tool (tool_call_id=call_A)        ← immediate child of above assistant
user                              ← breaks the chain
assistant (thinking-only)         ← dropped by _drop_thinking_only_and_merge_users
tool (tool_call_id=call_A)        ← now follows a user message; provider rejects

The global-set check keeps the last tool message because call_A exists somewhere earlier, but Moonshot/Kimi reject it because the immediately preceding message is user, not the owning assistant.

Fix

Replace the global set check with a single forward pass that tracks the most recent assistant's tool_call_ids and marks a tool message orphaned if its tool_call_id is not in that active set. Any non-assistant, non-tool message (user, system, etc.) resets the active set, so a tool message can only survive when it directly follows its parent assistant.

Files changed

  • run_agent.py_sanitize_api_messages()
  • agent/context_compressor.py_sanitize_tool_pairs()

Related

  • Internal skill reference: references/tool-message-orphaning-bug.md

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING