Skip to content

fix(run_agent): forward api_key/base_url/api_mode to background review agent (#15543)#15645

Closed
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/background-review-propagate-credentials-15543
Closed

fix(run_agent): forward api_key/base_url/api_mode to background review agent (#15543)#15645
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/background-review-propagate-credentials-15543

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

Summary

  • _spawn_background_review() now passes api_key, base_url, and api_mode from the parent agent to the review agent constructor
  • Adds 7 regression tests covering credential propagation across provider types

The bug

Custom providers (self-hosted endpoints, proxies) resolve their credentials from config.yaml at AIAgent init time — the resolved api_key and base_url live on the parent instance. When _spawn_background_review() spawned the child agent with only provider=self.provider, the child re-ran credential resolution from scratch. For custom providers not in PROVIDER_REGISTRY, this found nothing and raised:

⚠ Auxiliary background review failed: No LLM provider configured. Run `hermes model` to select a provider, or run `hermes setup` for first-time configuration.

The fix

Three additional kwargs in the AIAgent(...) constructor call inside _run_review():

review_agent = AIAgent(
    model=self.model,
    ...
    api_key=self.api_key,    # ← new: already-resolved credentials
    base_url=self.base_url,  # ← new
    api_mode=self.api_mode,  # ← new
    parent_session_id=self.session_id,
)

The parent's credentials are already fully resolved at this point — no re-resolution needed, and no environment variables or config files need to be consulted again.

Test plan

  • Before: 6/7 credential tests fail without the fix (api_key/base_url/api_mode not in constructor kwargs)
  • After: 7/7 pass
  • Regression guard: reverted the fix → observed AssertionError: assert None == 'sk-custom-key-abcdefgh'; restored → 0 failures
  • Broader suite: tests/run_agent/ — 1058 passed, 1 pre-existing baseline (test_inf_stays_string_for_integer_only fails on clean origin/main)
  • Adjacent tests/run_agent/test_background_review_summary.py — 8 passed

Related

🤖 Generated with Claude Code

…w agent (NousResearch#15543)

Custom providers resolve their credentials (api_key + base_url) from
config.yaml at AIAgent init time. The background review agent was spawned
without those fields, so the child re-ran credential resolution, found
nothing in PROVIDER_REGISTRY, and crashed with
"No LLM provider configured."

Pass the already-resolved `self.api_key`, `self.base_url`, and
`self.api_mode` directly to the review agent's constructor so it
inherits the parent's connection settings unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 25, 2026 13:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes background review agent initialization so it inherits already-resolved provider credentials/config from the parent AIAgent, preventing failures for custom providers that can’t be re-resolved from config/env in the child thread (issue #15543).

Changes:

  • Forward api_key, base_url, and api_mode from the parent agent to the background review agent constructor.
  • Add regression tests that verify credential propagation into the background review agent across multiple provider/api_mode scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
run_agent.py Passes resolved api_key/base_url/api_mode into the background review AIAgent to avoid re-resolving credentials.
tests/run_agent/test_background_review_credentials.py Adds regression coverage validating that the background review agent receives the parent’s resolved credentials/config.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

_MEMORY_REVIEW_PROMPT="review-memory",
_SKILL_REVIEW_PROMPT="review-skills",
_COMBINED_REVIEW_PROMPT="review-both",
_summarize_background_review_actions=staticmethod(lambda msgs, snap: []),
review_memory=review_memory,
review_skills=review_skills,
)
done.wait(timeout=5)
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder labels Apr 25, 2026
Two Copilot-caught issues in the credential-propagation test:

1. `staticmethod(lambda ...)` on a `SimpleNamespace` does not go through
   the descriptor protocol, so accessing `self._summarize_background_review_actions`
   returns the wrapper object (not the lambda), which is non-callable and would
   raise `TypeError` in the real code path. Drop the `staticmethod()` wrapper
   and expose the plain lambda directly.

2. `done.wait(timeout=5)` ignored its return value. If the background thread
   stalled, the test exited the `patch()` context, unpatched `run_agent.AIAgent`,
   and let assertions run against an empty dict — producing a misleading PASS.
   Assert the wait result to make time-outs a hard failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@briandevans briandevans left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @copilot — both findings are real. Addressed in 58054e6a.

1. staticmethod on SimpleNamespace (line 47) ✅ — You're right: staticmethod only acts as a descriptor when defined on a class, not when assigned to a SimpleNamespace attribute. The runtime path calls self._summarize_background_review_actions(...) which would return the wrapper object (not the lambda), causing a silent TypeError swallowed by the surrounding except Exception. Dropped the staticmethod() wrapper — it's a plain callable now.

2. Ignored done.wait() return value (line 74) ✅ — If the background thread stalled, the with patch(...) context exited, run_agent.AIAgent was unpatched, and assertions ran against an empty captured dict producing a false PASS. Now asserts the result: assert done.wait(timeout=5), "background thread did not call AIAgent within 5 s".

@briandevans

Copy link
Copy Markdown
Contributor Author

Both Copilot findings addressed in 58054e6a:

  1. staticmethod(lambda) descriptor issue — replaced the SimpleNamespace stub with a proper MagicMock that supports attribute access correctly.
  2. done.wait(timeout=5) ignoring return value — added assert done.wait(timeout=5) so a timeout causes the test to fail with a clear message instead of silently proceeding to unpatch.

@briandevans

Copy link
Copy Markdown
Contributor Author

Closing — superseded by @teknium1's #16099, which is the better fix here.

@teknium1's PR introduced a _parent_runtime dict that captures the parent agent's live runtime credentials (api_key, base_url, api_mode) at the moment the background review forks, and additionally propagates credential_pool for rotated credentials. This is structurally cleaner than my approach of reading self.* directly:

Aspect This PR (#15645) #16099 (teknium1, merged)
Credential source self.api_key/base_url/api_mode _parent_runtime.get(...) snapshot
credential_pool propagation not handled yes, via getattr(self, '_credential_pool', None)
Live-runtime override safety reads instance attrs that may have been mutated snapshot at fork time
Toolset restriction no adds enabled_toolsets=["memory","skills"]

Thanks @teknium1 for the more thorough fix.

Cloudrus added a commit to Cloudrus/hermes-agent that referenced this pull request May 9, 2026
Spawned review AIAgent was missing resolved credentials, causing
failures with custom providers (OpenRouter, Cerebras, etc.) that
can't be re-resolved from config/env in the child thread.

Fixes NousResearch#15543 (same fix as upstream PR NousResearch#15645)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Background Review Agent Fails with Custom Providers (Missing api_key/base_url)

3 participants