# NousResearch/hermes-agent · Auxiliary LLM provider fallback and warning fixes

## Open now

- Nothing open on GitHub.

These issues and PRs address bugs around missing auxiliary LLM provider configuration, improving warning messages and adding fallback behavior to OPENROUTER_API_KEY when the credential pool is missing or exhausted.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #53906. Verify it addresses your case.
- **Recommended:** read PR #53906 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #10149: \[Bug\]: No auxiliary LLM provider configured

## Prior & competing PRs

- **#53906: fix(auxiliary): fall back to OPENROUTER_API_KEY when credential pool exhausted**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/auxiliary_client.py`, `tests/agent/test_auxiliary_client.py`
  - fix hunks:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -1669,13 +1669,14 @@ def _try_openrouter(explicit_api_key: str = None, model: str = None) -> Tuple[Op
         pool_present, entry = _select_pool_entry("openrouter")
         if pool_present:
             or_key = explicit_api_key or _pool_runtime_api_key(entry)
    -        if not or_key:
    -            _mark_provider_unhealthy("openrouter", ttl=60)
    -            return None, None
    -        base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL
    -        logger.debug("Auxiliary client: OpenRouter via pool")
    -        return OpenAI(api_key=[redacted:secret-assignment] base_url=base_url,
    -                       default_headers=build_or_headers()), model or _OPENROUTER_MODEL
    +        if or_key:
    +            base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL
    +            logger.debug("Auxiliary client: OpenRouter via pool")
    +            return OpenAI(api_key=[redacted:secret-assignment] base_url=base_url,
    +                           default_headers=build_or_headers()), model or _OPENROUTER_MODEL
    +        # Pool exists but is exhausted (no usable runtime key) — fall through to
    +        # the OPENROUTER_API_KEY env-var path rather than failing outright.
    +        logger.debug("Auxiliary client: OpenRouter pool exhausted, trying OPENROUTER_API_KEY")
     
         or_key = explicit_api_key or os.getenv("OPENROUTER_API_KEY")
         if not or_key:
    ```
  - `tests/agent/test_auxiliary_client.py`

    ```diff
    @@ -27,6 +27,9 @@
         _refresh_nous_recommended_model,
         _normalize_aux_provider,
         _try_payment_fallback,
    +    _try_openrouter,
    +    _OPENROUTER_MODEL,
    +    OPENROUTER_BASE_URL,
         _resolve_auto,
         _resolve_xai_oauth_for_aux,
         _CodexCompletionsAdapter,
    ```

    ```diff
    @@ -919,6 +922,35 @@ def test_explicit_openrouter_missing_env_keeps_not_set_warning(self, monkeypatch
                 for record in caplog.records
             )
     
    +    def test_try_openrouter_pool_exhausted_falls_back_to_env(self, monkeypatch):
    +        """Pool present but exhausted → fall through to OPENROUTER_API_KEY env var."""
    +        monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-env-fallback")
    +        with patch("agent.auxiliary_client._select_pool_entry", return_value=(True, None)), \
    +             patch("agent.auxiliary_client.OpenAI") as mock_openai:
    +            mock_client = MagicMock(name="openrouter_client")
    +            mock_openai.return_value = mock_client
    +
    +            client, model = _try_openrouter()
    +
    +        assert client is mock_client
    +        assert model == _OPENROUTER_MODEL
    +        mock_openai.assert_called_once()
    +        assert mock_openai.call_args.kwargs["api_key"] == "sk-or-env-fallback"
    +        assert mock_openai.call_args.kwargs["base_url"] == OPENROUTER_BASE_URL
    +
    +    def test_try_openrouter_pool_exhausted_no_env_marks_unhealthy(self, monkeypatch):
    +        """Pool exhausted AND no env var → final failure marks provider unhealthy."""
    +        monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
    +        with patch("agent.auxiliary_client._select_pool_entry", return_value=(True, None)), \
    +             patch("agent.auxiliary_client._mark_provider_unhealthy") as mock_mark, \
    +             patch("agent.auxiliary_client.OpenAI") as mock_openai:
    +            client, model = _try_openrouter()
    +
    +        assert client is None
    +        assert model is None
    +        mock_openai.assert_not_called()
    +        mock_mark.assert_called_once_with("openrouter", ttl=60)
    +
     class TestGetTextAuxiliaryClient:
         """Test the full resolution chain for get_text_auxiliary_client."""
     
    ```
- **#10655: fix(auxiliary): fall back to OPENROUTER_API_KEY when pool entry is missing**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `c56b39c11e814ee571e391fc46384b0e7888a490`, `#53906`, `#23495`
- **#36940: fix(auxiliary): improve "no auxiliary LLM provider" warning messages (#10149)**. closed without merge
  - maintainer direction: category `silent_close`; implication `active`

## Fix status

- Merged to main in PR #53906 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-28`.
- Subsystem: `agent`
