Skip to content

fix: honour HERMES_LLM_BASE_URL in runtime_provider.py api_key resolution path - #20

Merged
OmarB97 merged 2 commits into
mainfrom
fix/hermes-llm-base-url-env-override
May 28, 2026
Merged

fix: honour HERMES_LLM_BASE_URL in runtime_provider.py api_key resolution path#20
OmarB97 merged 2 commits into
mainfrom
fix/hermes-llm-base-url-env-override

Conversation

@OmarB97

@OmarB97 OmarB97 commented May 28, 2026

Copy link
Copy Markdown
Owner

What changed

Adds a HERMES_LLM_BASE_URL env var fallback in hermes_cli/runtime_provider.py at the env_url resolution step (line 1155). When the provider-specific env var (e.g. OPENCODE_ZEN_BASE_URL) is unset, the code now reads the generic HERMES_LLM_BASE_URL before falling back to the hardcoded pconfig.inference_base_url.

Why

MeshBoard's stream-tap launcher creates a per-dispatch sandbox whose .env sets HERMES_LLM_BASE_URL to a local loopback proxy URL (e.g. http://127.0.0.1:55176). Hermes was ignoring it because:

  1. The launcher sets HERMES_LLM_BASE_URL in the subprocess env
  2. The sandbox .env also sets it (dotenv loads with override=True)
  3. But resolve_runtime_provider() never reads HERMES_LLM_BASE_URL — it only checks pconfig.base_url_env_var (provider-specific) and then falls back to the hardcoded default URL

This caused the stream-tap proxy to receive zero traffic, leaving the dashboard stream rows completely silent during active dispatches.

The earlier fix (commit a73ee55c2) added the fallback to hermes_cli/auth.py:resolve_api_key_provider_credentials() — but the caller at line 1163 already set base_url = env_url or pconfig.inference_base_url BEFORE creds were consulted. The creds base_url was never used for non-kimi providers because line 1169's guard if not base_url: was already False.

How to Review

  1. Verify the fallback is scoped correctly: only fires for api_key auth_type providers
  2. Verify explicit_base_url (user-passed) still wins over the env var
  3. Verify the existing kim-coding branch is unaffected (it takes a different path)
  4. Run: cd tests && python3 -m pytest hermes_cli/test_runtime_provider*.py -x --tb=short

Evidence

  • Manual test: HERMES_LLM_BASE_URL=http://127.0.0.1:9999 python3 -c "from hermes_cli.runtime_provider import resolve_runtime_provider; r = resolve_runtime_provider(requested='opencode-zen'); print(r['base_url'])" should print http://127.0.0.1:9999 instead of the default opencode-zen URL.
HERMES_LLM_BASE_URL=http://127.0.0.1:9999 python3 -c "
import os, sys; sys.path.insert(0, '.')
from hermes_cli.runtime_provider import resolve_runtime_provider
r = resolve_runtime_provider(requested='opencode-zen')
print('base_url:', r['base_url'])
"
# Without the env var, default URL should be used
python3 -c "
import os, sys; sys.path.insert(0, '.')
from hermes_cli.runtime_provider import resolve_runtime_provider
r = resolve_runtime_provider(requested='opencode-zen')
print('base_url:', r['base_url'])
"

Verification

python3 -m pytest tests/hermes_cli/test_runtime_provider_resolution.py -x --tb=short 2>&1 | tail -5

Pass expected: existing resolve-runtime tests unaffected (env var only activates when provider-specific var is absent).

Risks / Gaps

Collaborators

Participants: ko-mac.hermes (launcher), MeshBoard stream-tap worker
Process: MeshBoard autopilot dispatch → stream-tap proxy → Hermes subprocess
Task context: stream-tap-empty-jsonl-diagnostic (resolved root cause: Hermes bypassed tap due to missing env var consumption)
Related work: hermes-agent PR #33986 (HERMES_SKIP_PROFILE_OVERRIDE), fork main commit a73ee55 (auth.py fallback)

The kimi worker added the fallback to hermes_cli/auth.py:resolve_api_key_provider_credentials()
but the caller at runtime_provider.py:1163 already set base_url = pconfig.inference_base_url
before creds were consulted — so the creds base_url was never used for non-kimi providers.

Add the same HERMES_LLM_BASE_URL fallback at runtime_provider.py:1154 so
env_url carries the proxy URL through to line 1163's base_url resolution.

Refs meshboard task: stream-tap-runtime-provider-env-override
@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown

🔎 Lint report: fix/hermes-llm-base-url-env-override 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: 9544 on HEAD, 9544 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5030 pre-existing issues carried over.

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

…tion

The previous fix (auth.py + runtime_provider.py _resolve_explicit_runtime path)
missed the pool-based code path in _resolve_runtime_from_pool_entry().  For
providers like opencode-zen that resolve via the credential pool, the base_url
was set from the pool entry's default without checking HERMES_LLM_BASE_URL.

Add the override at line 306 (right after base_url is read from the pool entry)
so the MeshBoard stream-tap proxy URL is honoured regardless of which resolution
path is taken: explicit, named-custom, or pool.

This is the actual code path exercised by MeshBoard dispatches, which use
resolve_runtime_provider(requested='opencode-zen') → pool → _resolve_runtime_from_pool_entry.

Refs meshboard task: stream-tap-runtime-provider-env-override
@OmarB97
OmarB97 merged this pull request into main May 28, 2026
16 of 20 checks passed
OmarB97 added a commit that referenced this pull request Jun 10, 2026
…lice 4.0, hermes side) (#162)

The hermes half of cloud channels slice 4.0 (cloud half = meshboard-cloud #20,
deployed + live-verified today): promote a session to a cloud channel and tail
its local message log into it, so the owner's other devices can read the
conversation even when this gateway is offline.

- tui_gateway/cloud_channels.py: stdlib-only client (urllib) + one daemon
  pusher thread per shared session. Reads rows past an id watermark via its
  own read-only sqlite connection (no SessionDB coupling), maps them with the
  pure rows_to_batch (sender_device attribution preserved; user rows fall back
  to this device's name), pushes batches; cloud-side dedupe makes the
  watermark purely an optimisation, so crash/replay is safe. Cloud being down
  never affects the local session (errors log once per change and back off).
- server.py: session.cloud_share / cloud_status / cloud_unshare RPCs.
  Resolve stored-or-runtime ids and do NOT require a live session (the pusher
  reads sqlite). Hard zero-dep gate: refused with a clear message unless the
  operator set HERMES_CLOUD_TOKEN — the core never dials the cloud on its own.
- Desktop: "Share to cloud" in the session actions menu (en/zh/ja/zh-hant),
  self-contained like Export; all outcomes are plain-English toasts, including
  the not-configured case.

Tests: 6 (row→batch mapping incl. attribution fallback + role filtering;
pusher watermark/tail/no-op/error semantics against a real sqlite file with
the network monkeypatched; the opt-in gate). tui_gateway suite 125 passed;
tsc/eslint/ruff clean.

Co-authored-by: Omar Baradei <omar@kostudios.io>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant