You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Note / disclaimer: I chose to post this as an issue, rather than just submitting my own PR, because I expect that reviewing the issue report is easier than reviewing code from a random unknown contributor. I figure your agentic tools can implement the solution just as well, and I'm assuming that would actually be preferred over receiving unsolicited code for review. I used Claude Opus 4.8 to generate the content of the issue to ensure that it's as thorough and detailed as possible.
That said, I do have a patch for this already written. Let me know if you prefer for me to submit that directly.
Summary
When running a self-hosted Qwen3.6 model via a vLLM OpenAI-compatible endpoint (configured as a custom_providers entry), the model's chain-of-thought from earlier turns is never fed back to it on subsequent turns, even when the server is configured to preserve it. The model effectively "forgets" its own prior private reasoning.
Qwen3.6 was specifically trained to retain and leverage historical thinking traces via the preserve_thinking chat-template option (see the model card: "Qwen3.6 has been additionally trained to preserve and leverage thinking traces from historical messages…set the preserve_thinking option"). This is valuable for agent loops. Hermes currently defeats it on the client side.
Environment
Hermes 0.17.0
vLLM 0.23.1rc1 (OpenAI-compatible server)
Model Qwen/Qwen3.6-35B-A3B-FP8, served with --reasoning-parser qwen3 and --enable-auto-tool-choice --tool-call-parser qwen3_coder
Provider configured under custom_providers (base_url: http://…/v1)
preserve_thinking enabled at the request level via chat_template_kwargs, forwarded by Hermes' per-provider extra_body config (this already works today):
(Equivalent to the server-side --default-chat-template-kwargs '{"preserve_thinking": true}'.) Worth emphasizing: the server side is already configurable without any code change — the remaining gap is purely the client-side reasoning replay described below.
Root cause (client side)
Hermes captures and persists reasoning correctly — extract_reasoning (agent/agent_runtime_helpers.py) reads message.reasoning/reasoning_content from responses, it's stored in state.db and reloaded into history (hermes_state.py). But on replay it is stripped for any provider not in the DeepSeek/Kimi/MiMo set:
copy_reasoning_content_for_api (agent/agent_runtime_helpers.py) pops reasoning_content unless _needs_thinking_reasoning_pad() (DeepSeek/Kimi/MiMo only) is true.
agent/conversation_loop.py (~line 767) then unconditionally pops the internal reasoning key from the outgoing message ("reasoning field is for trajectory storage only").
So a Qwen3.6/vLLM provider receives assistant history with no thinking in any field, and the preserve_thinking template has nothing to render.
Why the existing echo-back path doesn't cover it (field-name detail)
The DeepSeek/Kimi/MiMo echo-back path replays reasoning under reasoning_content. On modern vLLM this is the wrong field:
With chat_template_kwargs: {"preserve_thinking": true} (via extra_body) the historical block renders; with preserve_thinking: false it is stripped — confirming the flag is the correct, template-agnostic server-side control and that it is respected.
Suggested fix
Add a gated notion of "this provider's template re-renders prior reasoning," and for such providers replay reasoning under the reasoning field (not reasoning_content), without the DeepSeek-style " " placeholder pad (empty <think> blocks hurt prefix caching — cf. QwenLM/Qwen3.8#131).
Concretely, three touch points:
A predicate (e.g. _preserves_thinking_history() in run_agent.py) identifying Qwen3.6/vLLM preserve_thinking endpoints.
In agent/conversation_loop.py (~767), keep a non-empty reasoning string on the outgoing message for such providers instead of popping it. (copy_reasoning_content_for_api already strips the useless reasoning_content for them.)
In reapply_reasoning_echo_for_provider (agent/agent_runtime_helpers.py), skip the strict-provider strip for these providers, and also strip a stale reasoning field when falling back to a strict provider.
Design note for maintainers: whether an endpoint preserves history depends on the served chat template + vLLM version, not just the model name. A config-driven switch (e.g. a preserve_thinking: true / reasoning_replay_field: reasoning flag on the custom_providers entry) would be more robust than model-name matching, and would let users opt in per endpoint. Model-name matching on qwen3.6 is a reasonable default if a config flag is undesirable.
Note / disclaimer: I chose to post this as an issue, rather than just submitting my own PR, because I expect that reviewing the issue report is easier than reviewing code from a random unknown contributor. I figure your agentic tools can implement the solution just as well, and I'm assuming that would actually be preferred over receiving unsolicited code for review. I used Claude Opus 4.8 to generate the content of the issue to ensure that it's as thorough and detailed as possible.
That said, I do have a patch for this already written. Let me know if you prefer for me to submit that directly.
Summary
When running a self-hosted Qwen3.6 model via a vLLM OpenAI-compatible endpoint (configured as a
custom_providersentry), the model's chain-of-thought from earlier turns is never fed back to it on subsequent turns, even when the server is configured to preserve it. The model effectively "forgets" its own prior private reasoning.Qwen3.6 was specifically trained to retain and leverage historical thinking traces via the
preserve_thinkingchat-template option (see the model card: "Qwen3.6 has been additionally trained to preserve and leverage thinking traces from historical messages…set thepreserve_thinkingoption"). This is valuable for agent loops. Hermes currently defeats it on the client side.Environment
Hermes
0.17.0vLLM
0.23.1rc1(OpenAI-compatible server)Model
Qwen/Qwen3.6-35B-A3B-FP8, served with--reasoning-parser qwen3and--enable-auto-tool-choice --tool-call-parser qwen3_coderProvider configured under
custom_providers(base_url: http://…/v1)preserve_thinkingenabled at the request level viachat_template_kwargs, forwarded by Hermes' per-providerextra_bodyconfig (this already works today):(Equivalent to the server-side
--default-chat-template-kwargs '{"preserve_thinking": true}'.) Worth emphasizing: the server side is already configurable without any code change — the remaining gap is purely the client-side reasoning replay described below.Root cause (client side)
Hermes captures and persists reasoning correctly —
extract_reasoning(agent/agent_runtime_helpers.py) readsmessage.reasoning/reasoning_contentfrom responses, it's stored instate.dband reloaded into history (hermes_state.py). But on replay it is stripped for any provider not in the DeepSeek/Kimi/MiMo set:copy_reasoning_content_for_api(agent/agent_runtime_helpers.py) popsreasoning_contentunless_needs_thinking_reasoning_pad()(DeepSeek/Kimi/MiMo only) is true.agent/conversation_loop.py(~line 767) then unconditionally pops the internalreasoningkey from the outgoing message ("reasoning field is for trajectory storage only").So a Qwen3.6/vLLM provider receives assistant history with no thinking in any field, and the
preserve_thinkingtemplate has nothing to render.Why the existing echo-back path doesn't cover it (field-name detail)
The DeepSeek/Kimi/MiMo echo-back path replays reasoning under
reasoning_content. On modern vLLM this is the wrong field:reasoning_content->reasoningvllm-project/vllm#27755) returns reasoning underreasoning, notreasoning_content.reasoning_contenton incoming assistant messages ([Bug]:reasoning_contentsilently dropped on incoming assistant messages vllm-project/vllm#38488) — so even enabling the current echo-back for this provider would not work.Verified empirically against vLLM 0.23.1 with the Qwen3.6 template, using the
/tokenizeendpoint (renders the sameapply_chat_templatepath):Response body puts thinking in
message.reasoning;reasoning_contentis absent.Sending an assistant message with a
reasoning_contentfield → token count unchanged; dumpingtoken_strsshows the field is absent from the rendered prompt (dropped per MCP server permanently gives up after a transient backend outage; never reconnects until gateway restart #38488).Sending the same content under a
reasoningfield → renders correctly as a historical block:With
chat_template_kwargs: {"preserve_thinking": true}(viaextra_body) the historical block renders; withpreserve_thinking: falseit is stripped — confirming the flag is the correct, template-agnostic server-side control and that it is respected.Suggested fix
Add a gated notion of "this provider's template re-renders prior reasoning," and for such providers replay reasoning under the
reasoningfield (notreasoning_content), without the DeepSeek-style" "placeholder pad (empty<think>blocks hurt prefix caching — cf. QwenLM/Qwen3.8#131).Concretely, three touch points:
_preserves_thinking_history()inrun_agent.py) identifying Qwen3.6/vLLM preserve_thinking endpoints.agent/conversation_loop.py(~767), keep a non-emptyreasoningstring on the outgoing message for such providers instead of popping it. (copy_reasoning_content_for_apialready strips the uselessreasoning_contentfor them.)reapply_reasoning_echo_for_provider(agent/agent_runtime_helpers.py), skip the strict-provider strip for these providers, and also strip a stalereasoningfield when falling back to a strict provider.Design note for maintainers: whether an endpoint preserves history depends on the served chat template + vLLM version, not just the model name. A config-driven switch (e.g. a
preserve_thinking: true/reasoning_replay_field: reasoningflag on thecustom_providersentry) would be more robust than model-name matching, and would let users opt in per endpoint. Model-name matching onqwen3.6is a reasonable default if a config flag is undesirable.Related
reasoning_contentsilently dropped on incoming assistant messages vllm-project/vllm#38488 (reasoning_contentdropped on input)reasoning_content->reasoningvllm-project/vllm#27755 (reasoning_content→reasoningrename)<think>blocks / prefix-cache impact)