mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
feat(delegation): add configurable reasoning_effort for subagents
Add delegation.reasoning_effort config key so subagents can run at a different thinking level than the parent agent. When set, overrides the parent's reasoning_config; when empty, inherits as before. Valid values: xhigh, high, medium, low, minimal, none (disables thinking). Config path: delegation.reasoning_effort in config.yaml Files changed: - tools/delegate_tool.py: resolve override in _build_child_agent - hermes_cli/config.py: add reasoning_effort to DEFAULT_CONFIG - tests/tools/test_delegate.py: 4 new tests covering all cases
This commit is contained in:
committed by
Teknium
parent
be9198f1e1
commit
718e8ad6fa
@@ -538,6 +538,8 @@ DEFAULT_CONFIG = {
|
||||
"api_key": "", # API key for delegation.base_url (falls back to OPENAI_API_KEY)
|
||||
"max_iterations": 50, # per-subagent iteration cap (each subagent gets its own budget,
|
||||
# independent of the parent's max_iterations)
|
||||
"reasoning_effort": "", # reasoning effort for subagents: "xhigh", "high", "medium",
|
||||
# "low", "minimal", "none" (empty = inherit parent's level)
|
||||
},
|
||||
|
||||
# Ephemeral prefill messages file — JSON list of {role, content} dicts
|
||||
|
||||
@@ -1210,5 +1210,73 @@ class TestDelegateHeartbeat(unittest.TestCase):
|
||||
f"Heartbeat should include last_activity_desc: {touch_calls}")
|
||||
|
||||
|
||||
class TestDelegationReasoningEffort(unittest.TestCase):
|
||||
"""Tests for delegation.reasoning_effort config override."""
|
||||
|
||||
@patch("tools.delegate_tool._load_config")
|
||||
@patch("run_agent.AIAgent")
|
||||
def test_inherits_parent_reasoning_when_no_override(self, MockAgent, mock_cfg):
|
||||
"""With no delegation.reasoning_effort, child inherits parent's config."""
|
||||
mock_cfg.return_value = {"max_iterations": 50, "reasoning_effort": ""}
|
||||
MockAgent.return_value = MagicMock()
|
||||
parent = _make_mock_parent()
|
||||
parent.reasoning_config = {"enabled": True, "effort": "xhigh"}
|
||||
|
||||
_build_child_agent(
|
||||
task_index=0, goal="test", context=None, toolsets=None,
|
||||
model=None, max_iterations=50, parent_agent=parent,
|
||||
)
|
||||
call_kwargs = MockAgent.call_args[1]
|
||||
self.assertEqual(call_kwargs["reasoning_config"], {"enabled": True, "effort": "xhigh"})
|
||||
|
||||
@patch("tools.delegate_tool._load_config")
|
||||
@patch("run_agent.AIAgent")
|
||||
def test_override_reasoning_effort_from_config(self, MockAgent, mock_cfg):
|
||||
"""delegation.reasoning_effort overrides the parent's level."""
|
||||
mock_cfg.return_value = {"max_iterations": 50, "reasoning_effort": "low"}
|
||||
MockAgent.return_value = MagicMock()
|
||||
parent = _make_mock_parent()
|
||||
parent.reasoning_config = {"enabled": True, "effort": "xhigh"}
|
||||
|
||||
_build_child_agent(
|
||||
task_index=0, goal="test", context=None, toolsets=None,
|
||||
model=None, max_iterations=50, parent_agent=parent,
|
||||
)
|
||||
call_kwargs = MockAgent.call_args[1]
|
||||
self.assertEqual(call_kwargs["reasoning_config"], {"enabled": True, "effort": "low"})
|
||||
|
||||
@patch("tools.delegate_tool._load_config")
|
||||
@patch("run_agent.AIAgent")
|
||||
def test_override_reasoning_effort_none_disables(self, MockAgent, mock_cfg):
|
||||
"""delegation.reasoning_effort: 'none' disables thinking for subagents."""
|
||||
mock_cfg.return_value = {"max_iterations": 50, "reasoning_effort": "none"}
|
||||
MockAgent.return_value = MagicMock()
|
||||
parent = _make_mock_parent()
|
||||
parent.reasoning_config = {"enabled": True, "effort": "high"}
|
||||
|
||||
_build_child_agent(
|
||||
task_index=0, goal="test", context=None, toolsets=None,
|
||||
model=None, max_iterations=50, parent_agent=parent,
|
||||
)
|
||||
call_kwargs = MockAgent.call_args[1]
|
||||
self.assertEqual(call_kwargs["reasoning_config"], {"enabled": False})
|
||||
|
||||
@patch("tools.delegate_tool._load_config")
|
||||
@patch("run_agent.AIAgent")
|
||||
def test_invalid_reasoning_effort_falls_back_to_parent(self, MockAgent, mock_cfg):
|
||||
"""Invalid delegation.reasoning_effort falls back to parent's config."""
|
||||
mock_cfg.return_value = {"max_iterations": 50, "reasoning_effort": "banana"}
|
||||
MockAgent.return_value = MagicMock()
|
||||
parent = _make_mock_parent()
|
||||
parent.reasoning_config = {"enabled": True, "effort": "medium"}
|
||||
|
||||
_build_child_agent(
|
||||
task_index=0, goal="test", context=None, toolsets=None,
|
||||
model=None, max_iterations=50, parent_agent=parent,
|
||||
)
|
||||
call_kwargs = MockAgent.call_args[1]
|
||||
self.assertEqual(call_kwargs["reasoning_config"], {"enabled": True, "effort": "medium"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -312,6 +312,25 @@ def _build_child_agent(
|
||||
effective_acp_command = override_acp_command or getattr(parent_agent, "acp_command", None)
|
||||
effective_acp_args = list(override_acp_args if override_acp_args is not None else (getattr(parent_agent, "acp_args", []) or []))
|
||||
|
||||
# Resolve reasoning config: delegation override > parent inherit
|
||||
parent_reasoning = getattr(parent_agent, "reasoning_config", None)
|
||||
child_reasoning = parent_reasoning
|
||||
try:
|
||||
delegation_cfg = _load_config()
|
||||
delegation_effort = str(delegation_cfg.get("reasoning_effort") or "").strip()
|
||||
if delegation_effort:
|
||||
from hermes_constants import parse_reasoning_effort
|
||||
parsed = parse_reasoning_effort(delegation_effort)
|
||||
if parsed is not None:
|
||||
child_reasoning = parsed
|
||||
else:
|
||||
logger.warning(
|
||||
"Unknown delegation.reasoning_effort '%s', inheriting parent level",
|
||||
delegation_effort,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.debug("Could not load delegation reasoning_effort: %s", exc)
|
||||
|
||||
child = AIAgent(
|
||||
base_url=effective_base_url,
|
||||
api_key=effective_api_key,
|
||||
@@ -322,7 +341,7 @@ def _build_child_agent(
|
||||
acp_args=effective_acp_args,
|
||||
max_iterations=max_iterations,
|
||||
max_tokens=getattr(parent_agent, "max_tokens", None),
|
||||
reasoning_config=getattr(parent_agent, "reasoning_config", None),
|
||||
reasoning_config=child_reasoning,
|
||||
prefill_messages=getattr(parent_agent, "prefill_messages", None),
|
||||
enabled_toolsets=child_toolsets,
|
||||
quiet_mode=True,
|
||||
|
||||
Reference in New Issue
Block a user