hermes - ✅(Solved) Fix [Bug] TUI sessions do not propagate fallback_model/fallback_providers to AIAgent [3 pull requests, 1 comments, 2 participants]

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…
GitHub stats
NousResearch/hermes-agent#28753Fetched 2026-05-20 04:02:17
View on GitHub
Comments
1
Participants
2
Timeline
8
Reactions
0
Author
Timeline (top)
cross-referenced ×3labeled ×3commented ×1referenced ×1

Root Cause

tui_gateway/server.py::_create_agent() constructs AIAgent(...) without passing fallback_model:

return AIAgent(
    model=model,
    max_iterations=_cfg_max_turns(cfg, 90),
    provider=runtime.get("provider"),
    base_url=runtime.get("base_url"),
    api_key=runtime.get("api_key"),
    api_mode=runtime.get("api_mode"),
    ...
    session_db=_get_db(),
    ...
)

Because agent/agent_init.py only initializes _fallback_chain from the fallback_model constructor parameter, this path ends up with:

agent._fallback_chain = []

For comparison, gateway/run.py loads and passes the chain:

self._fallback_model = self._load_fallback_model()
...
AIAgent(..., fallback_model=self._fallback_model)

Fix Action

Fixed

PR fix notes

PR #12: fix(tui): propagate fallback_model/fallback_providers to AIAgent (#28753)

Description (problem / solution / changelog)

🟡 Merge order: 8 / 12 — new helper function, moderate risk

Closes #28753 (P2)

Problem

TUI sessions never passed fallback_model to AIAgent, so the fallback chain was always empty. When the primary model fails, the agent exhausts retries instead of activating fallback.

Fix

  • Add _load_fallback_model() helper (reads config)
  • Wire into _make_agent() and _background_agent_kwargs()

Risk assessment

FactorRating
Lines changed8/-1
New code1 function + 2 call sites
Side effectsNone — only affects TUI sessions, and only when fallback is configured
Revert complexityEasy

Testing notes

  • Configure fallback_model in config.yaml, start hermes --tui, make primary fail → confirm fallback activates

Files changed

  • tui_gateway/server.py (+8/-1)

Changed files

  • tui_gateway/server.py (modified, +8/-1)

PR #29048: feat: Typed Hook Payloads (Phase 2) + Path Parity Tests (Phase 3)

Description (problem / solution / changelog)

Summary

Implements Phase 2 (Typed Hook Payloads) and Phase 3 (Path Parity Tests) of FR #28984.

Phase 1 was submitted separately as PR #28995.


Phase 2 — Typed Hook Payloads

New file: hermes_cli/hook_payloads.py (276 lines)

Defines frozen dataclass payloads for all 14 hook types:

HookDataclass
pre_tool_callPreToolCallPayload
post_tool_callPostToolCallPayload
transform_tool_resultTransformToolResultPayload
on_session_startSessionStartPayload
on_session_endSessionEndPayload
on_session_finalizeSessionFinalizePayload
on_session_resetSessionResetPayload
pre_approval_requestPreApprovalRequestPayload
post_approval_responsePostApprovalResponsePayload
pre_llm_callPreLlmCallPayload
post_llm_callPostLlmCallPayload
pre_api_requestPreApiRequestPayload
post_api_requestPostApiRequestPayload
subagent_stopSubagentStopPayload

payload_to_kwargs() converts dataclass → plain dict, preserving cb(**kwargs) compatibility for all existing plugin callbacks.

5 call sites migrated (adds type safety, zero breaking changes):

FileHook
hermes_cli/plugins.pyget_pre_tool_call_block_message()
agent/conversation_loop.pyon_session_start, pre_llm_call
model_tools.pypost_tool_call, transform_tool_result

New tests: tests/hermes_cli/test_hook_payloads.py — 21 tests covering all payloads, frozen immutability, round-trip, registry completeness.


Phase 3 — Path Parity Tests

