Version
Hermes Agent v0.14.0 (2026-05-16)
Summary
When the eager-fallback path is triggered on a rate-limit error (HTTP 429 / quota exhaustion), agent/conversation_loop.py calls _pool_may_recover_from_rate_limit(...) directly. That function lives in run_agent.py, but conversation_loop.py imports run_agent only via the lazy _ra() helper — there is no top-level binding for _pool_may_recover_from_rate_limit in the file's namespace. The call raises:
NameError: name '_pool_may_recover_from_rate_limit' is not defined
…and crashes run_conversation for the entire turn.
Reproduction
- Configure a
fallback_providers chain where at least one provider returns HTTP 429 (rate limit) or HTTP 402 (billing/quota), e.g.:
model:
default: deepseek-v4-flash-free
provider: opencode-zen
fallback_providers:
- provider: openrouter
model: deepseek/deepseek-v4-flash:free
- Drive enough traffic to trigger a 429 (or use a provider already at quota).
- Observe the error in
~/.hermes/logs/agent.log:
ERROR root: run_conversation raised: name '_pool_may_recover_from_rate_limit' is not defined
pool_may_recover = _pool_may_recover_from_rate_limit(
NameError: name '_pool_may_recover_from_rate_limit' is not defined
Root cause
Introduced by commit 053025238 "refactor(run_agent): extract run_conversation to agent/conversation_loop.py" (2026-05-16).
The refactor moved run_conversation to agent/conversation_loop.py and replaced direct run_agent.<helper> references with _ra().<helper> (lazy import). It missed one call site:
# agent/conversation_loop.py:2254
pool_may_recover = _pool_may_recover_from_rate_limit(
agent._credential_pool,
provider=agent.provider,
base_url=getattr(agent, "base_url", None),
)
The function definition lives in run_agent.py:239, so the unprefixed reference is unbound in conversation_loop.py's namespace.
Fix
Wrap the call with _ra():
pool_may_recover = _ra()._pool_may_recover_from_rate_limit(
agent._credential_pool,
provider=agent.provider,
base_url=getattr(agent, "base_url", None),
)
PR with the one-line fix follows.
Impact
Any user with a fallback_providers chain that has at least one rate-limited / quota-exhausted provider is affected. The eager-fallback path was the documented escape valve from 429s (commit 1fc77f995 "fall back on rate limit when pool has no rotation room"); right now it actually makes the situation worse — instead of switching to the next provider, the whole turn errors out.
Environment
- Hermes Agent v0.14.0 (2026.5.16)
- Python 3.11.14
- OpenAI SDK 2.24.0
- macOS
Version
Hermes Agent v0.14.0 (2026-05-16)
Summary
When the eager-fallback path is triggered on a rate-limit error (HTTP 429 / quota exhaustion),
agent/conversation_loop.pycalls_pool_may_recover_from_rate_limit(...)directly. That function lives inrun_agent.py, butconversation_loop.pyimportsrun_agentonly via the lazy_ra()helper — there is no top-level binding for_pool_may_recover_from_rate_limitin the file's namespace. The call raises:…and crashes
run_conversationfor the entire turn.Reproduction
fallback_providerschain where at least one provider returns HTTP 429 (rate limit) or HTTP 402 (billing/quota), e.g.:~/.hermes/logs/agent.log:Root cause
Introduced by commit
053025238"refactor(run_agent): extract run_conversation to agent/conversation_loop.py" (2026-05-16).The refactor moved
run_conversationtoagent/conversation_loop.pyand replaced directrun_agent.<helper>references with_ra().<helper>(lazy import). It missed one call site:The function definition lives in
run_agent.py:239, so the unprefixed reference is unbound inconversation_loop.py's namespace.Fix
Wrap the call with
_ra():PR with the one-line fix follows.
Impact
Any user with a
fallback_providerschain that has at least one rate-limited / quota-exhausted provider is affected. The eager-fallback path was the documented escape valve from 429s (commit1fc77f995"fall back on rate limit when pool has no rotation room"); right now it actually makes the situation worse — instead of switching to the next provider, the whole turn errors out.Environment