{"repo":"NousResearch/hermes-agent","public_id":"55187","display_label":"MoA disabled presets incorrectly matching/activating","summary":"A bug where disabled MoA presets were still being selected or matched during plain chat interactions. Fixes target preset-skipping logic to respect the enabled flag.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":55598,"merged_at":"2026-06-30T11:22:33+00:00"},"resolution":{"state":"resolved","by":"pr-55598"},"freshness":{"state":"verified","last_verified_at":"2026-06-30T11:22:33+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":55187,"pr_number":55598,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-30T11:22:33+00:00"}],"members":[{"kind":"issue","number":55187,"title":"[Bug] MoA virtual provider auto-selects on plain chat message; `enabled: false` presets still match","url":null,"state":"closed"},{"kind":"pr","number":55193,"title":"fix(moa): skip disabled presets in exact_moa_preset_name","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#55598","number":55598}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":55598,"title":"fix(moa): disabled presets no longer hijack a plain model switch","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/moa_config.py","tests/hermes_cli/test_moa_config.py"],"fix_hunks":[{"file":"hermes_cli/moa_config.py","hunks":["@@ -158,11 +158,26 @@ def resolve_moa_preset(config: Any, name: str | None = None) -> dict[str, Any]:\n \n \n def exact_moa_preset_name(config: Any, text: str) -> str | None:\n+    \"\"\"Return the preset name iff ``text`` exactly matches an *enabled* preset.\n+\n+    Used by the no-explicit-provider switch path (PATH B in\n+    ``hermes_cli/model_switch.py``) to recognize a bare ``/model <preset>``\n+    that the user typed without the ``moa:`` prefix. This is an *implicit*\n+    match, so it must honor the per-preset ``enabled`` opt-out: a user who set\n+    ``enabled: false`` to disable a preset must not have a plain model switch\n+    whose name happens to collide with that preset key silently pivot the\n+    session onto the MoA virtual provider (issue #55187). Explicit selection\n+    via ``--provider moa`` / the model picker does not go through here, so a\n+    disabled preset is still reachable when the user explicitly asks for it.\n+    \"\"\"\n     wanted = str(text or \"\").strip()\n     if not wanted:\n         return None\n     cfg = normalize_moa_config(config)\n-    return wanted if wanted in cfg[\"presets\"] else None\n+    preset = cfg[\"presets\"].get(wanted)\n+    if preset is None or not preset.get(\"enabled\", True):\n+        return None\n+    return wanted\n \n \n def set_active_moa_preset(config: Any, name: str | None) -> dict[str, Any]:"]},{"file":"tests/hermes_cli/test_moa_config.py","hunks":["@@ -120,6 +120,38 @@ def test_exact_preset_matching_is_not_fuzzy():\n     assert exact_moa_preset_name(config, \"coding please fix this\") is None\n \n \n+def test_exact_preset_matching_skips_disabled_presets():\n+    \"\"\"A disabled preset must not match the implicit bare-name switch path.\n+\n+    Regression for #55187: with ``enabled: false`` presets, a plain model\n+    switch whose name collides with a preset key (e.g. ``default``) silently\n+    pivoted the session onto the MoA virtual provider. The per-preset\n+    ``enabled`` opt-out must gate this implicit match.\n+    \"\"\"\n+    config = {\n+        \"presets\": {\n+            \"default\": {\"enabled\": False},\n+            \"klo\": {\"enabled\": False},\n+        },\n+    }\n+    assert exact_moa_preset_name(config, \"default\") is None\n+    assert exact_moa_preset_name(config, \"klo\") is None\n+\n+\n+def test_exact_preset_matching_allows_enabled_presets():\n+    \"\"\"An explicitly enabled preset still matches the bare-name switch path.\"\"\"\n+    config = {\n+        \"presets\": {\n+            \"fast\": {\"enabled\": True},\n+            \"slow\": {\"enabled\": False},\n+        },\n+    }\n+    assert exact_moa_preset_name(config, \"fast\") == \"fast\"\n+    assert exact_moa_preset_name(config, \"slow\") is None\n+    # Default (no explicit enabled key) is enabled and still matches.\n+    assert exact_moa_preset_name({\"presets\": {\"x\": {}}}, \"x\") == \"x\"\n+\n+\n def test_active_preset_toggle_validation():\n     config = {\"default_preset\": \"coding\", \"presets\": {\"coding\": {}, \"review\": {}}}\n \n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}