{"repo":"NousResearch/hermes-agent","public_id":"8517","display_label":"Background memory review model configuration and fixes","summary":"These issues and PRs concern problems with model selection for background memory and skill review processes, including removal of smart model routing, bugs around cheap models lacking tool-calling ability, and requests to make the review model configurable.","subsystem":"agent","counts":{"members":6,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":3,"merged_prs":2,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":12732},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-04-26T15:29:40+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.24799999999999997},"fix_edges":[{"issue_number":3233,"pr_number":16099,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":8462,"pr_number":12732,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":8515,"pr_number":12732,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":8516,"pr_number":12732,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":8517,"pr_number":12732,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":8517,"pr_number":16099,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":15543,"pr_number":16099,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":15882,"pr_number":16099,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":8517,"title":"[Bug]: Background memory review uses cheap model from smart routing — cannot call tools","url":null,"state":"closed"},{"kind":"issue","number":13578,"title":"[Feature]: Configurable model for background memory and skill reviews","url":null,"state":"closed"},{"kind":"issue","number":47279,"title":"[Feature]: change skills / memory models","url":null,"state":"closed"},{"kind":"pr","number":8351,"title":"fix(memory): use primary model for background memory/skill review","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"424e9f36b","number":0},{"kind":"commit","value":"424e9f36b0fff2f34a0037f1df22996cc5a659aa","number":0},{"kind":"commit","value":"e3901d5b2","number":0},{"kind":"commit","value":"e3901d5b257d5ac3f58420c3fb55aaa536fc56ac","number":0},{"kind":"pr","value":"#12732","number":12732},{"kind":"pr","value":"#16099","number":16099}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":12732,"title":"refactor: remove smart_model_routing feature","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/smart_model_routing.py","cli-config.yaml.example","cli.py","cron/scheduler.py","gateway/run.py","hermes_cli/config.py","hermes_cli/dump.py","hermes_cli/tips.py","hermes_cli/web_server.py","skills/autonomous-ai-agents/hermes-agent/SKILL.md","tests/agent/test_credential_pool_routing.py","tests/agent/test_smart_model_routing.py","tests/cli/test_cli_provider_resolution.py","tests/cli/test_fast_command.py","tests/cron/test_codex_execution_paths.py","tests/gateway/test_discord_channel_prompts.py","tests/gateway/test_fast_command.py","website/docs/integrations/providers.md"],"fix_hunks":[{"file":"/dev/null","hunks":["@@ -1,195 +0,0 @@\n-\"\"\"Helpers for optional cheap-vs-strong model routing.\"\"\"\n-\n-from __future__ import annotations\n-\n-import os\n-import re\n-from typing import Any, Dict, Optional\n-\n-from utils import is_truthy_value\n-\n-_COMPLEX_KEYWORDS = {\n-    \"debug\",\n-    \"debugging\",\n-    \"implement\",\n-    \"implementation\",\n-    \"refactor\",\n-    \"patch\",\n-    \"traceback\",\n-    \"stacktrace\",\n-    \"exception\",\n-    \"error\",\n-    \"analyze\",\n-    \"analysis\",\n-    \"investigate\",\n-    \"architecture\",\n-    \"design\",\n-    \"compare\",\n-    \"benchmark\",\n-    \"optimize\",\n-    \"optimise\",\n-    \"review\",\n-    \"terminal\",\n-    \"shell\",\n-    \"tool\",\n-    \"tools\",\n-    \"pytest\",\n-    \"test\",\n-    \"tests\",\n-    \"plan\",\n-    \"planning\",\n-    \"delegate\",\n-    \"subagent\",\n-    \"cron\",\n-    \"docker\",\n-    \"kubernetes\",\n-}\n-\n-_URL_RE = re.compile(r\"https?://|www\\.\", re.IGNORECASE)\n-\n-\n-def _coerce_bool(value: Any, default: bool = False) -> bool:\n-    return is_truthy_value(value, default=default)\n-\n-\n-def _coerce_int(value: Any, default: int) -> int:\n-    try:\n-        return int(value)\n-    except (TypeError, ValueError):\n-        return default\n-\n-\n-def choose_cheap_model_route(user_message: str, routing_config: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:\n-    \"\"\"Return the configured cheap-model route when a message looks simple.\n-\n-    Conservative by design: if the message has signs of code/tool/debugging/\n-    long-form work, keep the primary model.\n-    \"\"\"\n-    cfg = routing_config or {}\n-    if not _coerce_bool(cfg.get(\"enabled\"), False):\n-        return None\n-\n-    cheap_model = cfg.get(\"cheap_model\") or {}\n-    if not isinstance(cheap_model, dict):\n-        return None\n-    provider = str(cheap_model.get(\"provider\") or \"\").strip().lower()\n-    model = str(cheap_model.get(\"model\") or \"\").strip()\n-    if not provider or not model:\n-        return None\n-\n-    text = (user_message or \"\").strip()\n-    if not text:\n-        return None\n-\n-    max_chars = _coerce_int(cfg.get(\"max_simple_chars\"), 160)\n-    max_words = _coerce_int(cfg.get(\"max_simple_words\"), 28)\n-\n-    if len(text) > max_chars:\n-        return None\n-    if len(text.split()) > max_words:\n-        return None\n-    if text.count(\"\\n\") > 1:\n-        return None\n-    if \"```\" in text or \"`\" in text:\n-        return None\n-    if _URL_RE.search(text):\n-        return None\n-\n-    lowered = text.lower()\n-    words = {token.strip(\".,:;!?()[]{}\\\"'`\") for token in lowered.split()}\n-    if words & _COMPLEX_KEYWORDS:\n-        return None\n-\n-    route = dict(cheap_model)\n-    route[\"provider\"] = provider\n-    route[\"model\"] = model\n-    route[\"routing_reason\"] = \"simple_turn\"\n-    return route\n-\n-\n-def resolve_turn_route(user_message: str, routing_config: Optional[Dict[str, Any]], primary: Dict[str, Any]) -> Dict[str, Any]:\n-    \"\"\"Resolve the effective model/runtime for one turn.\n-\n-    Returns a dict with model/runtime/signature/label fields.\n-    \"\"\"\n-    route = choose_cheap_model_route(user_message, routing_config)\n-    if not route:\n-        return {\n-            \"model\": primary.get(\"model\"),\n-            \"runtime\": {\n-                \"api_key\": primary.get(\"api_key\"),\n-                \"base_url\": primary.get(\"base_url\"),\n-                \"provider\": primary.get(\"provider\"),\n-                \"api_mode\": primary.get(\"api_mode\"),\n-                \"command\": primary.get(\"command\"),\n-                \"args\": list(primary.get(\"args\") or []),\n-                \"credential_pool\": primary.get(\"credential_pool\"),\n-            },\n-            \"label\": None,\n-            \"signature\": (\n-                primary.get(\"model\"),\n-                primary.get(\"provider\"),\n-                primary.get(\"base_url\"),\n-                primary.get(\"api_mode\"),\n-                primary.get(\"command\"),\n-                tuple(primary.get(\"args\") or ()),\n-            ),\n-        }\n-\n-    from hermes_cli.runtime_provider import resolve_runtime_provider\n-\n-    explicit_api_key = None\n-    api_key_env = [redacted:secret-assignment]\"api_key_env\") or \"\").strip()\n-    if api_key_env:\n-        explicit_api_key = [redacted:secret-assignment] or None\n-\n-    try:\n-        runtime = resolve_runtime_provider(\n-            requested=route.get(\"provider\"),\n-            explicit_api_key=[redacted:secret-assignment]\n-            explicit_base_url=route.get(\"base_url\"),\n-        )\n-    except Exception:\n-        return {\n-            \"model\": primary.get(\"model\"),\n-            \"runtime\": {\n-                \"api_key\": primary.get(\"api_key\"),\n-                \"base_url\": primary.get(\"base_url\"),\n-                \"provider\": primary.get(\"provider\"),\n-                \"api_mode\": primary.get(\"api_mode\"),\n-                \"command\": primary.get(\"command\"),\n-                \"args\": list(primary.get(\"args\") or []),\n-                \"credential_pool\": primary.get(\"credential_pool\"),\n-            },\n-            \"label\": None,\n-            \"signature\": (\n-                primary.get(\"model\"),\n-                primary.get(\"provider\"),\n-                primary.get(\"base_url\"),\n-                primary.get(\"api_mode\"),\n-                primary.get(\"command\"),\n-                tuple(primary.get(\"args\") or ()),\n-            ),\n-        }\n-\n-    return {\n-        \"model\": route.get(\"model\"),\n-        \"runtime\": {\n-            \"api_key\": runtime.get(\"api_key\"),\n-            \"base_url\": runtime.get(\"base_url\"),\n-            \"provider\": runtime.get(\"provider\"),\n-            \"api_mode\": runtime.get(\"api_mode\"),\n-            \"command\": runtime.get(\"command\"),\n-            \"args\": list(runtime.get(\"args\") or []),\n-            \"credential_pool\": runtime.get(\"credential_pool\"),\n-        },\n-        \"label\": f\"smart route → {route.get('model')} ({runtime.get('provider')})\",\n-        \"signature\": (\n-            route.get(\"model\"),\n-            runtime.get(\"provider\"),\n-            runtime.get(\"base_url\"),\n-            runtime.get(\"api_mode\"),\n-            runtime.get(\"command\"),\n-            tuple(runtime.get(\"args\") or ()),\n-        ),\n-    }"]},{"file":"cli-config.yaml.example","hunks":["@@ -114,20 +114,6 @@ model:\n #   # Data policy: \"allow\" (default) or \"deny\" to exclude providers that may store data\n #   # data_collection: \"deny\"\n \n-# =============================================================================\n-# Smart Model Routing (optional)\n-# =============================================================================\n-# Use a cheaper model for short/simple turns while keeping your main model for\n-# more complex requests. Disabled by default.\n-#\n-# smart_model_routing:\n-#   enabled: true\n-#   max_simple_chars: 160\n-#   max_simple_words: 28\n-#   cheap_model:\n-#     provider: openrouter\n-#     model: google/gemini-2.5-flash\n-\n # =============================================================================\n # Git Worktree Isolation\n # ============================================================================="]},{"file":"cli.py","hunks":["@@ -310,12 +310,6 @@ def load_cli_config() -> Dict[str, Any]:\n             \"enabled\": True,      # Auto-compress when approaching context limit\n             \"threshold\": 0.50,    # Compress at 50% of model's context limit\n         },\n-        \"smart_model_routing\": {\n-            \"enabled\": False,\n-            \"max_simple_chars\": 160,\n-            \"max_simple_words\": 28,\n-            \"cheap_model\": {},\n-        },\n         \"agent\": {\n             \"max_turns\": 90,  # Default max tool-calling iterations (shared with subagents)\n             \"verbose\": False,","@@ -1857,8 +1851,9 @@ def __init__(\n             fb = [fb] if fb.get(\"provider\") and fb.get(\"model\") else []\n         self._fallback_model = fb\n \n-        # Optional cheap-vs-strong routing for simple turns\n-        self._smart_model_routing = CLI_CONFIG.get(\"smart_model_routing\", {}) or {}\n+        # Signature of the currently-initialised agent's runtime.  Used to\n+        # rebuild the agent when provider / model / base_url changes across\n+        # turns (e.g. after /model or credential rotation).\n         self._active_agent_route_signature = None\n \n         # Agent will be initialized on first use","@@ -2883,24 +2878,36 @@ def _ensure_runtime_credentials(self) -> bool:\n         return True\n \n     def _resolve_turn_agent_config(self, user_message: str) -> dict:\n-        \"\"\"Resolve model/runtime overrides for a single user turn.\"\"\"\n-        from agent.smart_model_routing import resolve_turn_route\n+        \"\"\"Build the effective model/runtime config for a single user turn.\n+\n+        Always uses the session's primary model/provider.  If the user has\n+        toggled `/fast` on and the current model supports Priority\n+        Processing / Anthropic fast mode, attach `request_overrides` so the\n+        API call is marked accordingly.\n+        \"\"\"\n         from hermes_cli.models import resolve_fast_mode_overrides\n \n-        route = resolve_turn_route(\n-            user_message,\n-            self._smart_model_routing,\n-            {\n-                \"model\": self.model,\n-                \"api_key\": self.api_key,\n-                \"base_url\": self.base_url,\n-                \"provider\": self.provider,\n-                \"api_mode\": self.api_mode,\n-                \"command\": self.acp_command,\n-                \"args\": list(self.acp_args or []),\n-                \"credential_pool\": getattr(self, \"_credential_pool\", None),\n-            },\n-        )\n+        runtime = {\n+            \"api_key\": self.api_key,\n+            \"base_url\": self.base_url,\n+            \"provider\": self.provider,\n+            \"api_mode\": self.api_mode,\n+            \"command\": self.acp_command,\n+            \"args\": list(self.acp_args or []),\n+            \"credential_pool\": getattr(self, \"_credential_pool\", None),\n+        }\n+        route = {\n+            \"model\": self.model,\n+            \"runtime\": runtime,\n+            \"signature\": (\n+                self.model,\n+                runtime[\"provider\"],\n+                runtime[\"base_url\"],\n+                runtime[\"api_mode\"],\n+                runtime[\"command\"],\n+                tuple(runtime[\"args\"]),\n+            ),\n+        }\n \n         service_tier = getattr(self, \"service_tier\", None)\n         if not service_tier:","@@ -2908,13 +2915,13 @@ def _resolve_turn_agent_config(self, user_message: str) -> dict:\n             return route\n \n         try:\n-            overrides = resolve_fast_mode_overrides(route.get(\"model\"))\n+            overrides = resolve_fast_mode_overrides(route[\"model\"])\n         except Exception:\n             overrides = None\n         route[\"request_overrides\"] = overrides\n         return route\n \n-    def _init_agent(self, *, model_override: str = None, runtime_override: dict = None, route_label: str = None, request_overrides: dict | None = None) -> bool:\n+    def _init_agent(self, *, model_override: str = None, runtime_override: dict = None, request_overrides: dict | None = None) -> bool:\n         \"\"\"\n         Initialize the agent on first use.\n         When resuming a session, restores conversation history from SQLite.","@@ -7911,7 +7918,6 @@ def chat(self, message, images: list = None) -> Optional[str]:\n         if not self._init_agent(\n             model_override=turn_route[\"model\"],\n             runtime_override=turn_route[\"runtime\"],\n-            route_label=turn_route[\"label\"],\n             request_overrides=turn_route.get(\"request_overrides\"),\n         ):\n             return None","@@ -10535,7 +10541,6 @@ def _signal_handler_q(signum, frame):\n                 if cli._init_agent(\n                     model_override=turn_route[\"model\"],\n                     runtime_override=turn_route[\"runtime\"],\n-                    route_label=turn_route[\"label\"],\n                     request_overrides=turn_route.get(\"request_overrides\"),\n                 ):\n                     cli.agent.quiet_mode = True"]},{"file":"cron/scheduler.py","hunks":["@@ -826,7 +826,6 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:\n \n         # Provider routing\n         pr = _cfg.get(\"provider_routing\", {})\n-        smart_routing = _cfg.get(\"smart_model_routing\", {}) or {}\n \n         from hermes_cli.runtime_provider import (\n             resolve_runtime_provider,","@@ -843,24 +842,9 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:\n             message = format_runtime_provider_error(exc)\n             raise RuntimeError(message) from exc\n \n-        from agent.smart_model_routing import resolve_turn_route\n-        turn_route = resolve_turn_route(\n-            prompt,\n-            smart_routing,\n-            {\n-                \"model\": model,\n-                \"api_key\": runtime.get(\"api_key\"),\n-                \"base_url\": runtime.get(\"base_url\"),\n-                \"provider\": runtime.get(\"provider\"),\n-                \"api_mode\": runtime.get(\"api_mode\"),\n-                \"command\": runtime.get(\"command\"),\n-                \"args\": list(runtime.get(\"args\") or []),\n-            },\n-        )\n-\n         fallback_model = _cfg.get(\"fallback_providers\") or _cfg.get(\"fallback_model\") or None\n         credential_pool = None\n-        runtime_provider = str(turn_route[\"runtime\"].get(\"provider\") or \"\").strip().lower()\n+        runtime_provider = str(runtime.get(\"provider\") or \"\").strip().lower()\n         if runtime_provider:\n             try:\n                 from agent.credential_pool import load_pool","@@ -877,13 +861,13 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:\n                 logger.debug(\"Job '%s': failed to load credential pool for %s: %s\", job_id, runtime_provider, e)\n \n         agent = AIAgent(\n-            model=turn_route[\"model\"],\n-            api_key=[redacted:secret-assignment]\"runtime\"].get(\"api_key\"),\n-            base_url=turn_route[\"runtime\"].get(\"base_url\"),\n-            provider=turn_route[\"runtime\"].get(\"provider\"),\n-            api_mode=turn_route[\"runtime\"].get(\"api_mode\"),\n-            acp_command=turn_route[\"runtime\"].get(\"command\"),\n-            acp_args=turn_route[\"runtime\"].get(\"args\"),\n+            model=model,\n+            api_key=[redacted:secret-assignment]\"api_key\"),\n+            base_url=runtime.get(\"base_url\"),\n+            provider=runtime.get(\"provider\"),\n+            api_mode=runtime.get(\"api_mode\"),\n+            acp_command=runtime.get(\"command\"),\n+            acp_args=runtime.get(\"args\"),\n             max_iterations=max_iterations,\n             reasoning_config=reasoning_config,\n             prefill_messages=prefill_messages,"]},{"file":"gateway/run.py","hunks":["@@ -629,7 +629,6 @@ def __init__(self, config: Optional[GatewayConfig] = None):\n         self._restart_drain_timeout = self._load_restart_drain_timeout()\n         self._provider_routing = self._load_provider_routing()\n         self._fallback_model = self._load_fallback_model()\n-        self._smart_model_routing = self._load_smart_model_routing()\n \n         # Wire process registry into session store for reset protection\n         from tools.process_registry import process_registry","@@ -1082,11 +1081,16 @@ def _resolve_session_agent_runtime(\n         return model, runtime_kwargs\n \n     def _resolve_turn_agent_config(self, user_message: str, model: str, runtime_kwargs: dict) -> dict:\n-        from agent.smart_model_routing import resolve_turn_route\n+        \"\"\"Build the effective model/runtime config for a single turn.\n+\n+        Always uses the session's primary model/provider.  If `/fast` is\n+        enabled and the model supports Priority Processing / Anthropic fast\n+        mode, attach `request_overrides` so the API call is marked\n+        accordingly.\n+        \"\"\"\n         from hermes_cli.models import resolve_fast_mode_overrides\n \n-        primary = {\n-            \"model\": model,\n+        runtime = {\n             \"api_key\": runtime_kwargs.get(\"api_key\"),\n             \"base_url\": runtime_kwargs.get(\"base_url\"),\n             \"provider\": runtime_kwargs.get(\"provider\"),","@@ -1095,7 +1099,18 @@ def _resolve_turn_agent_config(self, user_message: str, model: str, runtime_kwar\n             \"args\": list(runtime_kwargs.get(\"args\") or []),\n             \"credential_pool\": runtime_kwargs.get(\"credential_pool\"),\n         }\n-        route = resolve_turn_route(user_message, getattr(self, \"_smart_model_routing\", {}), primary)\n+        route = {\n+            \"model\": model,\n+            \"runtime\": runtime,\n+            \"signature\": (\n+                model,\n+                runtime[\"provider\"],\n+                runtime[\"base_url\"],\n+                runtime[\"api_mode\"],\n+                runtime[\"command\"],\n+                tuple(runtime[\"args\"]),\n+            ),\n+        }\n \n         service_tier = getattr(self, \"_service_tier\", None)\n         if not service_tier:","@@ -1103,7 +1118,7 @@ def _resolve_turn_agent_config(self, user_message: str, model: str, runtime_kwar\n             return route\n \n         try:\n-            overrides = resolve_fast_mode_overrides(route.get(\"model\"))\n+            overrides = resolve_fast_mode_overrides(route[\"model\"])\n         except Exception:\n             overrides = None\n         route[\"request_overrides\"] = overrides","@@ -1461,20 +1476,6 @@ def _load_fallback_model() -> list | dict | None:\n             pass\n         return None\n \n-    @staticmethod\n-    def _load_smart_model_routing() -> dict:\n-        \"\"\"Load optional smart cheap-vs-strong model routing config.\"\"\"\n-        try:\n-            import yaml as _y\n-            cfg_path = _hermes_home / \"config.yaml\"\n-            if cfg_path.exists():\n-                with open(cfg_path, encoding=\"utf-8\") as _f:\n-                    cfg = _y.safe_load(_f) or {}\n-                return cfg.get(\"smart_model_routing\", {}) or {}\n-        except Exception:\n-            pass\n-        return {}\n-\n     def _snapshot_running_agents(self) -> Dict[str, Any]:\n         return {\n             session_key: agent"]},{"file":"hermes_cli/config.py","hunks":["@@ -474,13 +474,6 @@ def _ensure_hermes_home_managed(home: Path):\n         },\n     },\n \n-    \"smart_model_routing\": {\n-        \"enabled\": False,\n-        \"max_simple_chars\": 160,\n-        \"max_simple_words\": 28,\n-        \"cheap_model\": {},\n-    },\n-    \n     # Auxiliary model config — provider:model for each side task.\n     # Format: provider is the provider name, model is the model slug.\n     # \"auto\" for provider = auto-detect best available provider.","@@ -2878,19 +2871,6 @@ def load_config() -> Dict[str, Any]:\n # fallback_model:\n #   provider: openrouter\n #   model: anthropic/claude-sonnet-4\n-#\n-# ── Smart Model Routing ────────────────────────────────────────────────\n-# Optional cheap-vs-strong routing for simple turns.\n-# Keeps the primary model for complex work, but can route short/simple\n-# messages to a cheaper model across providers.\n-#\n-# smart_model_routing:\n-#   enabled: true\n-#   max_simple_chars: 160\n-#   max_simple_words: 28\n-#   cheap_model:\n-#     provider: openrouter\n-#     model: google/gemini-2.5-flash\n \"\"\"\n \n ","@@ -2922,19 +2902,6 @@ def load_config() -> Dict[str, Any]:\n # fallback_model:\n #   provider: openrouter\n #   model: anthropic/claude-sonnet-4\n-#\n-# ── Smart Model Routing ────────────────────────────────────────────────\n-# Optional cheap-vs-strong routing for simple turns.\n-# Keeps the primary model for complex work, but can route short/simple\n-# messages to a cheaper model across providers.\n-#\n-# smart_model_routing:\n-#   enabled: true\n-#   max_simple_chars: 160\n-#   max_simple_words: 28\n-#   cheap_model:\n-#     provider: openrouter\n-#     model: google/gemini-2.5-flash\n \"\"\"\n \n "]},{"file":"hermes_cli/dump.py","hunks":["@@ -160,7 +160,6 @@ def _config_overrides(config: dict) -> dict[str, str]:\n         (\"display\", \"streaming\"),\n         (\"display\", \"skin\"),\n         (\"display\", \"show_reasoning\"),\n-        (\"smart_model_routing\", \"enabled\"),\n         (\"privacy\", \"redact_pii\"),\n         (\"tts\", \"provider\"),\n     ]"]},{"file":"hermes_cli/tips.py","hunks":["@@ -323,7 +323,6 @@\n     \"GPT-5 and Codex use 'developer' role instead of 'system' in the message format.\",\n     \"Per-task auxiliary overrides: auxiliary.vision.provider, auxiliary.compression.model, etc. in config.yaml.\",\n     \"The auxiliary client treats 'main' as a provider alias — resolves to your actual primary provider + model.\",\n-    \"Smart routing can auto-route simple queries to a cheaper model — set smart_model_routing.enabled: true.\",\n     \"hermes claw migrate --dry-run previews OpenClaw migration without writing anything.\",\n     \"File paths pasted with quotes or escaped spaces are handled automatically — no manual cleanup needed.\",\n     \"Slash commands never trigger the large-paste collapse — /command with big arguments works correctly.\","]},{"file":"hermes_cli/web_server.py","hunks":["@@ -232,7 +232,6 @@ async def auth_middleware(request: Request, call_next):\n     \"checkpoints\": \"agent\",\n     \"approvals\": \"security\",\n     \"human_delay\": \"display\",\n-    \"smart_model_routing\": \"agent\",\n     \"dashboard\": \"display\",\n     \"code_execution\": \"agent\",\n }"]},{"file":"skills/autonomous-ai-agents/hermes-agent/SKILL.md","hunks":["@@ -338,7 +338,6 @@ Edit with `hermes config edit` or `hermes config set section.key value`.\n | `memory` | `memory_enabled`, `user_profile_enabled`, `provider` |\n | `security` | `tirith_enabled`, `website_blocklist` |\n | `delegation` | `model`, `provider`, `base_url`, `api_key`, `max_iterations` (50), `reasoning_effort` |\n-| `smart_model_routing` | `enabled`, `cheap_model` |\n | `checkpoints` | `enabled`, `max_snapshots` (50) |\n \n Full config reference: https://hermes-agent.nousresearch.com/docs/user-guide/configuration"]},{"file":"tests/agent/test_credential_pool_routing.py","hunks":["@@ -1,129 +1,25 @@\n-\"\"\"Tests for credential pool preservation through smart routing and 429 recovery.\n+\"\"\"Tests for credential pool preservation through turn config and 429 recovery.\n \n Covers:\n-1. credential_pool flows through resolve_turn_route (no-route and fallback paths)\n-2. CLI _resolve_turn_agent_config passes credential_pool to primary dict\n-3. Gateway _resolve_turn_agent_config passes credential_pool to primary dict\n-4. Eager fallback deferred when credential pool has credentials\n-5. Eager fallback fires when no credential pool exists\n-6. Full 429 rotation cycle: retry-same → rotate → exhaust → fallback\n+1. CLI _resolve_turn_agent_config passes credential_pool to runtime dict\n+2. Gateway _resolve_turn_agent_config passes credential_pool to runtime dict\n+3. Eager fallback deferred when credential pool has credentials\n+4. Eager fallback fires when no credential pool exists\n+5. Full 429 rotation cycle: retry-same → rotate → exhaust → fallback\n \"\"\"\n \n-import os\n-import time\n from types import SimpleNamespace\n-from unittest.mock import MagicMock, patch, PropertyMock\n-\n-import pytest\n-\n-\n-# ---------------------------------------------------------------------------\n-# 1. smart_model_routing: credential_pool preserved in no-route path\n-# ---------------------------------------------------------------------------\n-\n-class TestSmartRoutingPoolPreservation:\n-    def test_no_route_preserves_credential_pool(self):\n-        from agent.smart_model_routing import resolve_turn_route\n-\n-        fake_pool = MagicMock(name=\"CredentialPool\")\n-        primary = {\n-            \"model\": \"gpt-5.4\",\n-            \"api_key\": \"sk-test\",\n-            \"base_url\": None,\n-            \"provider\": \"openai-codex\",\n-            \"api_mode\": \"codex_responses\",\n-            \"command\": None,\n-            \"args\": [],\n-            \"credential_pool\": fake_pool,\n-        }\n-        # routing disabled\n-        result = resolve_turn_route(\"hello\", None, primary)\n-        assert result[\"runtime\"][\"credential_pool\"] is fake_pool\n-\n-    def test_no_route_none_pool(self):\n-        from agent.smart_model_routing import resolve_turn_route\n-\n-        primary = {\n-            \"model\": \"gpt-5.4\",\n-            \"api_key\": \"sk-test\",\n-            \"base_url\": None,\n-            \"provider\": \"openai-codex\",\n-            \"api_mode\": \"codex_responses\",\n-            \"command\": None,\n-            \"args\": [],\n-        }\n-        result = resolve_turn_route(\"hello\", None, primary)\n-        assert result[\"runtime\"][\"credential_pool\"] is None\n-\n-    def test_routing_disabled_preserves_pool(self):\n-        from agent.smart_model_routing import resolve_turn_route\n-\n-        fake_pool = MagicMock(name=\"CredentialPool\")\n-        primary = {\n-            \"model\": \"gpt-5.4\",\n-            \"api_key\": \"sk-test\",\n-            \"base_url\": None,\n-            \"provider\": \"openai-codex\",\n-            \"api_mode\": \"codex_responses\",\n-            \"command\": None,\n-            \"args\": [],\n-            \"credential_pool\": fake_pool,\n-        }\n-        # routing explicitly disabled\n-        result = resolve_turn_route(\"hello\", {\"enabled\": False}, primary)\n-        assert result[\"runtime\"][\"credential_pool\"] is fake_pool\n-\n-    def test_route_fallback_on_resolve_error_preserves_pool(self, monkeypatch):\n-        \"\"\"When smart routing picks a cheap model but resolve_runtime_provider\n-        fails, the fallback to primary must still include credential_pool.\"\"\"\n-        from agent.smart_model_routing import resolve_turn_route\n-\n-        fake_pool = MagicMock(name=\"CredentialPool\")\n-        primary = {\n-            \"model\": \"gpt-5.4\",\n-            \"api_key\": \"sk-test\",\n-            \"base_url\": None,\n-            \"provider\": \"openai-codex\",\n-            \"api_mode\": \"codex_responses\",\n-            \"command\": None,\n-            \"args\": [],\n-            \"credential_pool\": fake_pool,\n-        }\n-        routing_config = {\n-            \"enabled\": True,\n-            \"cheap_model\": \"openai/gpt-4.1-mini\",\n-            \"cheap_provider\": \"openrouter\",\n-            \"max_tokens\": 200,\n-            \"patterns\": [\"^(hi|hello|hey)\"],\n-        }\n-        # Force resolve_runtime_provider to fail so it falls back to primary\n-        monkeypatch.setattr(\n-            \"hermes_cli.runtime_provider.resolve_runtime_provider\",\n-            MagicMock(side_effect=RuntimeError(\"no credentials\")),\n-        )\n-        result = resolve_turn_route(\"hi\", routing_config, primary)\n-        assert result[\"runtime\"][\"credential_pool\"] is fake_pool\n+from unittest.mock import MagicMock, patch\n \n \n # ---------------------------------------------------------------------------\n-# 2 & 3. CLI and Gateway _resolve_turn_agent_config include credential_pool\n+# 1. CLI _resolve_turn_agent_config includes credential_pool\n # ---------------------------------------------------------------------------\n \n class TestCliTurnRoutePool:\n-    def test_resolve_turn_includes_pool(self, monkeypatch, tmp_path):\n-        \"\"\"CLI's _resolve_turn_agent_config must pass credential_pool to primary.\"\"\"\n-        from agent.smart_model_routing import resolve_turn_route\n-        captured = {}\n-\n-        def spy_resolve(user_message, routing_config, primary):\n-            captured[\"primary\"] = primary\n-            return resolve_turn_route(user_message, routing_config, primary)\n-\n-        monkeypatch.setattr(\n-            \"agent.smart_model_routing.resolve_turn_route\", spy_resolve\n-        )\n-\n-        # Build a minimal HermesCLI-like object with the method\n+    def test_resolve_turn_includes_pool(self):\n+        \"\"\"CLI's _resolve_turn_agent_config must pass credential_pool in runtime.\"\"\"\n+        fake_pool = MagicMock(name=\"FakePool\")\n         shell = SimpleNamespace(\n             model=\"gpt-5.4\",\n             api_key=\"[redacted:secret-assignment]\",","@@ -132,58 +28,46 @@ def spy_resolve(user_message, routing_config, primary):\n             api_mode=\"codex_responses\",\n             acp_command=None,\n             acp_args=[],\n-            _credential_pool=[redacted:secret-assignment]\"FakePool\"),\n-            _smart_model_routing={\"enabled\": False},\n+            _credential_pool=[redacted:secret-assignment]\n+            service_tier=None,\n         )\n \n-        # Import and bind the real method\n         from cli import HermesCLI\n         bound = HermesCLI._resolve_turn_agent_config.__get__(shell)\n-        bound(\"test message\")\n+        route = bound(\"test message\")\n \n-        assert \"credential_pool\" in captured[\"primary\"]\n-        assert captured[\"primary\"][\"credential_pool\"] is shell._credential_pool\n+        assert route[\"runtime\"][\"credential_pool\"] is fake_pool\n \n \n+# ---------------------------------------------------------------------------\n+# 2. Gateway _resolve_turn_agent_config includes credential_pool\n+# ---------------------------------------------------------------------------\n+\n class TestGatewayTurnRoutePool:\n-    def test_resolve_turn_includes_pool(self, monkeypatch):\n+    def test_resolve_turn_includes_pool(self):\n         \"\"\"Gateway's _resolve_turn_agent_config must pass credential_pool.\"\"\"\n-        from agent.smart_model_routing import resolve_turn_route\n-        captured = {}\n-\n-        def spy_resolve(user_message, routing_config, primary):\n-            captured[\"primary\"] = primary\n-            return resolve_turn_route(user_message, routing_config, primary)\n-\n-        monkeypatch.setattr(\n-            \"agent.smart_model_routing.resolve_turn_route\", spy_resolve\n-        )\n-\n         from gateway.run import GatewayRunner\n \n-        runner = SimpleNamespace(\n-            _smart_model_routing={\"enabled\": False},\n-        )\n-\n+        fake_pool = MagicMock(name=\"FakePool\")\n+        runner = SimpleNamespace(_service_tier=None)\n         runtime_kwargs = {\n-            \"api_key\": \"sk-test\",\n+            \"api_key\": \"***\",\n             \"base_url\": None,\n             \"provider\": \"openai-codex\",\n             \"api_mode\": \"codex_responses\",\n             \"command\": None,\n             \"args\": [],\n-            \"credential_pool\": MagicMock(name=\"FakePool\"),\n+            \"credential_pool\": fake_pool,\n         }\n \n         bound = GatewayRunner._resolve_turn_agent_config.__get__(runner)\n-        bound(\"test message\", \"gpt-5.4\", runtime_kwargs)\n+        route = bound(\"test message\", \"gpt-5.4\", runtime_kwargs)\n \n-        assert \"credential_pool\" in captured[\"primary\"]\n-        assert captured[\"primary\"][\"credential_pool\"] is runtime_kwargs[\"credential_pool\"]\n+        assert route[\"runtime\"][\"credential_pool\"] is fake_pool\n \n \n # ---------------------------------------------------------------------------\n-# 4 & 5. Eager fallback deferred/fires based on credential pool\n+# 3 & 4. Eager fallback deferred/fires based on credential pool\n # ---------------------------------------------------------------------------\n \n class TestEagerFallbackWithPool:","@@ -251,7 +135,7 @@ def test_eager_fallback_fires_when_pool_exhausted(self):\n \n \n # ---------------------------------------------------------------------------\n-# 6. Full 429 rotation cycle via _recover_with_credential_pool\n+# 5. Full 429 rotation cycle via _recover_with_credential_pool\n # ---------------------------------------------------------------------------\n \n class TestPoolRotationCycle:"]},{"file":"/dev/null","hunks":["@@ -1,61 +0,0 @@\n-from agent.smart_model_routing import choose_cheap_model_route\n-\n-\n-_BASE_CONFIG = {\n-    \"enabled\": True,\n-    \"cheap_model\": {\n-        \"provider\": \"openrouter\",\n-        \"model\": \"google/gemini-2.5-flash\",\n-    },\n-}\n-\n-\n-def test_returns_none_when_disabled():\n-    cfg = {**_BASE_CONFIG, \"enabled\": False}\n-    assert choose_cheap_model_route(\"what time is it in tokyo?\", cfg) is None\n-\n-\n-def test_routes_short_simple_prompt():\n-    result = choose_cheap_model_route(\"what time is it in tokyo?\", _BASE_CONFIG)\n-    assert result is not None\n-    assert result[\"provider\"] == \"openrouter\"\n-    assert result[\"model\"] == \"google/gemini-2.5-flash\"\n-    assert result[\"routing_reason\"] == \"simple_turn\"\n-\n-\n-def test_skips_long_prompt():\n-    prompt = \"please summarize this carefully \" * 20\n-    assert choose_cheap_model_route(prompt, _BASE_CONFIG) is None\n-\n-\n-def test_skips_code_like_prompt():\n-    prompt = \"debug this traceback: ```python\\nraise ValueError('bad')\\n```\"\n-    assert choose_cheap_model_route(prompt, _BASE_CONFIG) is None\n-\n-\n-def test_skips_tool_heavy_prompt_keywords():\n-    prompt = \"implement a patch for this docker error\"\n-    assert choose_cheap_model_route(prompt, _BASE_CONFIG) is None\n-\n-\n-def test_resolve_turn_route_falls_back_to_primary_when_route_runtime_cannot_be_resolved(monkeypatch):\n-    from agent.smart_model_routing import resolve_turn_route\n-\n-    monkeypatch.setattr(\n-        \"hermes_cli.runtime_provider.resolve_runtime_provider\",\n-        lambda **kwargs: (_ for _ in ()).throw(RuntimeError(\"bad route\")),\n-    )\n-    result = resolve_turn_route(\n-        \"what time is it in tokyo?\",\n-        _BASE_CONFIG,\n-        {\n-            \"model\": \"anthropic/claude-sonnet-4\",\n-            \"provider\": \"openrouter\",\n-            \"base_url\": \"https://openrouter.ai/api/v1\",\n-            \"api_mode\": \"chat_completions\",\n-            \"api_key\": \"sk-primary\",\n-        },\n-    )\n-    assert result[\"model\"] == \"anthropic/claude-sonnet-4\"\n-    assert result[\"runtime\"][\"provider\"] == \"openrouter\"\n-    assert result[\"label\"] is None"]},{"file":"tests/cli/test_cli_provider_resolution.py","hunks":["@@ -207,48 +207,11 @@ def test_cli_turn_routing_uses_primary_when_disabled(monkeypatch):\n     shell.api_mode = \"chat_completions\"\n     shell.base_url = \"https://openrouter.ai/api/v1\"\n     shell.api_key = \"[redacted:secret-assignment]\"\n-    shell._smart_model_routing = {\"enabled\": False}\n \n     result = shell._resolve_turn_agent_config(\"what time is it in tokyo?\")\n \n     assert result[\"model\"] == \"gpt-5\"\n     assert result[\"runtime\"][\"provider\"] == \"openrouter\"\n-    assert result[\"label\"] is None\n-\n-\n-def test_cli_turn_routing_uses_cheap_model_when_simple(monkeypatch):\n-    cli = _import_cli()\n-\n-    def _runtime_resolve(**kwargs):\n-        assert kwargs[\"requested\"] == \"zai\"\n-        return {\n-            \"provider\": \"zai\",\n-            \"api_mode\": \"chat_completions\",\n-            \"base_url\": \"https://open.z.ai/api/v1\",\n-            \"api_key\": \"cheap-key\",\n-            \"source\": \"env/config\",\n-        }\n-\n-    monkeypatch.setattr(\"hermes_cli.runtime_provider.resolve_runtime_provider\", _runtime_resolve)\n-\n-    shell = cli.HermesCLI(model=\"anthropic/claude-sonnet-4\", compact=True, max_turns=1)\n-    shell.provider = \"openrouter\"\n-    shell.api_mode = \"chat_completions\"\n-    shell.base_url = \"https://openrouter.ai/api/v1\"\n-    shell.api_key = \"[redacted:secret-assignment]\"\n-    shell._smart_model_routing = {\n-        \"enabled\": True,\n-        \"cheap_model\": {\"provider\": \"zai\", \"model\": \"glm-5-air\"},\n-        \"max_simple_chars\": 160,\n-        \"max_simple_words\": 28,\n-    }\n-\n-    result = shell._resolve_turn_agent_config(\"what time is it in tokyo?\")\n-\n-    assert result[\"model\"] == \"glm-5-air\"\n-    assert result[\"runtime\"][\"provider\"] == \"zai\"\n-    assert result[\"runtime\"][\"api_key\"] == \"cheap-key\"\n-    assert result[\"label\"] is not None\n \n \n def test_cli_prefers_config_provider_over_stale_env_override(monkeypatch):"]},{"file":"tests/cli/test_fast_command.py","hunks":["@@ -183,27 +183,10 @@ def test_turn_route_injects_overrides_without_provider_switch(self):\n             acp_command=None,\n             acp_args=[],\n             _credential_pool=None,\n-            _smart_model_routing={},\n             service_tier=\"priority\",\n         )\n \n-        original_runtime = {\n-            \"api_key\": \"***\",\n-            \"base_url\": \"https://openrouter.ai/api/v1\",\n-            \"provider\": \"openrouter\",\n-            \"api_mode\": \"chat_completions\",\n-            \"command\": None,\n-            \"args\": [],\n-            \"credential_pool\": None,\n-        }\n-\n-        with patch(\"agent.smart_model_routing.resolve_turn_route\", return_value={\n-            \"model\": \"gpt-5.4\",\n-            \"runtime\": dict(original_runtime),\n-            \"label\": None,\n-            \"signature\": (\"gpt-5.4\", \"openrouter\", \"https://openrouter.ai/api/v1\", \"chat_completions\", None, ()),\n-        }):\n-            route = cli_mod.HermesCLI._resolve_turn_agent_config(stub, \"hi\")\n+        route = cli_mod.HermesCLI._resolve_turn_agent_config(stub, \"hi\")\n \n         # Provider should NOT have changed\n         assert route[\"runtime\"][\"provider\"] == \"openrouter\"","@@ -222,26 +205,10 @@ def test_turn_route_keeps_primary_runtime_when_model_has_no_fast_backend(self):\n             acp_command=None,\n             acp_args=[],\n             _credential_pool=None,\n-            _smart_model_routing={},\n             service_tier=\"priority\",\n         )\n \n-        primary_route = {\n-            \"model\": \"gpt-5.3-codex\",\n-            \"runtime\": {\n-                \"api_key\": \"***\",\n-                \"base_url\": \"https://openrouter.ai/api/v1\",\n-                \"provider\": \"openrouter\",\n-                \"api_mode\": \"chat_completions\",\n-                \"command\": None,\n-                \"args\": [],\n-                \"credential_pool\": None,\n-            },\n-            \"label\": None,\n-            \"signature\": (\"gpt-5.3-codex\", \"openrouter\", \"https://openrouter.ai/api/v1\", \"chat_completions\", None, ()),\n-        }\n-        with patch(\"agent.smart_model_routing.resolve_turn_route\", return_value=primary_route):\n-            route = cli_mod.HermesCLI._resolve_turn_agent_config(stub, \"hi\")\n+        route = cli_mod.HermesCLI._resolve_turn_agent_config(stub, \"hi\")\n \n         assert route[\"runtime\"][\"provider\"] == \"openrouter\"\n         assert route.get(\"request_overrides\") is None","@@ -329,27 +296,10 @@ def test_turn_route_injects_speed_for_anthropic(self):\n             acp_command=None,\n             acp_args=[],\n             _credential_pool=None,\n-            _smart_model_routing={},\n             service_tier=\"priority\",\n         )\n \n-        original_runtime = {\n-            \"api_key\": \"***\",\n-            \"base_url\": \"https://api.anthropic.com\",\n-            \"provider\": \"anthropic\",\n-            \"api_mode\": \"anthropic_messages\",\n-            \"command\": None,\n-            \"args\": [],\n-            \"credential_pool\": None,\n-        }\n-\n-        with patch(\"agent.smart_model_routing.resolve_turn_route\", return_value={\n-            \"model\": \"claude-opus-4-6\",\n-            \"runtime\": dict(original_runtime),\n-            \"label\": None,\n-            \"signature\": (\"claude-opus-4-6\", \"anthropic\", \"https://api.anthropic.com\", \"anthropic_messages\", None, ()),\n-        }):\n-            route = cli_mod.HermesCLI._resolve_turn_agent_config(stub, \"hi\")\n+        route = cli_mod.HermesCLI._resolve_turn_agent_config(stub, \"hi\")\n \n         assert route[\"runtime\"][\"provider\"] == \"anthropic\"\n         assert route[\"request_overrides\"] == {\"speed\": \"fast\"}"]},{"file":"tests/cron/test_codex_execution_paths.py","hunks":["@@ -152,7 +152,6 @@ def test_gateway_run_agent_codex_path_handles_internal_401_refresh(monkeypatch):\n     runner._provider_routing = {}\n     runner._fallback_model = None\n     runner._running_agents = {}\n-    runner._smart_model_routing = {}\n     from unittest.mock import MagicMock, AsyncMock\n     runner.hooks = MagicMock()\n     runner.hooks.emit = AsyncMock()"]},{"file":"tests/gateway/test_discord_channel_prompts.py","hunks":["@@ -75,7 +75,6 @@ def _make_runner():\n     runner._service_tier = None\n     runner._provider_routing = {}\n     runner._fallback_model = None\n-    runner._smart_model_routing = {}\n     runner._running_agents = {}\n     runner._pending_model_notes = {}\n     runner._session_db = None"]},{"file":"tests/gateway/test_fast_command.py","hunks":["@@ -4,7 +4,7 @@\n import threading\n import types\n from types import SimpleNamespace\n-from unittest.mock import AsyncMock, patch\n+from unittest.mock import AsyncMock\n \n import pytest\n import yaml","@@ -53,7 +53,6 @@ def _make_runner():\n     runner._service_tier = None\n     runner._provider_routing = {}\n     runner._fallback_model = None\n-    runner._smart_model_routing = {}\n     runner._running_agents = {}\n     runner._pending_model_notes = {}\n     runner._session_db = None","@@ -97,13 +96,7 @@ def test_turn_route_injects_priority_processing_without_changing_runtime():\n         \"credential_pool\": None,\n     }\n \n-    with patch(\"agent.smart_model_routing.resolve_turn_route\", return_value={\n-        \"model\": \"gpt-5.4\",\n-        \"runtime\": dict(runtime_kwargs),\n-        \"label\": None,\n-        \"signature\": (\"gpt-5.4\", \"openrouter\", \"https://openrouter.ai/api/v1\", \"chat_completions\", None, ()),\n-    }):\n-        route = gateway_run.GatewayRunner._resolve_turn_agent_config(runner, \"hi\", \"gpt-5.4\", runtime_kwargs)\n+    route = gateway_run.GatewayRunner._resolve_turn_agent_config(runner, \"hi\", \"gpt-5.4\", runtime_kwargs)\n \n     assert route[\"runtime\"][\"provider\"] == \"openrouter\"\n     assert route[\"runtime\"][\"api_mode\"] == \"chat_completions\"","@@ -123,13 +116,7 @@ def test_turn_route_skips_priority_processing_for_unsupported_models():\n         \"credential_pool\": None,\n     }\n \n-    with patch(\"agent.smart_model_routing.resolve_turn_route\", return_value={\n-        \"model\": \"gpt-5.3-codex\",\n-        \"runtime\": dict(runtime_kwargs),\n-        \"label\": None,\n-        \"signature\": (\"gpt-5.3-codex\", \"openrouter\", \"https://openrouter.ai/api/v1\", \"chat_completions\", None, ()),\n-    }):\n-        route = gateway_run.GatewayRunner._resolve_turn_agent_config(runner, \"hi\", \"gpt-5.3-codex\", runtime_kwargs)\n+    route = gateway_run.GatewayRunner._resolve_turn_agent_config(runner, \"hi\", \"gpt-5.3-codex\", runtime_kwargs)\n \n     assert route[\"request_overrides\"] is None\n "]},{"file":"website/docs/integrations/providers.md","hunks":["@@ -1165,39 +1165,6 @@ Supported providers: `openrouter`, `nous`, `openai-codex`, `copilot`, `copilot-a\n Fallback is configured exclusively through `config.yaml` — there are no environment variables for it. For full details on when it triggers, supported providers, and how it interacts with auxiliary tasks and delegation, see [Fallback Providers](/docs/user-guide/features/fallback-providers).\n :::\n \n-## Smart Model Routing\n-\n-Optional cheap-vs-strong routing lets Hermes keep your main model for complex work while sending very short/simple turns to a cheaper model.\n-\n-```yaml\n-smart_model_routing:\n-  enabled: true\n-  max_simple_chars: 160\n-  max_simple_words: 28\n-  cheap_model:\n-    provider: openrouter\n-    model: google/gemini-2.5-flash\n-    # base_url: http://localhost:8000/v1  # optional custom endpoint\n-    # key_env: MY_CUSTOM_KEY              # optional env var name for that endpoint's API key\n-```\n-\n-How it works:\n-- If a turn is short, single-line, and does not look code/tool/debug heavy, Hermes may route it to `cheap_model`\n-- If the turn looks complex, Hermes stays on your primary model/provider\n-- If the cheap route cannot be resolved cleanly, Hermes falls back to the primary model automatically\n-\n-This is intentionally conservative. It is meant for quick, low-stakes turns like:\n-- short factual questions\n-- quick rewrites\n-- lightweight summaries\n-\n-It will avoid routing prompts that look like:\n-- coding/debugging work\n-- tool-heavy requests\n-- long or multi-line analysis asks\n-\n-Use this when you want lower latency or cost without fully changing your default model.\n-\n ---\n \n ## See Also\n"]}]},{"kind":"pr","number":16099,"title":"fix(run_agent): background review fork inherits parent's live runtime","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["run_agent.py"],"fix_hunks":[{"file":"run_agent.py","hunks":["@@ -3154,12 +3154,25 @@ def _run_review():\n                 with open(os.devnull, \"w\") as _devnull, \\\n                      contextlib.redirect_stdout(_devnull), \\\n                      contextlib.redirect_stderr(_devnull):\n+                    # Inherit the parent agent's live runtime (provider, model,\n+                    # base_url, api_key, api_mode) so the fork uses the exact\n+                    # same credentials the main turn is using.  Without this,\n+                    # AIAgent.__init__ re-runs auto-resolution from env vars,\n+                    # which fails for OAuth-only providers, session-scoped\n+                    # creds, or credential-pool setups where the resolver can't\n+                    # reconstruct auth from scratch -- producing the spurious\n+                    # \"No LLM provider configured\" warning at end of turn.\n+                    _parent_runtime = self._current_main_runtime()\n                     review_agent = AIAgent(\n                         model=self.model,\n                         max_iterations=8,\n                         quiet_mode=True,\n                         platform=self.platform,\n                         provider=self.provider,\n+                        api_mode=_parent_runtime.get(\"api_mode\") or None,\n+                        base_url=_parent_runtime.get(\"base_url\") or None,\n+                        api_key=[redacted:secret-assignment]\"api_key\") or None,\n+                        credential_pool=[redacted:secret-assignment] \"_credential_pool\", None),\n                         parent_session_id=self.session_id,\n                     )\n                     review_agent._memory_write_origin = \"background_review\"\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}