Skip to content

fix: salvage 4 stream/loop stability fixes (#7901, #7693, #7738, #7747) - #7910

Merged
teknium1 merged 5 commits into
mainfrom
hermes/hermes-f6cda1f0
Apr 11, 2026
Merged

fix: salvage 4 stream/loop stability fixes (#7901, #7693, #7738, #7747)#7910
teknium1 merged 5 commits into
mainfrom
hermes/hermes-f6cda1f0

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Salvages four PRs that fix agent stream/loop stability issues — stopping mid-stream, false early termination, truncated tool calls, and stuck session resume loops.

Cherry-picked PRs

PR #7901 — fix(api-server): keep chat-completions SSE alive during long tool runs (helix4u)

  • The /v1/chat/completions SSE stream went silent during tool work, causing frontends to think the run died
  • Adds periodic SSE keepalive comments + X-Accel-Buffering: no header, matching the existing pattern on the /v1/runs endpoint

PR #7693 — fix: detect truncated tool_calls when finish_reason is not length (tomqiaozc)

  • When API routers rewrite finish_reason from "length" to "tool_calls", truncated JSON args bypassed the length handler and wasted 3 retries before giving up
  • Adds truncation heuristic (args not ending in } or ]) to short-circuit early, mirroring the streaming path

PR #7738 — fix(agent): prevent false thinking-exhaustion for non-reasoning models (ygd58)

  • _thinking_exhausted fired for any model returning content=None on truncation, not just reasoning models
  • Non-reasoning models (GLM-4.7, minimax, etc.) got false "Thinking Budget Exhausted" errors instead of normal continuation retry
  • Fix: gate on _has_think_tags regex

PR #7747 — fix(gateway): break stuck session resume loops on restart (konsisumer)

  • Hung sessions survived gateway restart because the agent reloaded the stuck transcript
  • Adds suspended flag on SessionEntry, /stop marks session suspended, gateway startup suspends recently-active sessions

Follow-up fixes applied during salvage

Test results

  • tests/gateway/test_api_server.py: 107 passed
  • tests/gateway/test_sse_agent_cancel.py: 6 passed
  • tests/run_agent/: 720 passed, 1 pre-existing skip (anthropic pkg)
  • tests/gateway/test_session.py: 60 passed
  • tests/gateway/test_session_reset_notify.py: 13 passed
  • tests/gateway/test_gateway_inactivity_timeout.py: 8 passed
  • Full gateway suite: 2478 passed (19 pre-existing failures across adapters)

helix4u and others added 5 commits April 11, 2026 11:57
When API routers rewrite finish_reason from "length" to "tool_calls",
truncated JSON arguments bypassed the length handler and wasted 3
retry attempts in the generic JSON validation loop. Now detects
truncation patterns in tool call arguments regardless of finish_reason.

Fixes #7680

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Models that do not use <think> tags (e.g. GLM-4.7 on NVIDIA Build,
minimax) may return content=None or empty string when truncated. The
previous _thinking_exhausted check treated any None/empty content as
thinking-budget exhaustion, causing these models to always show the
'Thinking Budget Exhausted' error instead of attempting continuation.

Fix: gate the exhaustion check on _has_think_tags — only trigger the
exhaustion path when the model actually produced reasoning blocks
(<think>, <thinking>, <reasoning>, <REASONING_SCRATCHPAD>). Models
without think tags now fall through to the normal continuation retry
logic (up to 3 attempts).

Fixes #7729
Cherry-picked from PR #7747 with follow-up fixes:
- Narrowed suspend_all_active() to suspend_recently_active() — only
  suspends sessions updated within the last 2 minutes (likely in-flight),
  not all sessions which would unnecessarily reset idle users
- /stop with no running agent no longer suspends the session; only
  actual force-stops mark the session for reset
The test expected content=None to immediately trigger thinking-exhaustion,
but PR #7738 correctly gates that check on _has_think_tags. Without think
tags, the agent falls through to normal continuation retry (3 attempts).
@teknium1
teknium1 merged commit 59e630a into main Apr 11, 2026
5 of 6 checks passed
Codename-11 added a commit to Codename-11/hermes-agent that referenced this pull request Apr 12, 2026
The /api/sessions/{id}/chat/stream SSE loop lacked the periodic
keepalive heartbeat that the /v1/chat/completions endpoint already
has (added in NousResearch#7910). During agent initialization and long LLM API
calls (especially with extended thinking), the stream goes silent
for 15-60+ seconds. Clients and intermediary proxies interpret the
silence as a stalled connection and drop it, resulting in 0-byte
responses and the user seeing typing indicators but no reply.

Adds the same keepalive pattern: track last_activity with
time.monotonic(), emit SSE comment keepalives every 30 seconds
(CHAT_COMPLETIONS_SSE_KEEPALIVE_SECONDS), and reset the timer on
each data frame write.
Codename-11 added a commit to Codename-11/hermes-agent that referenced this pull request Apr 13, 2026
The /api/sessions/{id}/chat/stream SSE loop lacked the periodic
keepalive heartbeat that the /v1/chat/completions endpoint already
has (added in NousResearch#7910). During agent initialization and long LLM API
calls (especially with extended thinking), the stream goes silent
for 15-60+ seconds. Clients and intermediary proxies interpret the
silence as a stalled connection and drop it, resulting in 0-byte
responses and the user seeing typing indicators but no reply.

Adds the same keepalive pattern: track last_activity with
time.monotonic(), emit SSE comment keepalives every 30 seconds
(CHAT_COMPLETIONS_SSE_KEEPALIVE_SECONDS), and reset the timer on
each data frame write.
luigileap added a commit to luigileap/hermes-agent that referenced this pull request Apr 16, 2026
…mpressor

The `_make_compressor` helper uses `ContextCompressor.__new__` to bypass
`__init__`, so every attribute referenced by the method under test must
be set explicitly.

PR NousResearch#7910 (fix(agent): route compression aux through live session runtime)
added a `main_runtime` dict inside `_generate_summary` that reads
`self.model`, `self.provider`, `self.base_url`, `self.api_key`, and
`self.api_mode`.  The focus-topic regression test bypassed `__init__`,
so those attributes were missing and `_generate_summary` raised
`AttributeError: 'ContextCompressor' object has no attribute 'model'`,
making the test fail with `result is None` (the method returned None
via its broad-exception cooldown path) and `KeyError: 'messages'` on
the captured-prompt assertion.

Initialise the attributes in `_make_compressor` so the test exercises
the real code path.  Same pattern as the prior CLI-interrupt fix that
added missing attributes for a `SomeClass.__new__(SomeClass)` stub.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants