mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-07 02:51:50 +00:00
fix(delegate): inherit parent fallback_chain in _build_child_agent
_build_child_agent constructed child AIAgents without passing fallback_model, leaving _fallback_chain=[] for every subagent. When a subagent hit a rate-limit or credential exhaustion the runtime fallback check (run_agent.py:7486 / 12267) found an empty chain and failed immediately — even though the parent agent was configured with fallback_providers and would have recovered. The cron scheduler already propagates fallback_model correctly (scheduler.py:1038). Fix closes the parity gap by reading the parent's _fallback_chain (the normalised list form accepted by AIAgent's fallback_model parameter) and threading it through. Empty chains coerce to None so AIAgent initialises _fallback_chain=[] as usual rather than iterating an empty list.
This commit is contained in:
parent
cb33c73418
commit
9faaa292b4
2 changed files with 54 additions and 0 deletions
|
|
@ -2403,5 +2403,52 @@ class TestSubagentApprovalCallback(unittest.TestCase):
|
|||
self.assertIsNone(_get_approval_callback())
|
||||
|
||||
|
||||
class TestFallbackModelInheritance(unittest.TestCase):
|
||||
"""Subagents must inherit the parent's fallback provider chain."""
|
||||
|
||||
def test_child_inherits_fallback_chain(self):
|
||||
"""_build_child_agent passes parent._fallback_chain as fallback_model."""
|
||||
parent = _make_mock_parent(depth=0)
|
||||
fallback_entry = {"provider": "openrouter", "model": "gpt-4o-mini", "api_key": "sk-or-x"}
|
||||
parent._fallback_chain = [fallback_entry]
|
||||
|
||||
with patch("run_agent.AIAgent") as MockAgent:
|
||||
MockAgent.return_value = MagicMock()
|
||||
_build_child_agent(
|
||||
task_index=0,
|
||||
goal="test fallback inheritance",
|
||||
context=None,
|
||||
toolsets=None,
|
||||
model=None,
|
||||
max_iterations=10,
|
||||
parent_agent=parent,
|
||||
task_count=1,
|
||||
)
|
||||
|
||||
_, kwargs = MockAgent.call_args
|
||||
self.assertEqual(kwargs["fallback_model"], [fallback_entry])
|
||||
|
||||
def test_child_gets_no_fallback_when_parent_chain_empty(self):
|
||||
"""When parent._fallback_chain is empty, fallback_model is None."""
|
||||
parent = _make_mock_parent(depth=0)
|
||||
parent._fallback_chain = []
|
||||
|
||||
with patch("run_agent.AIAgent") as MockAgent:
|
||||
MockAgent.return_value = MagicMock()
|
||||
_build_child_agent(
|
||||
task_index=0,
|
||||
goal="test no fallback",
|
||||
context=None,
|
||||
toolsets=None,
|
||||
model=None,
|
||||
max_iterations=10,
|
||||
parent_agent=parent,
|
||||
task_count=1,
|
||||
)
|
||||
|
||||
_, kwargs = MockAgent.call_args
|
||||
self.assertIsNone(kwargs["fallback_model"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -1026,6 +1026,12 @@ def _build_child_agent(
|
|||
except Exception as exc:
|
||||
logger.debug("Could not load delegation reasoning_effort: %s", exc)
|
||||
|
||||
# Inherit the parent's fallback provider chain so subagents can recover
|
||||
# from rate-limits and credential exhaustion exactly like the top-level
|
||||
# agent does. _fallback_chain is a list accepted by AIAgent's
|
||||
# fallback_model parameter (which handles both list and dict forms).
|
||||
parent_fallback = getattr(parent_agent, "_fallback_chain", None) or None
|
||||
|
||||
child = AIAgent(
|
||||
base_url=effective_base_url,
|
||||
api_key=effective_api_key,
|
||||
|
|
@ -1038,6 +1044,7 @@ def _build_child_agent(
|
|||
max_tokens=getattr(parent_agent, "max_tokens", None),
|
||||
reasoning_config=child_reasoning,
|
||||
prefill_messages=getattr(parent_agent, "prefill_messages", None),
|
||||
fallback_model=parent_fallback,
|
||||
enabled_toolsets=child_toolsets,
|
||||
quiet_mode=True,
|
||||
ephemeral_system_prompt=child_prompt,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue