hermes - ✅(Solved) Fix reasoning.available SSE event sends full reply text instead of extracted reasoning content [1 pull requests]

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…

PR fix notes

PR #13326: fix(agent): emit only extracted reasoning progress

Description (problem / solution / changelog)

Summary

  • Fixes #13007
  • Routes reasoning.available through the existing reasoning extractor
  • Prevents ordinary assistant answer text from being emitted as reasoning progress while preserving subagent _thinking previews

Root Cause

The progress callback path stripped reasoning tags from the whole assistant content and emitted the remaining text, so normal replies could be reported as reasoning.available even when no structured reasoning existed.

Tests

  • uv run --frozen --python 3.11 --extra dev pytest -o addopts='' tests/run_agent/test_run_agent.py tests/agent/test_subagent_progress.py -q (299 passed)
  • git diff --check -- run_agent.py tests/agent/test_subagent_progress.py tests/run_agent/test_run_agent.py

Changed files

  • run_agent.py (modified, +22/-5)
  • tests/agent/test_subagent_progress.py (modified, +12/-1)
  • tests/run_agent/test_run_agent.py (modified, +24/-0)

Code Example

# run_agent.py ~L9188
_think_text = assistant_message.content.strip()
_think_text = re.sub(
    r'</?(?:REASONING_SCRATCHPAD|think|reasoning)>', '', _think_text
).strip()

---

_think_match = re.search(
    r'<(?:REASONING_SCRATCHPAD|think|reasoning)>(.*?)</(?:REASONING_SCRATCHPAD|think|reasoning)>',
    assistant_message.content,
    re.DOTALL,
)
_think_text = _think_match.group(1).strip() if _think_match else ""
RAW_BUFFERClick to expand / collapse

Bug Description

In run_agent.py around line 9188, _think_text is assigned the full assistant_message.content, then reasoning XML tags are stripped with re.sub. When the model does not use <think>, <REASONING_SCRATCHPAD>, or <reasoning> tags in its response, _think_text ends up being the complete reply text — identical to what is streamed via message.delta events.

This causes the reasoning.available SSE event to fire with text equal to the full reply, making clients that display both events show duplicate content.

Steps to Reproduce

  1. Start the Gateway server
  2. Send a message that produces a plain-text response (no reasoning tags in the model output)
  3. Subscribe to the SSE stream for that run
  4. Observe that reasoning.available fires with text equal to the full reply, and message.delta events carry the same content

Current Code

# run_agent.py ~L9188
_think_text = assistant_message.content.strip()
_think_text = re.sub(
    r'</?(?:REASONING_SCRATCHPAD|think|reasoning)>', '', _think_text
).strip()

re.sub removes the tag delimiters but keeps all content between and outside them, so a plain reply like "Hello world" passes through unchanged.

Expected Behavior

reasoning.available should only fire when the model's response actually contains reasoning tags. The text field should contain only the content inside those tags.

Suggested fix:

_think_match = re.search(
    r'<(?:REASONING_SCRATCHPAD|think|reasoning)>(.*?)</(?:REASONING_SCRATCHPAD|think|reasoning)>',
    assistant_message.content,
    re.DOTALL,
)
_think_text = _think_match.group(1).strip() if _think_match else ""

Impact

Any client consuming the structured SSE stream will display reasoning and reply as identical content when the model responds without explicit reasoning tags.

extent analysis

TL;DR

The issue can be fixed by modifying the _think_text assignment to only extract content within reasoning tags using a regular expression search.

Guidance

  • Verify that the model's response contains reasoning tags before firing the reasoning.available SSE event.
  • Update the _think_text assignment to use re.search instead of re.sub to extract the content within reasoning tags.
  • Test the fix with different model responses, including those with and without reasoning tags, to ensure the reasoning.available event behaves as expected.
  • Consider adding error handling for cases where the model's response is malformed or does not contain the expected reasoning tags.

Example

_think_match = re.search(
    r'<(?:REASONING_SCRATCHPAD|think|reasoning)>(.*?)</(?:REASONING_SCRATCHPAD|think|reasoning)>',
    assistant_message.content,
    re.DOTALL,
)
_think_text = _think_match.group(1).strip() if _think_match else ""

Notes

This fix assumes that the model's response always contains properly formatted reasoning tags. If the model's response can be malformed, additional error handling may be necessary.

Recommendation

Apply the suggested fix to update the _think_text assignment, as it correctly extracts the content within reasoning tags and prevents the reasoning.available event from firing with duplicate content.

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