Skip to content

fix(gateway): offload /model switch off the event loop - #53603

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-d7b84599
Jun 27, 2026
Merged

teknium1 merged 1 commit into
mainfrom
hermes/hermes-d7b84599

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

Summary

The Telegram/Discord /model switch no longer freezes the gateway event loop.

Root cause: the /model command's actual switch called switch_model() directly on the asyncio event loop. switch_model() can fall through to a synchronous models.dev HTTP fetch (requests.get, 15s timeout) on a cold/expired cache, stalling the gateway for up to 15s and dropping the Telegram connection mid-switch.

The picker provider-list and fallback text-list sites were already offloaded in #41289, but the two _switch_model() calls — the picker callback and the direct /model <name> path — were not.

Changes

  • gateway/slash_commands.py: wrap both _switch_model() calls in asyncio.to_thread (picker callback _on_model_selected + direct /model <name> path).
  • tests/gateway/test_model_command_custom_providers.py: regression test proving the direct switch routes switch_model through to_thread.

Validation

Before After
/model <name> switch runs switch_model() on event loop → up to 15s freeze on cold cache offloaded to worker thread → loop stays responsive
picker callback switch runs on event loop offloaded
targeted tests 2 passed (test_model_command_custom_providers.py)

Closes #20525. Supersedes the stale author PRs #20530/#20979 (both 5700+ commits behind, targeting gateway/run.py where the handler no longer lives — it moved to gateway/slash_commands.py).

Infographic

offload-model-switch-off-event-loop

The Telegram/Discord /model command's actual switch calls switch_model()
directly on the asyncio event loop. switch_model() can fall through to a
synchronous models.dev HTTP fetch (requests.get, 15s timeout) on a cold or
expired cache, freezing the gateway for up to 15s and dropping the Telegram
connection while a user switches models.

The picker provider-list and fallback text-list sites were already offloaded
(#41289), but the two _switch_model() calls — the picker callback and the
direct /model <name> path — were not. Wrap both in asyncio.to_thread.

Closes #20525.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-d7b84599 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11506 on HEAD, 11506 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues (1):

Rule Count
invalid-argument-type 1
First entries
gateway/slash_commands.py:1463: [invalid-argument-type] invalid-argument-type: Argument to function `switch_model` is incorrect: Expected `dict[Unknown, Unknown]`, found `None | Unknown`

Unchanged: 6055 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jun 27, 2026
@teknium1
teknium1 merged commit d4c2217 into main Jun 27, 2026
30 checks passed
@teknium1
teknium1 deleted the hermes/hermes-d7b84599 branch June 27, 2026 11:36
pai-scaffolde pushed a commit to Scaffolde/hermes-agent that referenced this pull request Jun 28, 2026
…53603)

The Telegram/Discord /model command's actual switch calls switch_model()
directly on the asyncio event loop. switch_model() can fall through to a
synchronous models.dev HTTP fetch (requests.get, 15s timeout) on a cold or
expired cache, freezing the gateway for up to 15s and dropping the Telegram
connection while a user switches models.

The picker provider-list and fallback text-list sites were already offloaded
(NousResearch#41289), but the two _switch_model() calls — the picker callback and the
direct /model <name> path — were not. Wrap both in asyncio.to_thread.

Closes NousResearch#20525.
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…53603)

The Telegram/Discord /model command's actual switch calls switch_model()
directly on the asyncio event loop. switch_model() can fall through to a
synchronous models.dev HTTP fetch (requests.get, 15s timeout) on a cold or
expired cache, freezing the gateway for up to 15s and dropping the Telegram
connection while a user switches models.

The picker provider-list and fallback text-list sites were already offloaded
(NousResearch#41289), but the two _switch_model() calls — the picker callback and the
direct /model <name> path — were not. Wrap both in asyncio.to_thread.

Closes NousResearch#20525.
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…53603)

The Telegram/Discord /model command's actual switch calls switch_model()
directly on the asyncio event loop. switch_model() can fall through to a
synchronous models.dev HTTP fetch (requests.get, 15s timeout) on a cold or
expired cache, freezing the gateway for up to 15s and dropping the Telegram
connection while a user switches models.

The picker provider-list and fallback text-list sites were already offloaded
(NousResearch#41289), but the two _switch_model() calls — the picker callback and the
direct /model <name> path — were not. Wrap both in asyncio.to_thread.

Closes NousResearch#20525.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…53603)

The Telegram/Discord /model command's actual switch calls switch_model()
directly on the asyncio event loop. switch_model() can fall through to a
synchronous models.dev HTTP fetch (requests.get, 15s timeout) on a cold or
expired cache, freezing the gateway for up to 15s and dropping the Telegram
connection while a user switches models.

The picker provider-list and fallback text-list sites were already offloaded
(NousResearch#41289), but the two _switch_model() calls — the picker callback and the
direct /model <name> path — were not. Wrap both in asyncio.to_thread.

Closes NousResearch#20525.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…53603)

The Telegram/Discord /model command's actual switch calls switch_model()
directly on the asyncio event loop. switch_model() can fall through to a
synchronous models.dev HTTP fetch (requests.get, 15s timeout) on a cold or
expired cache, freezing the gateway for up to 15s and dropping the Telegram
connection while a user switches models.

The picker provider-list and fallback text-list sites were already offloaded
(NousResearch#41289), but the two _switch_model() calls — the picker callback and the
direct /model <name> path — were not. Wrap both in asyncio.to_thread.

Closes NousResearch#20525.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…53603)

The Telegram/Discord /model command's actual switch calls switch_model()
directly on the asyncio event loop. switch_model() can fall through to a
synchronous models.dev HTTP fetch (requests.get, 15s timeout) on a cold or
expired cache, freezing the gateway for up to 15s and dropping the Telegram
connection while a user switches models.

The picker provider-list and fallback text-list sites were already offloaded
(NousResearch#41289), but the two _switch_model() calls — the picker callback and the
direct /model <name> path — were not. Wrap both in asyncio.to_thread.

Closes NousResearch#20525.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…53603)

The Telegram/Discord /model command's actual switch calls switch_model()
directly on the asyncio event loop. switch_model() can fall through to a
synchronous models.dev HTTP fetch (requests.get, 15s timeout) on a cold or
expired cache, freezing the gateway for up to 15s and dropping the Telegram
connection while a user switches models.

The picker provider-list and fallback text-list sites were already offloaded
(NousResearch#41289), but the two _switch_model() calls — the picker callback and the
direct /model <name> path — were not. Wrap both in asyncio.to_thread.

Closes NousResearch#20525.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Telegram bot freezes when switching providers/models due to blocking HTTP call in asyncio event loop

2 participants