# NousResearch/hermes-agent · MoA per-slot reasoning effort configuration support

## Open now

- Nothing open on GitHub.

These issues and pull requests all concern adding support for configuring reasoning_effort on a per-slot basis within MoA (Mixture of Agents) reference advisors and presets. The work spans feature requests and implementation across slots, presets, and reference configurations.

## TL;DR

- 5 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #64631. Verify it addresses your case.
- **Recommended:** read PR #64631 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-64631`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.34`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #53932: feat(moa): allow reference slots to set reasoning_effort
- #58195: \[Feature\]: Per-slot reasoning_effort for MoA reference advisors

## Prior & competing PRs

- **#64631: feat(moa): per-slot reasoning_effort in MoA presets**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `agent/auxiliary_client.py`, `agent/moa_loop.py`, `hermes_cli/config.py`, `hermes_cli/moa_cmd.py`, `hermes_cli/moa_config.py`, `scripts/release.py`, `tests/agent/test_auxiliary_client.py`, `tests/agent/test_moa_reasoning_effort.py`, `tests/hermes_cli/test_moa_config.py`, `website/docs/user-guide/configuration.md`, `website/docs/user-guide/features/mixture-of-agents.md`
  - fix hunks:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -1286,6 +1286,7 @@ def create(self, **kwargs) -> Any:
             model = kwargs.get("model", self._model)
             tools = kwargs.get("tools")
             tool_choice = kwargs.get("tool_choice")
    +        reasoning_config = kwargs.get("_reasoning_config")
             # ZAI's Anthropic-compatible endpoint rejects max_tokens on vision
             # models (glm-4v-flash etc.) with error code 1210.  When the caller
             # signals this by setting _skip_zai_max_tokens in kwargs, omit it.
    ```

    ```diff
    @@ -1306,17 +1307,18 @@ def create(self, **kwargs) -> Any:
                 elif choice_type in {"auto", "required", "none"}:
                     normalized_tool_choice = choice_type
     
    -        # Honor extra_body.reasoning (chat.completions shape) so auxiliary
    -        # tasks configured with auxiliary.<task>.reasoning_effort (or an
    -        # explicit extra_body.reasoning) control Anthropic thinking too —
    +        # Reasoning priority: explicit per-call reasoning_config (MoA per-slot,
    +        # passed as _reasoning_config by _build_call_kwargs) wins over an
    +        # extra_body.reasoning dict (auxiliary.<task>.extra_body config).
             # build_anthropic_kwargs translates the config dict into the native
             # ``thinking`` field and handles models where thinking is mandatory.
    -        _reasoning_cfg = None
    -        _eb = kwargs.get("extra_body")
    -        if isinstance(_eb, dict):
    -            _rc = _eb.get("reasoning")
    -            if isinstance(_rc, dict):
    -                _reasoning_cfg = _rc
    +        _reasoning_cfg = reasoning_config
    +        if _reasoning_cfg is None:
    +            _eb = kwargs.get("extra_body")
    +            if isinstance(_eb, dict):
    +                _rc = _eb.get("reasoning")
    +                if isinstance(_rc, dict):
    +                    _reasoning_cfg = _rc
     
             anthropic_kwargs = build_anthropic_kwargs(
                 model=model,
    ```

    ```diff
    @@ -3441,6 +3443,7 @@ def _retry_same_provider_sync(
         tools: Optional[list],
         effective_timeout: float,
         effective_extra_body: dict,
    +    reasoning_config: Optional[dict],
     ) -> Any:
         if task == "vision":
             _, retry_client, retry_model = resolve_vision_provider_client(
    ```

    ```diff
    @@ -3474,6 +3477,7 @@ def _retry_same_provider_sync(
             tools=tools,
             timeout=effective_timeout,
             extra_body=effective_extra_body,
    +        reasoning_config=reasoning_config,
             base_url=retry_base or resolved_base_url,
         )
         if _is_anthropic_compat_endpoint(resolved_provider, retry_base):
    ```

    ```diff
    @@ -3498,6 +3502,7 @@ async def _retry_same_provider_async(
         tools: Optional[list],
         effective_timeout: float,
         effective_extra_body: dict,
    +    reasoning_config: Optional[dict],
     ) -> Any:
         if task == "vision":
             _, retry_client, retry_model = resolve_vision_provider_client(
    ```

    ```diff
    @@ -3531,6 +3536,7 @@ async def _retry_same_provider_async(
             tools=tools,
             timeout=effective_timeout,
             extra_body=effective_extra_body,
    +        reasoning_config=reasoning_config,
             base_url=retry_base or resolved_base_url,
         )
         if _is_anthropic_compat_endpoint(resolved_provider, retry_base):
    ```

    ```diff
    @@ -3650,6 +3656,7 @@ def _call_fallback_candidate_sync(
         tools: Optional[list],
         effective_timeout: float,
         effective_extra_body: dict,
    +    reasoning_config: Optional[dict],
     ) -> Optional[Any]:
         """Call one fallback candidate with stale-credential recovery.
     
    ```

    ```diff
    @@ -3671,7 +3678,8 @@ def _call_fallback_candidate_sync(
             fb_label, fb_model, messages,
             temperature=temperature, max_tokens=[redacted:secret-assignment]
             tools=tools, timeout=effective_timeout,
    -        extra_body=effective_extra_body, base_url=fb_base)
    +        extra_body=effective_extra_body, reasoning_config=reasoning_config,
    +        base_url=fb_base)
         try:
             return _validate_llm_response(
                 fb_client.chat.completions.create(**fb_kwargs), task)
    ```

    ```diff
    @@ -3687,6 +3695,7 @@ def _call_fallback_candidate_sync(
                         temperature=temperature, max_tokens=[redacted:secret-assignment]
                         tools=tools, timeout=effective_timeout,
                         extra_body=effective_extra_body,
    +                    reasoning_config=reasoning_config,
                         base_url=str(getattr(retry_client, "base_url", "") or fb_base))
                     try:
                         return _validate_llm_response(
    ```

    ```diff
    @@ -3719,6 +3728,7 @@ async def _call_fallback_candidate_async(
         tools: Optional[list],
         effective_timeout: float,
         effective_extra_body: dict,
    +    reasoning_config: Optional[dict],
     ) -> Optional[Any]:
         """Async mirror of :func:`_call_fallback_candidate_sync`."""
         fb_base = str(getattr(fb_client, "base_url", "") or "")
    ```

    ```diff
    @@ -3726,7 +3736,8 @@ async def _call_fallback_candidate_async(
             fb_label, fb_model, messages,
             temperature=temperature, max_tokens=[redacted:secret-assignment]
             tools=tools, timeout=effective_timeout,
    -        extra_body=effective_extra_body, base_url=fb_base)
    +        extra_body=effective_extra_body, reasoning_config=reasoning_config,
    +        base_url=fb_base)
         try:
             return _validate_llm_response(
                 await fb_client.chat.completions.create(**fb_kwargs), task)
    ```

    ```diff
    @@ -3743,6 +3754,7 @@ async def _call_fallback_candidate_async(
                         temperature=temperature, max_tokens=[redacted:secret-assignment]
                         tools=tools, timeout=effective_timeout,
                         extra_body=effective_extra_body,
    +                    reasoning_config=reasoning_config,
                         base_url=str(getattr(retry_client, "base_url", "") or fb_base))
                     try:
                         return _validate_llm_response(
    ```

    ```diff
    @@ -6163,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.<name>.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")
    ```

    ```diff
    @@ -6170,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.<name>...). Ignoring.",
    +                    task,
    +                )
    +                return result
                 from hermes_constants import parse_reasoning_effort
                 parsed = parse_reasoning_effort(effort)
                 if parsed is not None:
    ```

    ```diff
    @@ -6288,6 +6315,32 @@ def _convert_openai_images_to_anthropic(messages: list) -> list:
         return converted
     
     
    +_PROFILE_REASONING_KEYS = {
    +    "reasoning",
    +    "reasoning_effort",
    +    "thinking",
    +    "thinking_config",
    +    "thinkingconfig",
    +    "thinking_budget",
    +    "thinkingbudget",
    +    "enable_thinking",
    +    "think",
    +    "verbosity",
    +}
    +
    +
    +def _contains_profile_reasoning_fields(value: Any) -> bool:
    +    """Return whether a profile payload contains a reasoning wire control."""
    +    if not isinstance(value, dict):
    +        return False
    +    for key, nested in value.items():
    +        normalized = str(key).strip().lower()
    +        if normalized in _PROFILE_REASONING_KEYS:
    +            return True
    +        if _contains_profile_reasoning_fields(nested):
    +            return True
    +    return False
    +
     
     def _build_call_kwargs(
         provider: str,
    ```

    ```diff
    @@ -6298,6 +6351,7 @@ def _build_call_kwargs(
         tools: Optional[list] = None,
         timeout: float = 30.0,
         extra_body: Optional[dict] = None,
    +    reasoning_config: Optional[dict] = None,
         base_url: Optional[str] = None,
     ) -> dict:
         """Build kwargs for .chat.completions.create() with model/provider adjustments."""
    ```

    ```diff
    @@ -6381,13 +6435,89 @@ def _build_call_kwargs(
                 _deduped.append(_t)
             kwargs["tools"] = _deduped
     
    -    # Provider-specific extra_body
    +    # Build provider-aware reasoning kwargs through the same profile hooks used
    +    # by the standard chat-completions transport. Some providers require
    +    # top-level controls (Kimi/custom ``reasoning_effort``), others use nested
    +    # body fields (Gemini ``thinking_config``), and OpenRouter/Nous use
    +    # ``extra_body.reasoning``. Profiles are the source of truth for those wire
    +    # shapes. Providers without a reasoning-aware profile retain the generic
    +    # ``extra_body.reasoning`` fallback used by Codex-compatible adapters.
    +    effective_base = base_url or (
    +        _current_custom_base_url() if provider == "custom" else ""
    +    )
    +    profile_body: Dict[str, Any] = {}
    +    profile_reasoning_extra: Dict[str, Any] = {}
    +    profile_top_level: Dict[str, Any] = {}
    +    profile_handles_reasoning = False
    +    try:
    +        from providers import get_provider_profile
    +        from providers.base import ProviderProfile
    +
    +        profile = get_provider_profile(str(provider or "").strip().lower())
    +        if profile is not None:
    +            profile_body = profile.build_extra_body(
    +                model=model,
    +                base_url=effective_base,
    +                reasoning_config=reasoning_config,
    +            ) or {}
    +            profile_reasoning_extra, profile_top_level = (
    +                profile.build_api_kwargs_extras(
    +                    reasoning_config=reasoning_config,
    +                    supports_reasoning=reasoning_config is not None,
    +                    model=model,
    +                    base_url=effective_base,
    +                )
    +            )
    +            profile_reasoning_extra = profile_reasoning_extra or {}
    +            profile_top_level = profile_top_level or {}
    +            profile_handles_reasoning = (
    +                type(profile).build_api_kwargs_extras
    +                is not ProviderProfile.build_api_kwargs_extras
    +                or _contains_profile_reasoning_fields(profile_body)
    +                or _contains_profile_reasoning_fields(profile_reasoning_extra)
    +                or _contains_profile_reasoning_fields(profile_top_level)
    +            )
    +    except Exception as exc:
    +        logger.debug(
    +            "_build_call_kwargs: provider profile projection failed for %s: %s",
    +            provider,
    +            exc,
    +        )
    +
    +    kwargs.update(profile_top_level)
         merged_extra = dict(extra_body or {})
    -    if provider == "nous":
    -        merged_extra.setdefault("tags", []).extend(_nous_portal_tags())
    +    merged_extra.update(profile_body)
    +    merged_extra.update(profile_reasoning_extra)
    +    if (
    +        reasoning_config
    +        and isinstance(reasoning_config, dict)
    +        and not profile_handles_reasoning
    +    ):
    +        if reasoning_config.get("enabled") is False:
    +            merged_extra["reasoning"] = {"enabled": False}
    +        else:
    +            effort = reasoning_config.get("effort") or "medium"
    +            merged_extra["reasoning"] = {"enabled": True, "effort": effort}
    +    if provider == "nous" and "tags" not in merged_extra:
    +        merged_extra["tags"] = _nous_portal_tags()
         if merged_extra:
             kwargs["extra_body"] = merged_extra
     
    +    # Native Anthropic Messages adapters do not consume ``extra_body``. Carry
    +    # the normalized Hermes reasoning config through a private kwarg so the
    +    # adapter can pass it into build_anthropic_kwargs(), where provider-aware
    +    # thinking/output_config projection lives. Do not expose this private kwarg
    +    # to ordinary OpenAI-compatible SDK clients, which would reject it.
    +    if reasoning_config and isinstance(reasoning_config, dict):
    +        provider_norm = str(provider or "").strip().lower()
    +        effective_base = base_url or ""
    +        if (
    +            provider_norm == "anthropic"
    +            or _endpoint_speaks_anthropic_messages(effective_base)
    +            or _is_anthropic_compat_endpoint(provider_norm, effective_base)
    +        ):
    +            kwargs["_reasoning_config"] = dict(reasoning_config)
    +
         return kwargs
     
     
    ```

    ```diff
    @@ -6497,6 +6627,7 @@ def call_llm(
         tools: list = None,
         timeout: float = None,
         extra_body: dict = None,
    +    reasoning_config: Optional[dict] = None,
         api_mode: str = None,
         stream: bool = False,
         stream_options: dict = None,
    ```

    ```diff
    @@ -6520,6 +6651,8 @@ def call_llm(
             tools: Tool definitions (for function calling).
             timeout: Request timeout in seconds (None = read from auxiliary.{task}.timeout config).
             extra_body: Additional request body fields.
    +        reasoning_config: Optional Hermes reasoning config for direct model calls
    +              such as MoA reference/aggregator slots.
             stream: When True, return the raw SDK streaming iterator instead of a
                 validated complete response. The caller is responsible for consuming
                 chunks (and for any fallback). Used by the MoA aggregator so its
    ```

    ```diff
    @@ -6624,6 +6757,7 @@ def call_llm(
             resolved_provider, final_model, messages,
             temperature=temperature, max_tokens=[redacted:secret-assignment]
             tools=tools, timeout=effective_timeout, extra_body=effective_extra_body,
    +        reasoning_config=reasoning_config,
             base_url=_base_info or resolved_base_url)
     
         # Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)
    ```

    ```diff
    @@ -6876,6 +7010,7 @@ def call_llm(
                         tools=tools,
                         effective_timeout=effective_timeout,
                         effective_extra_body=effective_extra_body,
    +                    reasoning_config=reasoning_config,
                     )
     
             # ── Same-provider credential-pool recovery ─────────────────────
    ```

    ```diff
    @@ -6918,6 +7053,7 @@ def call_llm(
                             tools=tools,
                             effective_timeout=effective_timeout,
                             effective_extra_body=effective_extra_body,
    +                        reasoning_config=reasoning_config,
                         )
                     except Exception as retry2_err:
                         # The rotated key also hit a quota/auth wall.  Mark it
    ```

    ```diff
    @@ -7039,7 +7175,8 @@ def call_llm(
                         task=task, messages=messages,
                         temperature=temperature, max_tokens=[redacted:secret-assignment]
                         tools=tools, effective_timeout=effective_timeout,
    -                    effective_extra_body=effective_extra_body)
    +                    effective_extra_body=effective_extra_body,
    +                    reasoning_config=reasoning_config)
                     if fb_resp is not None:
                         return fb_resp
                     # The candidate had a stale/unrefreshable credential and was
    ```

    ```diff
    @@ -7053,7 +7190,8 @@ def call_llm(
                             task=task, messages=messages,
                             temperature=temperature, max_tokens=[redacted:secret-assignment]
                             tools=tools, effective_timeout=effective_timeout,
    -                        effective_extra_body=effective_extra_body)
    +                        effective_extra_body=effective_extra_body,
    +                        reasoning_config=reasoning_config)
                         if fb_resp is not None:
                             return fb_resp
                 # All fallback layers exhausted — emit a single user-visible
    ```

    ```diff
    @@ -7148,6 +7286,7 @@ async def async_call_llm(
         tools: list = None,
         timeout: float = None,
         extra_body: dict = None,
    +    reasoning_config: Optional[dict] = None,
     ) -> Any:
         """Centralized asynchronous LLM call.
     
    ```

    ```diff
    @@ -7227,6 +7366,7 @@ async def async_call_llm(
             resolved_provider, final_model, messages,
             temperature=temperature, max_tokens=[redacted:secret-assignment]
             tools=tools, timeout=effective_timeout, extra_body=effective_extra_body,
    +        reasoning_config=reasoning_config,
             base_url=_client_base or resolved_base_url)
     
         # Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)
    ```

    ```diff
    @@ -7423,6 +7563,7 @@ async def async_call_llm(
                         tools=tools,
                         effective_timeout=effective_timeout,
                         effective_extra_body=effective_extra_body,
    +                    reasoning_config=reasoning_config,
                     )
     
             # ── Same-provider credential-pool recovery (mirrors sync) ─────
    ```

    ```diff
    @@ -7460,6 +7601,7 @@ async def async_call_llm(
                             tools=tools,
                             effective_timeout=effective_timeout,
                             effective_extra_body=effective_extra_body,
    +                        reasoning_config=reasoning_config,
                         )
                     except Exception as retry2_err:
                         if (_is_payment_error(retry2_err) or _is_auth_error(retry2_err)
    ```

    ```diff
    @@ -7548,7 +7690,8 @@ async def async_call_llm(
                         task=task, messages=messages,
                         temperature=temperature, max_tokens=[redacted:secret-assignment]
                         tools=tools, effective_timeout=effective_timeout,
    -                    effective_extra_body=effective_extra_body)
    +                    effective_extra_body=effective_extra_body,
    +                    reasoning_config=reasoning_config)
                     if fb_resp is not None:
                         return fb_resp
                     # Stale/unrefreshable candidate credential — quarantined; walk
    ```

    ```diff
    @@ -7564,7 +7707,8 @@ async def async_call_llm(
                             task=task, messages=messages,
                             temperature=temperature, max_tokens=[redacted:secret-assignment]
                             tools=tools, effective_timeout=effective_timeout,
    -                        effective_extra_body=effective_extra_body)
    +                        effective_extra_body=effective_extra_body,
    +                        reasoning_config=reasoning_config)
                         if fb_resp is not None:
                             return fb_resp
                 # All fallback layers exhausted — warn before re-raising. (#26882)
    ```
  - `agent/moa_loop.py`

    ```diff
    @@ -120,11 +120,24 @@ def __init__(
     
     
     
    -def _slot_label(slot: dict[str, str]) -> str:
    -    return f"{(slot.get('provider') or '').strip()}:{(slot.get('model') or '').strip()}"
    +def _slot_label(slot: dict[str, Any]) -> str:
    +    label = f"{(slot.get('provider') or '').strip()}:{(slot.get('model') or '').strip()}"
    +    effort = str(slot.get("reasoning_effort") or "").strip()
    +    return f"{label}[reasoning={effort}]" if effort else label
     
     
    -def _slot_runtime(slot: dict[str, str]) -> dict[str, Any]:
    +def _slot_reasoning_config(slot: dict[str, Any]) -> dict[str, Any] | None:
    +    """Translate optional per-MoA-slot reasoning_effort into runtime config."""
    +    effort = slot.get("reasoning_effort")
    +    try:
    +        from hermes_constants import parse_reasoning_effort
    +
    +        return parse_reasoning_effort(effort)
    +    except Exception:  # pragma: no cover - defensive; bad config must not break MoA
    +        return None
    +
    +
    +def _slot_runtime(slot: dict[str, Any]) -> dict[str, Any]:
         """Resolve a reference/aggregator slot to real runtime call kwargs.
     
         A MoA slot is just a model selection — it must be called the same way any
    ```

    ```diff
    @@ -276,6 +289,7 @@ def _run_reference(
                 messages=messages,
                 temperature=temperature,
                 max_tokens=[redacted:secret-assignment]
    +            reasoning_config=_slot_reasoning_config(slot),
                 **runtime,
             )
             usage = CanonicalUsage()
    ```

    ```diff
    @@ -680,6 +694,7 @@ def aggregate_moa_context(
                 messages=agg_messages,
                 temperature=aggregator_temperature,
                 max_tokens=[redacted:secret-assignment]
    +            reasoning_config=_slot_reasoning_config(aggregator),
                 **agg_runtime,
             )
             synthesis = _extract_text(response)
    ```

    ```diff
    @@ -1074,6 +1089,7 @@ def create(self, **api_kwargs: Any) -> Any:
                 max_tokens=[redacted:secret-assignment]"max_tokens"),
                 tools=agg_kwargs.get("tools"),
                 extra_body=agg_kwargs.get("extra_body"),
    +            reasoning_config=_slot_reasoning_config(aggregator),
                 **stream_kwargs,
                 **_slot_runtime(aggregator),
             )
    ```
  - `hermes_cli/config.py`

    ```diff
    @@ -1756,7 +1756,10 @@ def _ensure_hermes_home_managed(home: Path):
                 "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.<name>.
    +            # reference_models[].reasoning_effort / aggregator.reasoning_effort),
    +            # not at the auxiliary-task level.
             },
             "moa_aggregator": {
                 "provider": "auto",
    ```

    ```diff
    @@ -1765,7 +1768,7 @@ def _ensure_hermes_home_managed(home: Path):
                 "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.
             },
         },
         
    ```
  - `hermes_cli/moa_cmd.py`

    ```diff
    @@ -60,6 +60,12 @@ def _pick_slot(current: dict[str, str] | None = None) -> dict[str, str]:
         return {"provider": str(provider.get("slug") or ""), "model": str(model)}
     
     
    +def _format_slot(slot: dict[str, Any]) -> str:
    +    label = f"{slot['provider']}:{slot['model']}"
    +    effort = str(slot.get("reasoning_effort") or "").strip()
    +    return f"{label} [reasoning={effort}]" if effort else label
    +
    +
     def _print_config(config: dict[str, Any]) -> None:
         cfg = normalize_moa_config(config.get("moa") if isinstance(config, dict) else {})
         print("Mixture of Agents presets")
    ```

    ```diff
    @@ -71,9 +77,9 @@ def _print_config(config: dict[str, Any]) -> None:
             print(f"\n{marker} {name}")
             print("  Reference models:")
             for idx, slot in enumerate(preset["reference_models"], start=1):
    -            print(f"    {idx}. {slot['provider']}:{slot['model']}")
    +            print(f"    {idx}. {_format_slot(slot)}")
             agg = preset["aggregator"]
    -        print(f"  Aggregator: {agg['provider']}:{agg['model']}")
    +        print(f"  Aggregator: {_format_slot(agg)}")
     
     
     def cmd_moa(args) -> None:
    ```
  - `hermes_cli/moa_config.py`

    ```diff
    @@ -73,7 +73,23 @@ def _coerce_fanout(value: Any) -> str:
         return mode if mode in {"per_iteration", "user_turn"} else "per_iteration"
     
     
    -def _clean_slot(slot: Any) -> dict[str, str] | None:
    +def _clean_reasoning_effort(value: Any) -> str | None:
    +    """Return a canonical per-slot reasoning effort, or None when unset/invalid."""
    +    if value is None or value is True:
    +        return None
    +    if value is False:
    +        return "none"
    +    text = str(value or "").strip().lower()
    +    if not text:
    +        return None
    +    if text in {"none", "false", "disabled"}:
    +        return "none"
    +    if text in {"minimal", "low", "medium", "high", "xhigh", "max"}:
    +        return text
    +    return None
    +
    +
    +def _clean_slot(slot: Any) -> dict[str, Any] | None:
         if not isinstance(slot, dict):
             return None
         provider = str(slot.get("provider") or "").strip()
    ```

    ```diff
    @@ -87,7 +103,11 @@ def _clean_slot(slot: Any) -> dict[str, str] | None:
         # an invalid slot is dropped, falling back to the preset's defaults.
         if provider.lower() == "moa":
             return None
    -    return {"provider": provider, "model": model}
    +    clean: dict[str, Any] = {"provider": provider, "model": model}
    +    effort = _clean_reasoning_effort(slot.get("reasoning_effort"))
    +    if effort:
    +        clean["reasoning_effort"] = effort
    +    return clean
     
     
     def _default_preset() -> dict[str, Any]:
    ```
  - `scripts/release.py`

    ```diff
    @@ -330,6 +330,7 @@
         "[redacted:email]": "synapsesx",
         "[redacted:email]": "itsflownium",
         "[redacted:email]": "dirtyren",
    +    "[redacted:email]": "justinschille",
         "[redacted:email]": "unsupportedpastels",
         "[redacted:email]": "John-Lussier",
         "[redacted:email]": "bytesnail",  # PR #60360 salvage (--yolo startup ordering; #60328)
    ```
  - `tests/agent/test_auxiliary_client.py`

    ```diff
    @@ -3331,6 +3331,28 @@ def test_empty_reasoning_effort_is_noop(self):
             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
    @@ -3897,6 +3919,157 @@ def mark_exhausted_and_rotate(self, **kwargs):
             mock_fallback.assert_not_called()
     
     
    +class TestAnthropicAuxiliaryReasoningTranslation:
    +    """Native Anthropic aux adapters must receive normalized Hermes reasoning.
    +
    +    MoA slot reasoning is carried through call_llm as a Hermes
    +    ``reasoning_config``. The native Anthropic Messages path cannot consume the
    +    generic OpenAI-style ``extra_body.reasoning`` fallback, so assert the final
    +    ``messages.create`` kwargs contain Anthropic's provider-aware wire shape.
    +    """
    +
    +    @staticmethod
    +    def _build_adapter(model="claude-fable-5"):
    +        from agent.auxiliary_client import _AnthropicCompletionsAdapter
    +
    +        captured = {}
    +
    +        class _Messages:
    +            def create(self, **kwargs):
    +                captured.update(kwargs)
    +                return SimpleNamespace(
    +                    content=[SimpleNamespace(type="text", text="ok")],
    +                    stop_reason="end_turn",
    +                    usage=SimpleNamespace(input_tokens=1, output_tokens=1, total_tokens=2),
    +                )
    +
    +        real_client = SimpleNamespace(messages=_Messages())
    +        return _AnthropicCompletionsAdapter(real_client, model), captured
    +
    +    def test_reasoning_config_reaches_native_anthropic_wire_kwargs(self):
    +        adapter, captured = self._build_adapter()
    +
    +        adapter.create(
    +            model="claude-fable-5",
    +            messages=[{"role": "user", "content": "hi"}],
    +            _reasoning_config={"enabled": True, "effort": "medium"},
    +        )
    +
    +        assert captured["thinking"] == {"type": "adaptive", "display": "summarized"}
    +        assert captured["output_config"] == {"effort": "medium"}
    +        assert "extra_body" not in captured
    +
    +    def test_build_call_kwargs_private_reasoning_only_for_anthropic_messages(self):
    +        anthropic_kwargs = _build_call_kwargs(
    +            "anthropic",
    +            "claude-fable-5",
    +            [{"role": "user", "content": "hi"}],
    +            reasoning_config={"enabled": True, "effort": "medium"},
    +            base_url="https://api.anthropic.com/v1",
    +        )
    +        assert anthropic_kwargs["_reasoning_config"] == {"enabled": True, "effort": "medium"}
    +
    +        proxy_kwargs = _build_call_kwargs(
    +            "custom",
    +            "claude-fable-5",
    +            [{"role": "user", "content": "hi"}],
    +            reasoning_config={"enabled": True, "effort": "medium"},
    +            base_url="https://example.test/anthropic/v1",
    +        )
    +        assert proxy_kwargs["_reasoning_config"] == {"enabled": True, "effort": "medium"}
    +
    +        openai_wire_kwargs = _build_call_kwargs(
    +            "custom",
    +            "gpt-compatible",
    +            [{"role": "user", "content": "hi"}],
    +            reasoning_config={"enabled": True, "effort": "medium"},
    +            base_url="https://example.test/v1",
    +        )
    +        assert "_reasoning_config" not in openai_wire_kwargs
    +
    +
    +class TestAuxiliaryProviderProfileReasoning:
    +    """Auxiliary calls must reuse provider-profile reasoning wire shapes."""
    +
    +    def test_kimi_reasoning_uses_top_level_effort(self):
    +        kwargs = _build_call_kwargs(
    +            "kimi-coding",
    +            "kimi-k2-turbo-preview",
    +            [{"role": "user", "content": "hi"}],
    +            reasoning_config={"enabled": True, "effort": "medium"},
    +            base_url="https://api.moonshot.ai/v1",
    +        )
    +
    +        assert kwargs["reasoning_effort"] == "medium"
    +        assert "reasoning" not in kwargs.get("extra_body", {})
    +        assert "thinking" not in kwargs.get("extra_body", {})
    +
    +    def test_gemini_reasoning_uses_thinking_config(self):
    +        kwargs = _build_call_kwargs(
    +            "gemini",
    +            "gemini-3.5-flash",
    +            [{"role": "user", "content": "hi"}],
    +            reasoning_config={"enabled": True, "effort": "high"},
    +            base_url="https://generativelanguage.googleapis.com/v1beta",
    +        )
    +
    +        assert kwargs["extra_body"]["thinking_config"] == {
    +            "includeThoughts": True,
    +            "thinkingLevel": "high",
    +        }
    +        assert "reasoning" not in kwargs["extra_body"]
    +
    +    def test_custom_openai_compatible_reasoning_uses_top_level_effort(self):
    +        kwargs = _build_call_kwargs(
    +            "custom",
    +            "glm-5.2",
    +            [{"role": "user", "content": "hi"}],
    +            reasoning_config={"enabled": True, "effort": "max"},
    +            base_url="https://example.test/v1",
    +        )
    +
    +        assert kwargs["reasoning_effort"] == "max"
    +        assert "reasoning" not in kwargs.get("extra_body", {})
    +
    +    @pytest.mark.asyncio
    +    async def test_async_call_llm_preserves_profile_reasoning_kwargs(self):
    +        response = SimpleNamespace(
    +            choices=[SimpleNamespace(message=SimpleNamespace(content="ok"))]
    +        )
    +        create = AsyncMock(return_value=response)
    +        client = SimpleNamespace(
    +            base_url="https://api.moonshot.ai/v1",
    +            chat=SimpleNamespace(
    +                completions=SimpleNamespace(create=create),
    +            ),
    +        )
    +
    +        with patch(
    +            "agent.auxiliary_client._resolve_task_provider_model",
    +            return_value=(
    +                "kimi-coding",
    +                "kimi-k2-turbo-preview",
    +                "https://api.moonshot.ai/v1",
    +                "test-key",
    +                None,
    +            ),
    +        ), patch(
    +            "agent.auxiliary_client._get_cached_client",
    +            return_value=(client, "kimi-k2-turbo-preview"),
    +        ):
    +            result = await async_call_llm(
    +                provider="kimi-coding",
    +                model="kimi-k2-turbo-preview",
    +                messages=[{"role": "user", "content": "hi"}],
    +                reasoning_config={"enabled": True, "effort": "high"},
    +            )
    +
    +        assert result is response
    +        final_kwargs = create.call_args.kwargs
    +        assert final_kwargs["reasoning_effort"] == "high"
    +        assert "reasoning" not in final_kwargs.get("extra_body", {})
    +
    +
     class TestCodexAdapterReasoningTranslation:
         """Verify _CodexCompletionsAdapter translates extra_body.reasoning
         into the Responses API's top-level reasoning + include fields, matching
    ```
  - `tests/agent/test_moa_reasoning_effort.py`

    ```diff
    @@ -0,0 +1,72 @@
    +from types import SimpleNamespace
    +from unittest.mock import patch
    +
    +
    +
    +def _response(content="ok"):
    +    message = SimpleNamespace(content=content, tool_calls=[])
    +    choice = SimpleNamespace(message=message, finish_reason="stop")
    +    return SimpleNamespace(choices=[choice], usage=None, model="fake")
    +
    +
    +
    +def test_slot_label_includes_reasoning_effort():
    +    from agent.moa_loop import _slot_label
    +
    +    assert _slot_label(
    +        {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": "xhigh"}
    +    ) == "openai-codex:gpt-5.6-sol[reasoning=xhigh]"
    +
    +
    +
    +def test_slot_reasoning_config_parses_effort_and_none():
    +    from agent.moa_loop import _slot_reasoning_config
    +
    +    assert _slot_reasoning_config({"reasoning_effort": "high"}) == {
    +        "enabled": True,
    +        "effort": "high",
    +    }
    +    assert _slot_reasoning_config({"reasoning_effort": "none"}) == {"enabled": False}
    +    assert _slot_reasoning_config({}) is None
    +
    +
    +
    +def test_moa_reference_passes_per_slot_reasoning_config(monkeypatch):
    +    from agent.moa_loop import _run_reference
    +
    +    captured = {}
    +
    +    def fake_call_llm(**kwargs):
    +        captured.update(kwargs)
    +        return _response("advice")
    +
    +    monkeypatch.setattr("agent.moa_loop.call_llm", fake_call_llm)
    +    with patch("hermes_cli.runtime_provider.resolve_runtime_provider") as mock_resolve:
    +        mock_resolve.return_value = {"provider": "openai-codex", "model": "gpt-5.6-sol"}
    +        _run_reference(
    +            {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": "low"},
    +            [{"role": "user", "content": "judge this"}],
    +        )
    +
    +    assert captured["reasoning_config"] == {"enabled": True, "effort": "low"}
    +
    +
    +
    +def test_call_llm_builder_translates_reasoning_config_to_extra_body():
    +    from agent.auxiliary_client import _build_call_kwargs
    +
    +    kwargs = _build_call_kwargs(
    +        "openai-codex",
    +        "gpt-5.6-sol",
    +        [{"role": "user", "content": "hi"}],
    +        reasoning_config={"enabled": True, "effort": "xhigh"},
    +    )
    +    assert kwargs["extra_body"]["reasoning"] == {"enabled": True, "effort": "xhigh"}
    +
    +    off = _build_call_kwargs(
    +        "openai-codex",
    +        "gpt-5.6-sol",
    +        [{"role": "user", "content": "hi"}],
    +        reasoning_config={"enabled": False},
    +    )
    +    assert off["extra_body"]["reasoning"] == {"enabled": False}
    ```
  - `tests/hermes_cli/test_moa_config.py`

    ```diff
    @@ -97,6 +97,29 @@ def test_normalize_moa_config_wraps_bare_dict_reference_models():
         assert cfg["presets"]["p"]["reference_models"] == [{"provider": "openai", "model": "gpt-4o"}]
     
     
    +def test_normalize_moa_config_preserves_slot_reasoning_effort():
    +    cfg = normalize_moa_config(
    +        {
    +            "presets": {
    +                "p": {
    +                    "reference_models": [
    +                        {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": "LOW"},
    +                        {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": False},
    +                        {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": "nonsense"},
    +                    ],
    +                    "aggregator": {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": "xhigh"},
    +                }
    +            }
    +        }
    +    )
    +
    +    preset = cfg["presets"]["p"]
    +    assert preset["reference_models"][0]["reasoning_effort"] == "low"
    +    assert preset["reference_models"][1]["reasoning_effort"] == "none"
    +    assert "reasoning_effort" not in preset["reference_models"][2]
    +    assert preset["aggregator"]["reasoning_effort"] == "xhigh"
    +
    +
     def test_normalize_moa_config_coerces_numeric_strings():
         """Valid numeric strings (e.g. from YAML round-trip) must coerce correctly."""
         cfg = normalize_moa_config({"max_tokens": "8192", "reference_temperature": "0.9"})
    ```
  - `website/docs/user-guide/configuration.md`

    ```diff
    @@ -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.<name>.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:
    ```
  - `website/docs/user-guide/features/mixture-of-agents.md`

    ```diff
    @@ -132,6 +132,35 @@ moa:
     
     Leave it unset (or `0`/blank) to keep the prior uncapped behavior.
     
    +### Per-slot reasoning effort
    +
    +Reference and aggregator slots may also set `reasoning_effort`. Use this when
    +you want the same model to contribute at different depths, or when the
    +aggregator should think harder than the advisory references. Valid values match
    +Hermes' normal reasoning controls: `none`, `minimal`, `low`, `medium`, `high`,
    +`xhigh`, and `max`.
    +
    +```yaml
    +moa:
    +  presets:
    +    deep_review:
    +      reference_models:
    +        - provider: openai-codex
    +          model: gpt-5.6-sol
    +          reasoning_effort: low
    +        - provider: openai-codex
    +          model: gpt-5.6-sol
    +          reasoning_effort: xhigh
    +        - provider: xai-oauth
    +          model: grok-4.5
    +      aggregator:
    +        provider: openai-codex
    +        model: gpt-5.6-sol
    +        reasoning_effort: high
    +```
    +
    +Omit `reasoning_effort` to use the provider/Hermes default for that slot.
    +
     ## Terminal preset management
     
     ```bash
    ```
- **#54316: feat(moa): allow reference reasoning effort**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#64631`
- **#61711: feat(moa): support per-slot reasoning effort**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `3dca75b45`, `5646dbdd5`, `#64631`, `#64597`, `#53932`

## Fix status

- Merged to main in PR #64631 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-15`.
- Subsystem: `agent`
