fix(agent): block cross-provider reasoning leak to DeepSeek/Kimi (#15748)#16500
Merged
Conversation
) On provider switches mid-session (e.g. MiniMax -> DeepSeek), the source assistant turn carries a 'reasoning' field written by the prior provider but no 'reasoning_content' key. _copy_reasoning_content_for_api would promote that foreign 'reasoning' to 'reasoning_content' on the outbound DeepSeek request, leaking a cross-provider chain of thought and in practice causing HTTP 400. DeepSeek's own _build_assistant_message always pins reasoning_content='' at creation time for tool-call turns, so the shape (reasoning set, reasoning_content absent, tool_calls present) is unreachable from same-provider DeepSeek history — it can only come from a prior provider. Pad with '' in that case instead of promoting. Healthy same-provider 'reasoning' promotion (no tool_calls, or on providers that do not require the empty-string pin) is unchanged.
This was referenced Apr 27, 2026
Closed
cluricaun28
referenced
this pull request
in cluricaun28/Logos
Apr 28, 2026
…748) (#16500) On provider switches mid-session (e.g. MiniMax -> DeepSeek), the source assistant turn carries a 'reasoning' field written by the prior provider but no 'reasoning_content' key. _copy_reasoning_content_for_api would promote that foreign 'reasoning' to 'reasoning_content' on the outbound DeepSeek request, leaking a cross-provider chain of thought and in practice causing HTTP 400. DeepSeek's own _build_assistant_message always pins reasoning_content='' at creation time for tool-call turns, so the shape (reasoning set, reasoning_content absent, tool_calls present) is unreachable from same-provider DeepSeek history — it can only come from a prior provider. Pad with '' in that case instead of promoting. Healthy same-provider 'reasoning' promotion (no tool_calls, or on providers that do not require the empty-string pin) is unchanged.
ulasbilgen
pushed a commit
to ulasbilgen/hermes-adhd-agent
that referenced
this pull request
May 1, 2026
…sResearch#15748) (NousResearch#16500) On provider switches mid-session (e.g. MiniMax -> DeepSeek), the source assistant turn carries a 'reasoning' field written by the prior provider but no 'reasoning_content' key. _copy_reasoning_content_for_api would promote that foreign 'reasoning' to 'reasoning_content' on the outbound DeepSeek request, leaking a cross-provider chain of thought and in practice causing HTTP 400. DeepSeek's own _build_assistant_message always pins reasoning_content='' at creation time for tool-call turns, so the shape (reasoning set, reasoning_content absent, tool_calls present) is unreachable from same-provider DeepSeek history — it can only come from a prior provider. Pad with '' in that case instead of promoting. Healthy same-provider 'reasoning' promotion (no tool_calls, or on providers that do not require the empty-string pin) is unchanged.
donald131
pushed a commit
to donald131/hermes-agent
that referenced
this pull request
May 2, 2026
…sResearch#15748) (NousResearch#16500) On provider switches mid-session (e.g. MiniMax -> DeepSeek), the source assistant turn carries a 'reasoning' field written by the prior provider but no 'reasoning_content' key. _copy_reasoning_content_for_api would promote that foreign 'reasoning' to 'reasoning_content' on the outbound DeepSeek request, leaking a cross-provider chain of thought and in practice causing HTTP 400. DeepSeek's own _build_assistant_message always pins reasoning_content='' at creation time for tool-call turns, so the shape (reasoning set, reasoning_content absent, tool_calls present) is unreachable from same-provider DeepSeek history — it can only come from a prior provider. Pad with '' in that case instead of promoting. Healthy same-provider 'reasoning' promotion (no tool_calls, or on providers that do not require the empty-string pin) is unchanged.
02356abc
pushed a commit
to 02356abc/hermes-agent
that referenced
this pull request
May 14, 2026
…sResearch#15748) (NousResearch#16500) On provider switches mid-session (e.g. MiniMax -> DeepSeek), the source assistant turn carries a 'reasoning' field written by the prior provider but no 'reasoning_content' key. _copy_reasoning_content_for_api would promote that foreign 'reasoning' to 'reasoning_content' on the outbound DeepSeek request, leaking a cross-provider chain of thought and in practice causing HTTP 400. DeepSeek's own _build_assistant_message always pins reasoning_content='' at creation time for tool-call turns, so the shape (reasoning set, reasoning_content absent, tool_calls present) is unreachable from same-provider DeepSeek history — it can only come from a prior provider. Pad with '' in that case instead of promoting. Healthy same-provider 'reasoning' promotion (no tool_calls, or on providers that do not require the empty-string pin) is unchanged.
dannyJ848
pushed a commit
to dannyJ848/hermes-agent
that referenced
this pull request
May 17, 2026
…sResearch#15748) (NousResearch#16500) On provider switches mid-session (e.g. MiniMax -> DeepSeek), the source assistant turn carries a 'reasoning' field written by the prior provider but no 'reasoning_content' key. _copy_reasoning_content_for_api would promote that foreign 'reasoning' to 'reasoning_content' on the outbound DeepSeek request, leaking a cross-provider chain of thought and in practice causing HTTP 400. DeepSeek's own _build_assistant_message always pins reasoning_content='' at creation time for tool-call turns, so the shape (reasoning set, reasoning_content absent, tool_calls present) is unreachable from same-provider DeepSeek history — it can only come from a prior provider. Pad with '' in that case instead of promoting. Healthy same-provider 'reasoning' promotion (no tool_calls, or on providers that do not require the empty-string pin) is unchanged.
gweeteve
pushed a commit
to gweeteve/hermes-agent
that referenced
this pull request
Jun 2, 2026
…sResearch#15748) (NousResearch#16500) On provider switches mid-session (e.g. MiniMax -> DeepSeek), the source assistant turn carries a 'reasoning' field written by the prior provider but no 'reasoning_content' key. _copy_reasoning_content_for_api would promote that foreign 'reasoning' to 'reasoning_content' on the outbound DeepSeek request, leaking a cross-provider chain of thought and in practice causing HTTP 400. DeepSeek's own _build_assistant_message always pins reasoning_content='' at creation time for tool-call turns, so the shape (reasoning set, reasoning_content absent, tool_calls present) is unreachable from same-provider DeepSeek history — it can only come from a prior provider. Pad with '' in that case instead of promoting. Healthy same-provider 'reasoning' promotion (no tool_calls, or on providers that do not require the empty-string pin) is unchanged.
Egavasyug
pushed a commit
to Egavasyug/hermes-agent
that referenced
this pull request
Jun 10, 2026
…sResearch#15748) (NousResearch#16500) On provider switches mid-session (e.g. MiniMax -> DeepSeek), the source assistant turn carries a 'reasoning' field written by the prior provider but no 'reasoning_content' key. _copy_reasoning_content_for_api would promote that foreign 'reasoning' to 'reasoning_content' on the outbound DeepSeek request, leaking a cross-provider chain of thought and in practice causing HTTP 400. DeepSeek's own _build_assistant_message always pins reasoning_content='' at creation time for tool-call turns, so the shape (reasoning set, reasoning_content absent, tool_calls present) is unreachable from same-provider DeepSeek history — it can only come from a prior provider. Pad with '' in that case instead of promoting. Healthy same-provider 'reasoning' promotion (no tool_calls, or on providers that do not require the empty-string pin) is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cross-provider session switches (e.g. MiniMax → DeepSeek) no longer leak the prior provider's chain of thought into DeepSeek's
reasoning_content— closes #15748.Root cause:
_copy_reasoning_content_for_apipromoted anyreasoningfield toreasoning_contentbefore the DeepSeek/Kimi empty-pad check. When the source turn came from a different provider (noreasoning_contentkey,reasoningset by the prior provider), the foreign chain of thought was sent to DeepSeek on replay.Changes
run_agent.py::_copy_reasoning_content_for_api: new step 2 — when on DeepSeek/Kimi AND the source turn hastool_callsANDreasoningis set ANDreasoning_contentkey is absent, inject""instead of promotingreasoning. Rationale:_build_assistant_messagealways pinsreasoning_content=""for same-provider DeepSeek tool-call turns, so that shape is unreachable from same-provider history.test_deepseek_reasoning_field_promotedto exercise the reachable same-provider shape (notool_calls), addtest_deepseek_poisoned_cross_provider_history_padded+test_kimi_poisoned_cross_provider_history_paddedfor the [Bug] _copy_reasoning_content_for_api: cross-provider reasoning promotion leaks stale content to DeepSeek/Kimi #15748 scenario.Validation
'MiniMax thinking...'""reasoning_contentset (incl.""placeholder)Test results:
tests/run_agent/test_deepseek_reasoning_content_echo.py— 23 passed (21 existing + 2 new).