{"repo":"NousResearch/hermes-agent","public_id":"49095","display_label":"Bedrock delegation API mode routing fixes","summary":"Multiple fixes targeting how the delegation system routes Bedrock provider requests, specifically around selecting the correct API mode (converse) and target model for Bedrock subagents. Includes a concern about silent fallback to OpenRouter when Bedrock delegation provider is misconfigured.","subsystem":"agent","counts":{"members":5,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":3},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":51724},"resolution":{"state":"resolved","by":"pr-51724"},"freshness":{"state":"verified","last_verified_at":"2026-06-24T06:49:37+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.4},"fix_edges":[{"issue_number":49095,"pr_number":51724,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":49095,"title":"fix(delegation): bedrock delegation.provider silently falls back to openrouter/free","url":null,"state":"closed"},{"kind":"pr","number":49117,"title":"fix(delegation): force bedrock_converse api_mode for bedrock delegation provider","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#51724","number":51724},{"kind":"pr","value":"#49095","number":49095}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":49155,"title":"fix(delegation): use target_model for bedrock api_mode routing (#49095)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#51724","number":51724}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":49375,"title":"fix(delegate): route bedrock subagents through converse mode","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#49117","number":49117}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":51724,"title":"fix(delegation): use target_model for bedrock api_mode routing (#49095)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/runtime_provider.py","tests/hermes_cli/test_runtime_provider_resolution.py"],"fix_hunks":[{"file":"hermes_cli/runtime_provider.py","hunks":["@@ -1750,7 +1750,7 @@ def resolve_runtime_provider(\n         # Dual-path routing: Claude models use AnthropicBedrock SDK for full\n         # feature parity (prompt caching, thinking budgets, adaptive thinking).\n         # Non-Claude models use the Converse API for multi-model support.\n-        _current_model = str(model_cfg.get(\"default\") or \"\").strip()\n+        _current_model = str(target_model or model_cfg.get(\"default\") or \"\").strip()\n         if is_anthropic_bedrock_model(_current_model):\n             # Claude on Bedrock → AnthropicBedrock SDK → anthropic_messages path\n             runtime = {"]},{"file":"tests/hermes_cli/test_runtime_provider_resolution.py","hunks":["@@ -2835,3 +2835,61 @@ def test_host_derived_key_helper_basic_cases():\n     for k in (\"DEEPSEEK_API_KEY\", \"GROQ_API_KEY\", \"MISTRAL_API_KEY\",\n               \"OPENAI_API_KEY\", \"OPENROUTER_API_KEY\"):\n         _os.environ.pop(k, None)\n+\n+\n+def _patch_bedrock(monkeypatch, config_default=\"\"):\n+    \"\"\"Stub the bedrock_adapter helpers resolve_runtime_provider imports.\n+\n+    Leaves the real ``is_anthropic_bedrock_model`` in place so the dual-path\n+    routing decision is exercised for real. ``config_default`` is the persisted\n+    ``model.default`` — kept non-Claude here to prove ``target_model`` (not the\n+    stale config default) drives the api_mode decision.\n+    \"\"\"\n+    import agent.bedrock_adapter as ba\n+\n+    monkeypatch.setattr(rp, \"resolve_provider\", lambda *a, **k: \"bedrock\")\n+    monkeypatch.setattr(rp, \"_get_model_config\", lambda: {\"default\": config_default})\n+    monkeypatch.setattr(rp, \"load_config\", lambda: {\"bedrock\": {}})\n+    monkeypatch.setattr(ba, \"has_aws_credentials\", lambda: True)\n+    monkeypatch.setattr(ba, \"resolve_aws_auth_env_var\", lambda: \"AWS_PROFILE\")\n+    monkeypatch.setattr(ba, \"resolve_bedrock_region\", lambda: \"eu-north-1\")\n+\n+\n+def test_resolve_runtime_provider_bedrock_claude_target_model_uses_anthropic_messages(monkeypatch):\n+    \"\"\"Claude-on-Bedrock delegation must route through the AnthropicBedrock SDK.\n+\n+    Regression for #49095: the bedrock branch derived api_mode from the stale\n+    persisted ``model.default`` instead of ``target_model``. When delegation\n+    targets a Claude model but the parent's default is non-Claude, the wrong\n+    branch (Converse) was picked, and the child silently fell back to\n+    openrouter/free. ``target_model`` must win so Claude keeps the\n+    anthropic_messages path (prompt caching, thinking budgets).\n+    \"\"\"\n+    _patch_bedrock(monkeypatch, config_default=\"amazon.nova-pro-v1:0\")\n+\n+    resolved = rp.resolve_runtime_provider(\n+        requested=\"bedrock\",\n+        target_model=\"global.anthropic.claude-sonnet-4-6\",\n+    )\n+\n+    assert resolved[\"provider\"] == \"bedrock\"\n+    assert resolved[\"api_mode\"] == \"anthropic_messages\"\n+    assert resolved.get(\"bedrock_anthropic\") is True\n+\n+\n+def test_resolve_runtime_provider_bedrock_nonclaude_target_model_uses_converse(monkeypatch):\n+    \"\"\"Non-Claude Bedrock target stays on the Converse API path.\n+\n+    Confirms the target_model fix doesn't over-correct: a Nova target must NOT\n+    be forced onto anthropic_messages even when the persisted default is Claude.\n+    \"\"\"\n+    _patch_bedrock(monkeypatch, config_default=\"global.anthropic.claude-sonnet-4-6\")\n+\n+    resolved = rp.resolve_runtime_provider(\n+        requested=\"bedrock\",\n+        target_model=\"amazon.nova-pro-v1:0\",\n+    )\n+\n+    assert resolved[\"provider\"] == \"bedrock\"\n+    assert resolved[\"api_mode\"] == \"bedrock_converse\"\n+    assert resolved.get(\"bedrock_anthropic\") is not True\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}