New request
#3405: Bug: Profile provider/model not respected in WebUI — session routes to global default provider instead of profile's configured provider
We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.
Summary
When a chat is started in the WebUI with a profile selected, the session's provider is not resolved from that profile. If the chosen model string is not @provider:-qualified and no explicit provider is sent, the backend falls back to the global active provider from the model catalog. The result: a profile bound to one provider/key silently runs on a different provider.
This is the provider/model analogue of #3294 ("Profile Tool Configuration Not Respected in Web UI"). Same class of bug — profile selection is not authoritative; unset/implicit fields inherit the global default instead of the profile config.
Impact (why this is serious)
- Wrong credentials/billing: a profile wired to an Anthropic API key runs against GitHub Copilot instead.
- Wrong context window → silent truncation: the global default model entry can carry a much larger advertised
context_lengththan the provider actually serves. The agent never triggers its own compression, the provider silently drops the oldest messages, and long single-subject chats "forget" earlier content with no warning. (This is how I noticed — the model pill happened to display Opus, but logs showed Copilot.) - Invisible: the composer shows the profile name and a model name but never the provider, so there is no UI signal that routing diverged from the profile.
Evidence
WebUI session whose selected profile (personal) is configured with provider: anthropic and its own ANTHROPIC_API_KEY. Actual agent run (from ~/.hermes/logs/agent.log):
conversation turn: session=<id> model=claude-opus-4.8 provider=copilot platform=api_server
OpenAI client created ... provider=copilot base_url=https://api.githubcopilot.com model=claude-opus-4.8
API call #1: model=claude-opus-4.8 provider=copilot in=62058 ...
Profile config (~/.hermes/profiles/personal/config.yaml): model.provider: anthropic. Expected provider=anthropic; observed provider=copilot.
Root cause (located)
api/routes.py, _resolve_session_model_and_provider (approx lines 1802–1842).
When model is present but not @provider:-qualified and requested_provider is empty, the function resolves against the catalog's active_provider (the global active provider) rather than the selected profile's configured provider. The profile's provider binding never enters this path. So a bare model id like claude-opus-4.8 inherits whatever the global default provider is (Copilot), regardless of the profile the user picked.
Steps to reproduce
- Create profile
personalwithmodel.provider: anthropic,model.default: claude-opus-4.8, Anthropic key in its.env. - Set the global default provider to something else (e.g.
copilot) with an Opus model entry. - In the WebUI composer, select the
personalprofile and pick "Claude Opus 4.8" (bare, no@provider:qualifier). - Send a message.
- Inspect
~/.hermes/logs/agent.log: the run showsprovider=copilot, notanthropic.
Expected
When a profile is selected and no explicit per-chat provider override is given, the session must resolve the provider from the profile's config. The profile's provider should win over the global catalog active_provider.
Suggested fix direction
In the start-chat path, thread the selected profile's configured provider into provider resolution and have it take precedence over the global active_provider fallback when the model is not @provider:-qualified and no explicit override is supplied. (Mirrors the fix shape requested in #3294 for tools/skills.)
Environment
- Hermes WebUI on
master(around the post-#3251 release merge). - Provider in question: GitHub Copilot (advertises a 1,000,000-token context that the endpoint does not actually serve), vs intended Anthropic.
Related: #3294 (same "profile not respected" class), #3368 / #3360 (model-picker substring matching — adjacent, not the same).
Profile provider resolution falls back to global defaults instead of the profile-bound configuration in session routes.
- api/routes.py
- api/profiles.py
- static/js/composer.js
- tests/test_profiles.py