fix(gateway): reload fallback_providers on live agent create/reuse

Gateway froze the fallback chain at process start while cron reloads it
per job, so a chain configured after hermes gateway was running never
reached messaging sessions. Refresh from disk on agent create and when
reusing a cached agent.

Fixes #60955.

(cherry picked from commit b64e7155b2)
This commit is contained in:
HexLab98 2026-07-08 22:33:27 +07:00 committed by kshitij
parent 74e28f7d10
commit be1346cf2a

View file

@ -5008,6 +5008,42 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
pass
return None
def _refresh_fallback_model(self) -> list | None:
"""Re-read fallback_providers from disk for the next agent create/reuse.
Cron already does this per job via ``get_fallback_chain``; the gateway
previously froze ``self._fallback_model`` at process start, so a chain
configured (or changed) after ``hermes gateway`` was running never
reached messaging sessions even though the same process's cron jobs
fell back correctly. Fixes #60955.
"""
self._fallback_model = self._load_fallback_model()
return self._fallback_model
@staticmethod
def _apply_fallback_chain_to_agent(agent: Any, chain: list | None) -> None:
"""Keep a cached agent's fallback chain aligned with current config.
Skips rewrite while a cooldown is holding the agent on an already-
activated fallback provider ``restore_primary_runtime`` owns that
turn-scoped lifecycle. When primary is active (or cooldown expired),
replace the chain so mid-uptime ``fallback_providers`` edits take
effect without requiring a gateway restart (#60955).
"""
if agent is None:
return
new_chain = list(chain or [])
rate_limited_until = getattr(agent, "_rate_limited_until", 0) or 0
if (
getattr(agent, "_fallback_activated", False)
and rate_limited_until > time.monotonic()
):
return
agent._fallback_chain = new_chain
agent._fallback_model = new_chain[0] if new_chain else None
if not getattr(agent, "_fallback_activated", False):
agent._fallback_index = 0
def _snapshot_running_agents(self) -> Dict[str, Any]:
return {
session_key: agent
@ -13244,7 +13280,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
chat_type=source.chat_type,
thread_id=source.thread_id,
session_db=getattr(self._session_db, "_db", self._session_db),
fallback_model=self._fallback_model,
# Reload from disk — do not reuse the startup snapshot (#60955).
fallback_model=self._refresh_fallback_model(),
)
try:
return agent.run_conversation(
@ -18062,6 +18099,12 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
# Refresh agent max_iterations from current config
# (cached agent may have been created with old config)
agent.max_iterations = max_iterations
# Refresh fallback chain the same way — a chain
# configured after this agent was cached (or after
# gateway start) must reach the next turn (#60955).
self._apply_fallback_chain_to_agent(
agent, self._refresh_fallback_model(),
)
logger.debug("Reusing cached agent for session %s", session_key)
reused_cached_agent = True
@ -18117,7 +18160,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
thread_id=source.thread_id,
gateway_session_key=session_key,
session_db=getattr(self._session_db, "_db", self._session_db),
fallback_model=self._fallback_model,
# Reload from disk — do not reuse the startup snapshot (#60955).
fallback_model=self._refresh_fallback_model(),
)
if _cache_lock and _cache is not None:
with _cache_lock: