{"repo":"NousResearch/hermes-agent","public_id":"782","display_label":"max_tokens config propagation bugs and fixes","summary":"Multiple bug reports and fix attempts addressing max_tokens values from config.yaml being silently ignored and not passed through to AIAgent in CLI, gateway, and custom provider modes. Also includes related gemma4 context-length entries and a Kimi provider token parameter fix.","subsystem":"agent","counts":{"members":35,"open_issues":3,"open_issues_distinct":3,"open_prs":2,"closed_issues":10,"merged_prs":2,"attempt_prs":20},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":39864},"resolution":{"state":"partially_resolved","by":"pr-39864,pr-41694"},"freshness":{"state":"verified","last_verified_at":"2026-06-08T04:50:26+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.04907563025210083},"fix_edges":[{"issue_number":20741,"pr_number":39864,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":39281,"pr_number":41694,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":782,"title":"feat: support HERMES_MAX_TOKENS env var / config.yaml max_tokens for custom providers","url":null,"state":"closed"},{"kind":"issue","number":4404,"title":"[Bug]: model.max_tokens in config.yaml has no effect — setting is never passed to AIAgent","url":null,"state":"closed"},{"kind":"issue","number":10917,"title":"[Bug]: model.max_tokens in config.yaml ignored for CLI/WebUI — thinking blocks and long responses silently truncated with local models","url":null,"state":"closed"},{"kind":"issue","number":11443,"title":"model.max_tokens from config.yaml is ignored in both Gateway and CLI modes","url":null,"state":"closed"},{"kind":"issue","number":15037,"title":"feat(config): support per-model max_tokens in custom_providers config","url":null,"state":"closed"},{"kind":"issue","number":20004,"title":"max_tokens config from custom_providers is not passed to AIAgent","url":null,"state":"open"},{"kind":"issue","number":20741,"title":"Bug: `max_tokens` from config.yaml is silently ignored — never propagated to AIAgent, causing output truncation on Ollama Cloud / zai / custom endpoints","url":null,"state":"closed"},{"kind":"issue","number":28046,"title":"Bug: max_tokens not read from custom_providers per-model config, always defaults to 4096","url":null,"state":"open"},{"kind":"issue","number":28782,"title":"feat: custom_providers should support per-provider max_tokens override","url":null,"state":"closed"},{"kind":"issue","number":39281,"title":"[Bug]: Hermes fails to work using gemma4 with ollama backend","url":null,"state":"closed"},{"kind":"issue","number":45924,"title":"[Bug]: hermes + gemma 4 12b","url":null,"state":"closed"},{"kind":"issue","number":46833,"title":"[Setup]: Thinking models on `custom`/Ollama (OpenAI-compatible) providers always truncate, and Hermes requests the full GGUF context window causing VRAM blowups","url":null,"state":"open"},{"kind":"issue","number":49297,"title":"[Bug]: Hermes fails to work using gemma4 with ollama backend","url":null,"state":"closed"},{"kind":"pr","number":787,"title":"fix: support max_tokens config for custom providers","url":null,"state":"closed","close_reason":{"category":"stale_abandoned","implication":"active"},"merged":false},{"kind":"pr","number":788,"title":"fix: support max_tokens config for custom providers","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":5001,"title":"fix: wire model.max_tokens from config to Agent","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":10924,"title":"fix(runtime): propagate configured model max tokens","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":10992,"title":"fix(cli): pass model.max_tokens into CLI and gateway agents","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#10924","number":10924}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":19991,"title":"fix: properly pass model.max_tokens config to AIAgent in gateway","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"14275d7baa1a48844e58671f63ab49c4823c7cc0","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":20121,"title":"fix(gateway): honor custom_providers max_tokens when constructing AIAgent","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#20149","number":20149}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":20149,"title":"fix(gateway): honor max_tokens from custom_providers / providers entries (#20004)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#20121","number":20121},{"kind":"pr","value":"#20004","number":20004}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":20769,"title":"fix(config): propagate max_tokens from config.yaml to AI transport (#20741)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#39864","number":39864},{"kind":"pr","value":"#20741","number":20741},{"kind":"pr","value":"#20741","number":20741},{"kind":"pr","value":"#39864","number":39864}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":20804,"title":"fix(gateway): propagate max_tokens from config.yaml to AIAgent","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"cf786593c","number":0},{"kind":"commit","value":"1c909e75e","number":0},{"kind":"pr","value":"#39864","number":39864}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":28142,"title":"fix(agent_init): read max_tokens from custom_providers per-model config","url":null,"state":"closed","close_reason":{"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":28154,"title":"fix(agent): honor custom provider max_tokens","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#28142","number":28142},{"kind":"pr","value":"#28046","number":28046},{"kind":"pr","value":"#28142","number":28142},{"kind":"pr","value":"#20004","number":20004},{"kind":"pr","value":"#18445","number":18445},{"kind":"pr","value":"#20705","number":20705}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":28786,"title":"feat: per-provider max_tokens via custom_providers models.<model>.max_tokens","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"14275d7baa1a48844e58671f63ab49c4823c7cc0","number":0}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":28988,"title":"fix: read max_tokens from custom_providers per-model config","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#28995","number":28995}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":28995,"title":"feat: Config-Runtime Contract Registry (Phase 1) + fix #28046 + fix #28863","url":null,"state":"open","close_reason":{"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":35518,"title":"feat(custom_providers): per-model max_tokens with switch/fallback re-resolution","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":39624,"title":"fix(ollama): set default_max_tokens=4096 for custom/Ollama provider","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"09ec26c66","number":0},{"kind":"pr","value":"#41694","number":41694}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":39864,"title":"fix(gateway): propagate max_tokens from config.yaml to AIAgent (#20741)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cli.py","gateway/run.py","hermes_cli/runtime_provider.py","scripts/release.py","tests/gateway/test_max_tokens_propagation.py"],"fix_hunks":[{"file":"cli.py","hunks":["@@ -3194,6 +3194,18 @@ def __init__(\n         _config_model = (_model_config.get(\"default\") or _model_config.get(\"model\") or \"\") if isinstance(_model_config, dict) else (_model_config or \"\")\n         _DEFAULT_CONFIG_MODEL = \"\"\n         self.model = model or _config_model or _DEFAULT_CONFIG_MODEL\n+        # Read max_tokens from config (env var override: HERMES_MAX_TOKENS)\n+        _env_mt = os.environ.get(\"HERMES_MAX_TOKENS\")\n+        if _env_mt:\n+            try:\n+                self.max_tokens = [redacted:secret-assignment]\n+            except (ValueError, TypeError):\n+                self.max_tokens = None\n+        elif isinstance(_model_config, dict):\n+            _mt = _model_config.get(\"max_tokens\")\n+            self.max_tokens = _mt if isinstance(_mt, int) else None\n+        else:\n+            self.max_tokens = None\n         # Auto-detect model from local server if still on default\n         if self.model == _DEFAULT_CONFIG_MODEL:\n             _base_url = (_model_config.get(\"base_url\") or \"\") if isinstance(_model_config, dict) else \"\"","@@ -5168,6 +5180,7 @@ def _init_agent(self, *, model_override: str = None, runtime_override: dict = No\n                 acp_command=runtime.get(\"command\"),\n                 acp_args=runtime.get(\"args\"),\n                 credential_pool=[redacted:secret-assignment]\"credential_pool\"),\n+                max_tokens=[redacted:secret-assignment]\n                 max_iterations=self.max_turns,\n                 enabled_toolsets=self.enabled_toolsets,\n                 disabled_toolsets=self.disabled_toolsets,","@@ -9284,6 +9297,7 @@ def run_background():\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+                    max_tokens=[redacted:secret-assignment]\"runtime\"].get(\"max_tokens\"),\n                     max_iterations=self.max_turns,\n                     enabled_toolsets=self.enabled_toolsets,\n                     quiet_mode=True,"]},{"file":"gateway/run.py","hunks":["@@ -1179,6 +1179,7 @@ def _resolve_runtime_agent_kwargs() -> dict:\n     from hermes_cli.runtime_provider import (\n         resolve_runtime_provider,\n         format_runtime_provider_error,\n+        _get_model_config,\n     )\n     from hermes_cli.auth import AuthError, is_rate_limited_auth_error\n ","@@ -1200,6 +1201,26 @@ def _resolve_runtime_agent_kwargs() -> dict:\n     except Exception as exc:\n         raise RuntimeError(format_runtime_provider_error(exc)) from exc\n \n+    model_cfg = _get_model_config()\n+    max_tokens = None\n+    _env_mt = os.environ.get(\"HERMES_MAX_TOKENS\")\n+    if _env_mt:\n+        try:\n+            max_tokens = [redacted:secret-assignment]\n+        except (ValueError, TypeError):\n+            max_tokens = None\n+    elif isinstance(model_cfg, dict):\n+        mt = model_cfg.get(\"max_tokens\")\n+        if isinstance(mt, int):\n+            max_tokens = mt\n+    # Fall back to a per-provider output cap (custom_providers max_output_tokens)\n+    # only when the documented global model.max_tokens isn't set, so the global\n+    # key always wins.\n+    if max_tokens is None:\n+        _runtime_mot = runtime.get(\"max_output_tokens\")\n+        if isinstance(_runtime_mot, int) and _runtime_mot > 0:\n+            max_tokens = [redacted:secret-assignment]\n+\n     return {\n         \"api_key\": runtime.get(\"api_key\"),\n         \"base_url\": runtime.get(\"base_url\"),","@@ -1208,6 +1229,7 @@ def _resolve_runtime_agent_kwargs() -> dict:\n         \"command\": runtime.get(\"command\"),\n         \"args\": list(runtime.get(\"args\") or []),\n         \"credential_pool\": runtime.get(\"credential_pool\"),\n+        \"max_tokens\": max_tokens,\n     }\n \n ","@@ -2596,6 +2618,7 @@ def _resolve_session_agent_runtime(\n                 \"api_key\": override.get(\"api_key\"),\n                 \"base_url\": override.get(\"base_url\"),\n                 \"api_mode\": override.get(\"api_mode\"),\n+                \"max_tokens\": override.get(\"max_tokens\"),\n             }\n             if override_runtime.get(\"api_key\"):\n                 logger.debug(","@@ -2693,6 +2716,7 @@ def _resolve_turn_agent_config(self, user_message: str, model: str, runtime_kwar\n             \"command\": runtime_kwargs.get(\"command\"),\n             \"args\": list(runtime_kwargs.get(\"args\") or []),\n             \"credential_pool\": runtime_kwargs.get(\"credential_pool\"),\n+            \"max_tokens\": runtime_kwargs.get(\"max_tokens\"),\n         }\n         route = {\n             \"model\": model,"]},{"file":"hermes_cli/runtime_provider.py","hunks":["@@ -474,6 +474,21 @@ def _try_resolve_from_custom_pool(\n         return None\n \n \n+def _lift_max_output_tokens(entry: Dict[str, Any], result: Dict[str, Any]) -> None:\n+    \"\"\"Propagate a per-provider output cap onto the resolved runtime dict.\n+\n+    Accepts ``max_output_tokens`` or ``max_tokens`` on a ``custom_providers``\n+    entry so a provider block can pin its own output limit. Gateway and CLI\n+    map this onto ``AIAgent.max_tokens`` only when the top-level\n+    ``model.max_tokens`` isn't set, so the documented global key still wins.\n+    \"\"\"\n+    for _k in (\"max_output_tokens\", \"max_tokens\"):\n+        _v = entry.get(_k)\n+        if isinstance(_v, int) and _v > 0:\n+            result[\"max_output_tokens\"] = _v\n+            return\n+\n+\n def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, Any]]:\n     requested_norm = _normalize_custom_provider_name(requested_provider or \"\")\n     if not requested_norm or requested_norm == \"custom\":","@@ -541,6 +556,7 @@ def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, An\n                     api_mode = _parse_api_mode(entry.get(\"api_mode\") or entry.get(\"transport\"))\n                     if api_mode:\n                         result[\"api_mode\"] = api_mode\n+                    _lift_max_output_tokens(entry, result)\n                     return result\n             # Also check the 'name' field if present\n             display_name = entry.get(\"name\", \"\")","@@ -562,6 +578,7 @@ def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, An\n                         api_mode = _parse_api_mode(entry.get(\"api_mode\") or entry.get(\"transport\"))\n                         if api_mode:\n                             result[\"api_mode\"] = api_mode\n+                        _lift_max_output_tokens(entry, result)\n                         return result\n \n     # Fall back to custom_providers: list (legacy format)","@@ -611,6 +628,7 @@ def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, An\n         model_name = str(entry.get(\"model\", \"\") or \"\").strip()\n         if model_name:\n             result[\"model\"] = model_name\n+        _lift_max_output_tokens(entry, result)\n         return result\n \n     return None","@@ -699,6 +717,8 @@ def _resolve_named_custom_runtime(\n         model_name = custom_provider.get(\"model\")\n         if model_name:\n             pool_result[\"model\"] = model_name\n+        if isinstance(custom_provider.get(\"max_output_tokens\"), int):\n+            pool_result[\"max_output_tokens\"] = custom_provider[\"max_output_tokens\"]\n         request_overrides = _custom_provider_request_overrides(custom_provider)\n         if request_overrides:\n             pool_result[\"request_overrides\"] = {","@@ -736,6 +756,8 @@ def _resolve_named_custom_runtime(\n     # provider name differs from the actual model string the API expects.\n     if custom_provider.get(\"model\"):\n         result[\"model\"] = custom_provider[\"model\"]\n+    if isinstance(custom_provider.get(\"max_output_tokens\"), int):\n+        result[\"max_output_tokens\"] = custom_provider[\"max_output_tokens\"]\n     request_overrides = _custom_provider_request_overrides(custom_provider)\n     if request_overrides:\n         result[\"request_overrides\"] = request_overrides"]},{"file":"scripts/release.py","hunks":["@@ -45,6 +45,7 @@\n \n # Auto-extracted from noreply emails + manual overrides\n AUTHOR_MAP = {\n+    \"[redacted:email]\": \"ViewWay\",\n     \"[redacted:email]\": \"harjothkhara\",\n     \"[redacted:email]\": \"HeLLGURD\",\n     \"[redacted:email]\": \"dirtyren\","]},{"file":"tests/gateway/test_max_tokens_propagation.py","hunks":["@@ -0,0 +1,178 @@\n+\"\"\"Regression tests for max_tokens propagation from config.yaml to AIAgent.\n+\n+Covers #20741: `model.max_tokens` was silently dropped before reaching the\n+gateway-spawned agent, so providers without a hardcoded default (OpenRouter\n+free models, Ollama Cloud, custom OpenAI-compatible endpoints) truncated long\n+generations with `finish_reason=\"length\"`.\n+\n+Precedence verified here:\n+    HERMES_MAX_TOKENS env  >  model.max_tokens  >  per-provider\n+    max_output_tokens  >  None\n+\"\"\"\n+\n+import importlib\n+import os\n+import sys\n+import textwrap\n+\n+import pytest\n+\n+\n[redacted:email]\n+def isolated_home(tmp_path, monkeypatch):\n+    \"\"\"Isolated HERMES_HOME with a writable config.yaml and a clean module cache.\n+\n+    These tests deliberately re-import ``hermes_cli`` / ``gateway`` so each\n+    config write is read fresh. To avoid leaking that purge into sibling test\n+    files in the same worker (which breaks their import-time mocks), we snapshot\n+    the affected modules and restore them on teardown.\n+    \"\"\"\n+    hermes_home = tmp_path / \".hermes\"\n+    hermes_home.mkdir()\n+    monkeypatch.setenv(\"HERMES_HOME\", str(hermes_home))\n+    monkeypatch.delenv(\"HERMES_MAX_TOKENS\", raising=False)\n+\n+    _saved = {\n+        k: v\n+        for k, v in sys.modules.items()\n+        if k.startswith((\"hermes_cli\", \"gateway\"))\n+    }\n+\n+    def write_cfg(body: str) -> None:\n+        (hermes_home / \"config.yaml\").write_text(textwrap.dedent(body))\n+\n+    def fresh_gateway():\n+        for mod in list(sys.modules.keys()):\n+            if mod.startswith((\"hermes_cli\", \"gateway\")):\n+                del sys.modules[mod]\n+        return importlib.import_module(\"gateway.run\")\n+\n+    try:\n+        yield write_cfg, fresh_gateway\n+    finally:\n+        # Drop anything we (re)imported, then restore the pre-test snapshot so\n+        # the next test file sees the module objects it was loaded with.\n+        for k in list(sys.modules.keys()):\n+            if k.startswith((\"hermes_cli\", \"gateway\")):\n+                del sys.modules[k]\n+        sys.modules.update(_saved)\n+\n+\n+def test_top_level_max_tokens_propagates(isolated_home):\n+    \"\"\"model.max_tokens is read into the gateway runtime kwargs (#20741).\"\"\"\n+    write_cfg, fresh_gateway = isolated_home\n+    write_cfg(\n+        \"\"\"\n+        model:\n+          default: glm-5.1\n+          provider: openrouter\n+          max_tokens: 16384\n+        \"\"\"\n+    )\n+    grun = fresh_gateway()\n+    kw = grun._resolve_runtime_agent_kwargs()\n+    assert kw[\"max_tokens\"] == 16384\n+\n+\n+def test_per_provider_max_output_tokens_fallback(isolated_home):\n+    \"\"\"A custom provider's max_output_tokens fills in when no global is set.\"\"\"\n+    write_cfg, fresh_gateway = isolated_home\n+    write_cfg(\n+        \"\"\"\n+        model:\n+          default: glm-5.1\n+          provider: mylocal\n+        providers:\n+          mylocal:\n+            api: http://localhost:11434/v1\n+            api_key: [redacted:secret-assignment]\n+            default_model: glm-5.1\n+            max_output_tokens: 12000\n+        \"\"\"\n+    )\n+    grun = fresh_gateway()\n+    kw = grun._resolve_runtime_agent_kwargs()\n+    assert kw[\"max_tokens\"] == 12000\n+\n+\n+def test_global_max_tokens_beats_per_provider(isolated_home):\n+    \"\"\"The documented global model.max_tokens wins over a provider cap.\"\"\"\n+    write_cfg, fresh_gateway = isolated_home\n+    write_cfg(\n+        \"\"\"\n+        model:\n+          default: glm-5.1\n+          provider: mylocal\n+          max_tokens: 16384\n+        providers:\n+          mylocal:\n+            api: http://localhost:11434/v1\n+            api_key: [redacted:secret-assignment]\n+            default_model: glm-5.1\n+            max_output_tokens: 12000\n+        \"\"\"\n+    )\n+    grun = fresh_gateway()\n+    kw = grun._resolve_runtime_agent_kwargs()\n+    assert kw[\"max_tokens\"] == 16384\n+\n+\n+def test_env_override_beats_everything(isolated_home, monkeypatch):\n+    \"\"\"HERMES_MAX_TOKENS is the internal override mechanism (highest priority).\"\"\"\n+    write_cfg, fresh_gateway = isolated_home\n+    monkeypatch.setenv(\"HERMES_MAX_TOKENS\", \"2048\")\n+    write_cfg(\n+        \"\"\"\n+        model:\n+          default: glm-5.1\n+          provider: mylocal\n+          max_tokens: 16384\n+        providers:\n+          mylocal:\n+            api: http://localhost:11434/v1\n+            api_key: [redacted:secret-assignment]\n+            default_model: glm-5.1\n+            max_output_tokens: 12000\n+        \"\"\"\n+    )\n+    grun = fresh_gateway()\n+    kw = grun._resolve_runtime_agent_kwargs()\n+    assert kw[\"max_tokens\"] == 2048\n+\n+\n+def test_no_config_leaves_max_tokens_none(isolated_home):\n+    \"\"\"No cap configured anywhere -> max_tokens is None (no spurious limit).\"\"\"\n+    write_cfg, fresh_gateway = isolated_home\n+    write_cfg(\n+        \"\"\"\n+        model:\n+          default: glm-5.1\n+          provider: openrouter\n+        \"\"\"\n+    )\n+    grun = fresh_gateway()\n+    kw = grun._resolve_runtime_agent_kwargs()\n+    assert kw[\"max_tokens\"] is None\n+\n+\n+def test_lift_helper_accepts_alias_and_rejects_garbage(isolated_home):\n+    \"\"\"_lift_max_output_tokens accepts both keys, ignores non-positive/non-int.\"\"\"\n+    write_cfg, _ = isolated_home\n+    write_cfg(\"model:\\n  provider: openrouter\\n\")\n+    for mod in list(sys.modules.keys()):\n+        if mod.startswith(\"hermes_cli\"):\n+            del sys.modules[mod]\n+    rp = importlib.import_module(\"hermes_cli.runtime_provider\")\n+\n+    out: dict = {}\n+    rp._lift_max_output_tokens({\"max_output_tokens\": 8192}, out)\n+    assert out[\"max_output_tokens\"] == 8192\n+\n+    out = {}\n+    rp._lift_max_output_tokens({\"max_tokens\": 4096}, out)\n+    assert out[\"max_output_tokens\"] == 4096\n+\n+    for bad in ({\"max_output_tokens\": 0}, {\"max_output_tokens\": \"x\"}, {}):\n+        out = {}\n+        rp._lift_max_output_tokens(bad, out)\n+        assert \"max_output_tokens\" not in out\n"]}]},{"kind":"pr","number":41694,"title":"fix(ollama): set default_max_tokens=65536 for custom/Ollama provider","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["plugins/model-providers/custom/__init__.py","scripts/release.py"]},{"kind":"pr","number":45930,"title":"Fix false local Ollama length truncation","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"00a8252b7","number":0}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":57548,"title":"fix(agent): honor custom_providers per-model max_tokens at startup","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#57704","number":57704},{"kind":"pr","value":"#31829","number":31829},{"kind":"pr","value":"#35327","number":35327},{"kind":"pr","value":"#57548","number":57548},{"kind":"pr","value":"#28154","number":28154},{"kind":"pr","value":"#57713","number":57713},{"kind":"pr","value":"#31365","number":31365},{"kind":"pr","value":"#57536","number":57536},{"kind":"pr","value":"#57248","number":57248},{"kind":"pr","value":"#57696","number":57696},{"kind":"pr","value":"#47821","number":47821}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":64426,"title":"fix(providers): resolve custom:<name> slug to custom profile for wire path (#39281)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"c88723d34a130c82a7eb7b0adb749267643d54f1","number":0},{"kind":"pr","value":"#39281","number":39281},{"kind":"pr","value":"#41694","number":41694}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}