New file: hermes_cli/path_parity.py (155 lines)

assert_field_parity(
    "fallback_model",
    {
        "gateway_run_agent": lambda: extract_gateway_fields(),
        "tui_make_agent": lambda: extract_tui_fields(),
    },
)

When any path does not consume the field, raises PathParityError with a diff showing which paths are missing it.

3 known divergences documented as failing tests:

  • #28753: TUI doesn't pass fallback_model to AIAgent
  • #28746: idle-expiry path doesn't emit session:end event
  • #28637: /model switch loses per-model token usage

New tests: tests/hermes_cli/test_path_parity.py — 13 tests covering the helper functions and documented divergence patterns.


What This Prevents

Future scenarioWithout Phase 2With Phase 2
New param added to pre_tool_call2 of 3 call sites miss itDataclass forces all sites to provide it
Hook field renamedSilent breakage in pluginsFrozen dataclass catches at construction
Future scenarioWithout Phase 3With Phase 3
fallback_model added to gateway but forgotten in TUISilent divergence (#28753)assert_field_parity fails in CI
session:end forgotten in one exit pathBug hidden until user hits expiry path (#28746)Test documents expected parity

Test Results

tests/hermes_cli/test_hook_payloads.py  21 passed
tests/hermes_cli/test_path_parity.py   13 passed
tests/hermes_cli/test_plugins.py       105 passed
tests/test_model_tools.py               passed
tests/test_transform_tool_result_hook.py passed
tests/agent/test_plugin_llm.py          passed
tests/agent/test_shell_hooks.py         passed
─────────────────────────────────────────────
Total                                     139 passed

Refs: FR #28984 | PR #28995 (Phase 1)

Changed files

  • .dev-workflow/code-graph.db (added, +0/-0)
  • agent/conversation_loop.py (modified, +6/-4)
  • hermes_cli/hook_payloads.py (added, +276/-0)
  • hermes_cli/path_parity.py (added, +155/-0)
  • hermes_cli/plugins.py (modified, +7/-2)
  • hook_system_analysis.md (added, +348/-0)
  • model_tools.py (modified, +6/-4)
  • plans/path_parity_analysis.md (added, +135/-0)
  • tests/hermes_cli/test_hook_payloads.py (added, +245/-0)
  • tests/hermes_cli/test_path_parity.py (added, +198/-0)

Code Example

model:
     provider: openai-codex
     default: gpt-5.5

   fallback_model:
     provider: zenmux-anthropic
     model: anthropic/claude-sonnet-4.6

---

API call failed (attempt 3/3) ... provider=openai-codex ... model=gpt-5.5 summary=Request timed out.
   API call failed after 3 retries. Request timed out. | provider=openai-codex model=gpt-5.5

---

return AIAgent(
    model=model,
    max_iterations=_cfg_max_turns(cfg, 90),
    provider=runtime.get("provider"),
    base_url=runtime.get("base_url"),
    api_key=runtime.get("api_key"),
    api_mode=runtime.get("api_mode"),
    ...
    session_db=_get_db(),
    ...
)

---

agent._fallback_chain = []

---

self._fallback_model = self._load_fallback_model()
...
AIAgent(..., fallback_model=self._fallback_model)

---

def _load_fallback_model() -> list | dict | None:
    cfg = _load_config()
    return cfg.get("fallback_providers") or cfg.get("fallback_model") or None

# in _create_agent()
return AIAgent(
    ...
    fallback_model=_load_fallback_model(),
    ...
)
RAW_BUFFERClick to expand / collapse

Bug

React/Ink TUI sessions do not receive the configured fallback_model / fallback_providers chain when tui_gateway.server._create_agent() constructs the foreground AIAgent. As a result, when the primary model fails (timeouts, connection errors, rate limits, etc.), the agent exhausts retries on the primary and returns API call failed after 3 retries instead of activating the configured fallback.

This is inconsistent with gateway/API sessions, which pass fallback_model=self._fallback_model into AIAgent and do activate the fallback chain.

Reproduction

  1. Configure a primary model plus fallback in ~/.hermes/config.yaml, for example:

    model:
      provider: openai-codex
      default: gpt-5.5
    
    fallback_model:
      provider: zenmux-anthropic
      model: anthropic/claude-sonnet-4.6

    (fallback_providers: list format should be affected the same way.)

  2. Start Hermes via the React/Ink TUI path (hermes --tui) so the session is served by tui_gateway.

  3. Make the primary provider fail repeatedly (observed with openai-codex/gpt-5.5 timeouts / connection errors).

  4. Observe that the turn fails after primary retries, with logs like:

    API call failed (attempt 3/3) ... provider=openai-codex ... model=gpt-5.5 summary=Request timed out.
    API call failed after 3 retries. Request timed out. | provider=openai-codex model=gpt-5.5
  5. No Fallback activated: gpt-5.5 → ... log appears for the TUI session.

Expected behavior

TUI sessions should respect the same global fallback configuration as CLI/gateway/API sessions. After retries are exhausted (or on eager-fallback errors), _try_activate_fallback() should see a non-empty _fallback_chain and switch to the configured fallback provider/model.

Actual behavior

The TUI-created agent has an empty fallback chain, so _try_activate_fallback() returns False immediately and the turn fails.

Root cause

tui_gateway/server.py::_create_agent() constructs AIAgent(...) without passing fallback_model:

return AIAgent(
    model=model,
    max_iterations=_cfg_max_turns(cfg, 90),
    provider=runtime.get("provider"),
    base_url=runtime.get("base_url"),
    api_key=runtime.get("api_key"),
    api_mode=runtime.get("api_mode"),
    ...
    session_db=_get_db(),
    ...
)

Because agent/agent_init.py only initializes _fallback_chain from the fallback_model constructor parameter, this path ends up with:

agent._fallback_chain = []

For comparison, gateway/run.py loads and passes the chain:

self._fallback_model = self._load_fallback_model()
...
AIAgent(..., fallback_model=self._fallback_model)

Suggested fix

Add a TUI equivalent of the gateway fallback loader and pass it into all relevant TUI-created AIAgent instances.

Minimal foreground-session fix:

def _load_fallback_model() -> list | dict | None:
    cfg = _load_config()
    return cfg.get("fallback_providers") or cfg.get("fallback_model") or None

# in _create_agent()
return AIAgent(
    ...
    fallback_model=_load_fallback_model(),
    ...
)

Also check _background_agent_kwargs() / background TUI runs: it currently forwards getattr(agent, "_fallback_model", None), which is legacy single-entry state. It may need to preserve the full fallback chain, e.g. getattr(agent, "_fallback_chain", None) or getattr(agent, "_fallback_model", None).

Related issues

This is the same class of bug as other entrypoints not propagating fallback config, but I could not find an existing TUI-specific issue:

  • #6673 — cron scheduler did not pass fallback_model to AIAgent
  • #18961 — --oneshot CLI mode did not propagate fallback_providers to AIAgent
  • #18452 — ACP adapter does not pass fallback_providers to AIAgent

Environment

  • Hermes Agent: current main/local install as of 2026-05-19
  • UI path: React/Ink TUI (tui_gateway.entry / tui_gateway.server)
  • Primary observed: openai-codex / gpt-5.5
  • Fallback observed/configured: custom Anthropic-compatible provider (zenmux-anthropic / anthropic/claude-sonnet-4.6)

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…

FAQ

Expected behavior

TUI sessions should respect the same global fallback configuration as CLI/gateway/API sessions. After retries are exhausted (or on eager-fallback errors), _try_activate_fallback() should see a non-empty _fallback_chain and switch to the configured fallback provider/model.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix [Bug] TUI sessions do not propagate fallback_model/fallback_providers to AIAgent [3 pull requests, 1 comments, 2 participants]