diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 92e65812fdf0..2b91b9e4d8e6 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -6175,6 +6175,12 @@ def _get_task_extra_body(task: str) -> Dict[str, Any]: chat.completions passes it through, the Codex Responses adapter maps it to top-level ``reasoning``/``include``, and the Anthropic auxiliary client maps it to ``build_anthropic_kwargs(reasoning_config=...)``. + + MoA tasks are excluded by design: reasoning depth for MoA is a per-slot + setting in the MoA preset (``moa.presets..reference_models[]. + reasoning_effort`` / ``aggregator.reasoning_effort``), not an + auxiliary-task knob — an ensemble-wide value would override the + per-slot ones. """ task_config = _get_auxiliary_task_config(task) raw = task_config.get("extra_body") @@ -6182,6 +6188,15 @@ def _get_task_extra_body(task: str) -> Dict[str, Any]: if "reasoning" not in result: effort = task_config.get("reasoning_effort") if effort is not None and effort != "": + if task in ("moa_reference", "moa_aggregator"): + logger.warning( + "auxiliary.%s.reasoning_effort is not supported — MoA " + "reasoning depth is per-slot: set reasoning_effort on the " + "preset's reference_models entries / aggregator instead " + "(moa.presets....). Ignoring.", + task, + ) + return result from hermes_constants import parse_reasoning_effort parsed = parse_reasoning_effort(effort) if parsed is not None: diff --git a/hermes_cli/config.py b/hermes_cli/config.py index fd25344bbb18..2e497b12d8e6 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -1756,7 +1756,10 @@ DEFAULT_CONFIG = { "api_key": "", "timeout": 900, "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) + # NOTE: no reasoning_effort here by design — MoA reasoning depth is + # configured PER SLOT in the MoA preset (moa.presets.. + # reference_models[].reasoning_effort / aggregator.reasoning_effort), + # not at the auxiliary-task level. }, "moa_aggregator": { "provider": "auto", @@ -1765,7 +1768,7 @@ DEFAULT_CONFIG = { "api_key": "", "timeout": 900, "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) + # NOTE: no reasoning_effort here by design — see moa_reference above. }, }, diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index 216a79fb3d48..531647eb511c 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -3331,6 +3331,28 @@ class TestAuxiliaryTaskExtraBody: with patch("hermes_cli.config.load_config", return_value=config): assert _get_task_extra_body("session_search") == {} + @pytest.mark.parametrize("moa_task", ["moa_reference", "moa_aggregator"]) + def test_moa_tasks_reject_task_level_reasoning_effort(self, moa_task, caplog): + """MoA reasoning is per-slot in the preset — the auxiliary-task + shorthand is ignored with a warning pointing at the preset config.""" + from agent.auxiliary_client import _get_task_extra_body + + config = {"auxiliary": {moa_task: {"reasoning_effort": "xhigh"}}} + with patch("hermes_cli.config.load_config", return_value=config), \ + caplog.at_level(logging.WARNING, logger="agent.auxiliary_client"): + result = _get_task_extra_body(moa_task) + + assert "reasoning" not in result + assert any("per-slot" in rec.message for rec in caplog.records) + + @pytest.mark.parametrize("moa_task", ["moa_reference", "moa_aggregator"]) + def test_moa_default_config_has_no_reasoning_effort(self, moa_task): + """Invariant: the shipped MoA auxiliary blocks must not grow a + reasoning_effort key — per-slot preset config is the only surface.""" + from hermes_cli.config import DEFAULT_CONFIG + + assert "reasoning_effort" not in DEFAULT_CONFIG["auxiliary"][moa_task] + def test_anthropic_aux_client_forwards_extra_body_reasoning(self): """_AnthropicCompletionsAdapter passes extra_body.reasoning into build_anthropic_kwargs as reasoning_config.""" diff --git a/website/docs/user-guide/configuration.md b/website/docs/user-guide/configuration.md index c4f9e8481579..be9b3a3f3284 100644 --- a/website/docs/user-guide/configuration.md +++ b/website/docs/user-guide/configuration.md @@ -980,7 +980,9 @@ Auxiliary task blocks additionally accept a `reasoning_effort` knob: |-----|-------------|---------| | `reasoning_effort` | Thinking level for that task's LLM calls: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra` | not set (provider default) | -This is the per-task counterpart of the global `agent.reasoning_effort`: run compression at `low` or vision at `none` to cut side-task latency and cost when your main model is an expensive reasoning model, without touching your main chat behavior. It works on every auxiliary task block (`vision`, `web_extract`, `compression`, `title_generation`, `curator`, `background_review`, `moa_reference`, `moa_aggregator`, ...), across all three auxiliary wire formats (chat completions, Codex Responses, Anthropic Messages). An explicit `extra_body.reasoning` on the same task wins over the shorthand. +This is the per-task counterpart of the global `agent.reasoning_effort`: run compression at `low` or vision at `none` to cut side-task latency and cost when your main model is an expensive reasoning model, without touching your main chat behavior. It works on every auxiliary task block (`vision`, `web_extract`, `compression`, `title_generation`, `curator`, `background_review`, ...), across all three auxiliary wire formats (chat completions, Codex Responses, Anthropic Messages). An explicit `extra_body.reasoning` on the same task wins over the shorthand. + +MoA is the one exception: reasoning depth for Mixture-of-Agents is configured **per slot** in the MoA preset (`moa.presets..reference_models[].reasoning_effort` / `aggregator.reasoning_effort`), not on the `moa_reference`/`moa_aggregator` auxiliary blocks — see [Mixture of Agents](/user-guide/features/mixture-of-agents). ```yaml auxiliary: