{"repo":"NousResearch/hermes-agent","public_id":"4661","display_label":"Temperature handling for Kimi and gpt-5.5 auxiliary/vision calls","summary":"Most members concern temperature values sent to Kimi/Moonshot models (kimi-for-coding, kimi-k2.5, kimi-k2.7-code) in auxiliary and vision calls, including endpoint-specific overrides and honoring config. A smaller part covers omitting temperature for the OpenAI gpt-5.5 family, so the cluster is mostly one theme with an adjacent provider-specific item.","subsystem":"agent","counts":{"members":21,"open_issues":1,"open_issues_distinct":1,"open_prs":1,"closed_issues":5,"merged_prs":5,"attempt_prs":10},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":12773,"merged_at":"2026-04-20T01:54:35+00:00"},"resolution":{"state":"partially_resolved","by":"pr-12773,pr-12861,pr-12862,pr-115850"},"freshness":{"state":"verified","last_verified_at":"2026-09-19T18:10:59+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.0899047619047619},"fix_edges":[{"issue_number":4661,"pr_number":12862,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-20T07:32:10+00:00"},{"issue_number":9125,"pr_number":11834,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-04-17T22:49:14+00:00"},{"issue_number":9125,"pr_number":12773,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-20T01:54:35+00:00"},{"issue_number":11764,"pr_number":12773,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-04-20T01:54:35+00:00"},{"issue_number":12745,"pr_number":12773,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-04-20T01:54:35+00:00"},{"issue_number":12745,"pr_number":12861,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-20T07:32:07+00:00"},{"issue_number":51083,"pr_number":115850,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-09-19T18:10:59+00:00"}],"members":[{"kind":"issue","number":4661,"title":"[Bug]: kimi-coding vision provider fails with 400 error on second call due to hardcoded temperature","url":null,"state":"closed"},{"kind":"issue","number":9125,"title":"[Bug]: kimi-coding auxiliary calls fail with 400 \"invalid temperature\" when provider=auto","url":null,"state":"closed"},{"kind":"issue","number":12404,"title":"kimi-k2.5 fails with invalid temperature error","url":null,"state":"closed"},{"kind":"issue","number":12745,"title":"[Bug]: kimi-k2.5 incorrectly classified as instant mode (temperature=0.6) instead of thinking mode (temperature=1.0)","url":null,"state":"closed"},{"kind":"issue","number":51083,"title":"[Bug]: auxiliary vision sends unsupported temperature to OpenAI gpt-5.5","url":null,"state":"closed"},{"kind":"issue","number":53455,"title":"vision_analyze tool passes invalid temperature to kimi-k2.7-code model","url":null,"state":"open"},{"kind":"pr","number":4967,"title":"fix: vision tool respects auxiliary.vision.temperature from config","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"088bf9057fc879a49dd0d9fc1e2a0047a8f2aa09","number":0},{"kind":"pr","value":"#4661","number":4661}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":9191,"title":"fix(auxiliary): omit temperature for auto-routed kimi calls","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#9125","number":9125}],"category":"stale_abandoned","implication":"active"},"merged":false},{"kind":"pr","number":11825,"title":"fix(kimi): force kimi-for-coding temperature to 0.6","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#11834","number":11834}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":11834,"title":"fix(kimi): force kimi-for-coding temperature to 0.6","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/auxiliary_client.py","run_agent.py","tests/agent/test_auxiliary_client.py"],"fix_hunks":[{"file":"agent/auxiliary_client.py","hunks":["@@ -94,6 +94,17 @@ def _normalize_aux_provider(provider: Optional[str]) -> str:\n         return \"custom\"\n     return _PROVIDER_ALIASES.get(normalized, normalized)\n \n+\n+_FIXED_TEMPERATURE_MODELS: Dict[str, float] = {\n+    \"kimi-for-coding\": 0.6,\n+}\n+\n+\n+def _fixed_temperature_for_model(model: Optional[str]) -> Optional[float]:\n+    \"\"\"Return a required temperature override for models with strict contracts.\"\"\"\n+    normalized = (model or \"\").strip().lower()\n+    return _FIXED_TEMPERATURE_MODELS.get(normalized)\n+\n # Default auxiliary models for direct API-key providers (cheap/fast for side tasks)\n _API_KEY_PROVIDER_AUX_MODELS: [redacted:secret-assignment] str] = {\n     \"gemini\": \"gemini-3-flash-preview\",","@@ -2293,6 +2304,10 @@ def _build_call_kwargs(\n         \"timeout\": timeout,\n     }\n \n+    fixed_temperature = _fixed_temperature_for_model(model)\n+    if fixed_temperature is not None:\n+        temperature = fixed_temperature\n+\n     # Opus 4.7+ rejects any non-default temperature/top_p/top_k — silently\n     # drop here so auxiliary callers that hardcode temperature (e.g. 0.3 on\n     # flush_memories, 0 on structured-JSON extraction) don't 400 the moment"]},{"file":"run_agent.py","hunks":["@@ -7207,14 +7207,22 @@ def flush_memories(self, messages: list = None, min_turns: int = None):\n \n             # Use auxiliary client for the flush call when available --\n             # it's cheaper and avoids Codex Responses API incompatibility.\n-            from agent.auxiliary_client import call_llm as _call_llm\n+            from agent.auxiliary_client import (\n+                call_llm as _call_llm,\n+                _fixed_temperature_for_model,\n+            )\n             _aux_available = True\n+            # Use the fixed-temperature override (e.g. kimi-for-coding → 0.6) if\n+            # the model has a strict contract; otherwise the historical 0.3 default.\n+            _flush_temperature = _fixed_temperature_for_model(self.model)\n+            if _flush_temperature is None:\n+                _flush_temperature = 0.3\n             try:\n                 response = _call_llm(\n                     task=\"flush_memories\",\n                     messages=api_messages,\n                     tools=[memory_tool_def],\n-                    temperature=0.3,\n+                    temperature=_flush_temperature,\n                     max_tokens=5120,\n                     # timeout resolved from auxiliary.flush_memories.timeout config\n                 )","@@ -7226,7 +7234,7 @@ def flush_memories(self, messages: list = None, min_turns: int = None):\n                 # No auxiliary client -- use the Codex Responses path directly\n                 codex_kwargs = self._build_api_kwargs(api_messages)\n                 codex_kwargs[\"tools\"] = self._responses_tools([memory_tool_def])\n-                codex_kwargs[\"temperature\"] = 0.3\n+                codex_kwargs[\"temperature\"] = _flush_temperature\n                 if \"max_output_tokens\" in codex_kwargs:\n                     codex_kwargs[\"max_output_tokens\"] = 5120\n                 response = self._run_codex_stream(codex_kwargs)","@@ -7245,7 +7253,7 @@ def flush_memories(self, messages: list = None, min_turns: int = None):\n                     \"model\": self.model,\n                     \"messages\": api_messages,\n                     \"tools\": [memory_tool_def],\n-                    \"temperature\": 0.3,\n+                    \"temperature\": _flush_temperature,\n                     **self._max_tokens_param(5120),\n                 }\n                 from agent.auxiliary_client import _get_task_timeout"]},{"file":"tests/agent/test_auxiliary_client.py","hunks":["@@ -696,6 +696,95 @@ def test_500_not_connection(self):\n         assert _is_connection_error(err) is False\n \n \n+class TestKimiForCodingTemperature:\n+    \"\"\"kimi-for-coding now requires temperature=0.6 exactly.\"\"\"\n+\n+    def test_build_call_kwargs_forces_fixed_temperature(self):\n+        from agent.auxiliary_client import _build_call_kwargs\n+\n+        kwargs = _build_call_kwargs(\n+            provider=\"kimi-coding\",\n+            model=\"kimi-for-coding\",\n+            messages=[{\"role\": \"user\", \"content\": \"hello\"}],\n+            temperature=0.3,\n+        )\n+\n+        assert kwargs[\"temperature\"] == 0.6\n+\n+    def test_build_call_kwargs_injects_temperature_when_missing(self):\n+        from agent.auxiliary_client import _build_call_kwargs\n+\n+        kwargs = _build_call_kwargs(\n+            provider=\"kimi-coding\",\n+            model=\"kimi-for-coding\",\n+            messages=[{\"role\": \"user\", \"content\": \"hello\"}],\n+            temperature=None,\n+        )\n+\n+        assert kwargs[\"temperature\"] == 0.6\n+\n+    def test_auto_routed_kimi_for_coding_sync_call_uses_fixed_temperature(self):\n+        client = MagicMock()\n+        client.base_url = \"https://api.kimi.com/coding/v1\"\n+        response = MagicMock()\n+        client.chat.completions.create.return_value = response\n+\n+        with patch(\n+            \"agent.auxiliary_client._get_cached_client\",\n+            return_value=(client, \"kimi-for-coding\"),\n+        ), patch(\n+            \"agent.auxiliary_client._resolve_task_provider_model\",\n+            return_value=(\"auto\", \"kimi-for-coding\", None, None, None),\n+        ):\n+            result = call_llm(\n+                task=\"session_search\",\n+                messages=[{\"role\": \"user\", \"content\": \"hello\"}],\n+                temperature=0.1,\n+            )\n+\n+        assert result is response\n+        kwargs = client.chat.completions.create.call_args.kwargs\n+        assert kwargs[\"model\"] == \"kimi-for-coding\"\n+        assert kwargs[\"temperature\"] == 0.6\n+\n+    @pytest.mark.asyncio\n+    async def test_auto_routed_kimi_for_coding_async_call_uses_fixed_temperature(self):\n+        client = MagicMock()\n+        client.base_url = \"https://api.kimi.com/coding/v1\"\n+        response = MagicMock()\n+        client.chat.completions.create = AsyncMock(return_value=response)\n+\n+        with patch(\n+            \"agent.auxiliary_client._get_cached_client\",\n+            return_value=(client, \"kimi-for-coding\"),\n+        ), patch(\n+            \"agent.auxiliary_client._resolve_task_provider_model\",\n+            return_value=(\"auto\", \"kimi-for-coding\", None, None, None),\n+        ):\n+            result = await async_call_llm(\n+                task=\"session_search\",\n+                messages=[{\"role\": \"user\", \"content\": \"hello\"}],\n+                temperature=0.1,\n+            )\n+\n+        assert result is response\n+        kwargs = client.chat.completions.create.call_args.kwargs\n+        assert kwargs[\"model\"] == \"kimi-for-coding\"\n+        assert kwargs[\"temperature\"] == 0.6\n+\n+    def test_non_kimi_model_still_preserves_temperature(self):\n+        from agent.auxiliary_client import _build_call_kwargs\n+\n+        kwargs = _build_call_kwargs(\n+            provider=\"kimi-coding\",\n+            model=\"kimi-k2.5\",\n+            messages=[{\"role\": \"user\", \"content\": \"hello\"}],\n+            temperature=0.3,\n+        )\n+\n+        assert kwargs[\"temperature\"] == 0.3\n+\n+\n # ---------------------------------------------------------------------------\n # async_call_llm payment / connection fallback (#7512 bug 2)\n # ---------------------------------------------------------------------------\n"]}]},{"kind":"pr","number":12668,"title":"fix(kimi): kimi-k2.5 needs temperature=1.0 on api.moonshot.ai endpoint","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#12773","number":12773}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":12680,"title":"fix(kimi): honor public Moonshot temperature contract across direct call paths","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#12773","number":12773}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":12760,"title":"fix: move kimi-k2.5 from instant to thinking models","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#12773","number":12773}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":12772,"title":"fix: move kimi-k2.5 to thinking models","url":null,"state":"closed","close_reason":{"category":"stale_abandoned","implication":"active"},"merged":false},{"kind":"pr","number":12773,"title":"fix(kimi): honor public Moonshot temperature contract across direct call paths","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/auxiliary_client.py","mini_swe_runner.py","run_agent.py","scripts/release.py","tests/agent/test_auxiliary_client.py","tests/run_agent/test_run_agent.py","tests/test_mini_swe_runner.py","tests/test_trajectory_compressor.py","tests/test_trajectory_compressor_async.py","trajectory_compressor.py"],"fix_hunks":[{"file":"agent/auxiliary_client.py","hunks":["@@ -116,8 +116,25 @@ def _normalize_aux_provider(provider: Optional[str]) -> str:\n     \"kimi-k2-thinking-turbo\",\n })\n \n+# Moonshot's public chat endpoint (api.moonshot.ai/v1) enforces a different\n+# temperature contract than the Coding Plan endpoint above.  Empirically,\n+# `kimi-k2.5` on the public API rejects 0.6 with HTTP 400\n+# \"invalid temperature: only 1 is allowed for this model\" — the Coding Plan\n+# lock (0.6 for non-thinking) does not apply.  `kimi-k2-turbo-preview` and the\n+# thinking variants already match the Coding Plan contract on the public\n+# endpoint, so we only override the models that diverge.\n+# Users hit this endpoint when `KIMI_API_KEY` is a legacy `sk-*` key (the\n+# `sk-kimi-*` prefix routes to api.kimi.com/coding/v1 instead — see\n+# hermes_cli/auth.py:_kimi_base_url_for_key).\n+_KIMI_PUBLIC_API_OVERRIDES: Dict[str, float] = {\n+    \"kimi-k2.5\": 1.0,\n+}\n+\n \n-def _fixed_temperature_for_model(model: Optional[str]) -> Optional[float]:\n+def _fixed_temperature_for_model(\n+    model: Optional[str],\n+    base_url: Optional[str] = None,\n+) -> Optional[float]:\n     \"\"\"Return a required temperature override for models with strict contracts.\n \n     Moonshot's kimi-for-coding endpoint rejects any non-approved temperature on","@@ -125,15 +142,31 @@ def _fixed_temperature_for_model(model: Optional[str]) -> Optional[float]:\n     variants require 1.0.  An optional ``vendor/`` prefix (e.g.\n     ``moonshotai/kimi-k2.5``) is tolerated for aggregator routings.\n \n+    When ``base_url`` points to Moonshot's public chat endpoint\n+    (``api.moonshot.ai``), the contract changes for ``kimi-k2.5``: the public\n+    API only accepts ``temperature=1``, not 0.6.  That override takes precedence\n+    over the Coding Plan defaults above.\n+\n     Returns ``None`` for every other model, including ``kimi-k2-instruct*``\n     which is the separate non-coding K2 family with variable temperature.\n     \"\"\"\n     normalized = (model or \"\").strip().lower()\n+    bare = normalized.rsplit(\"/\", 1)[-1]\n+\n+    # Public Moonshot API has a stricter contract for some models than the\n+    # Coding Plan endpoint — check it first so it wins on conflict.\n+    if base_url and \"api.moonshot.ai\" in base_url.lower():\n+        public = _KIMI_PUBLIC_API_OVERRIDES.get(bare)\n+        if public is not None:\n+            logger.debug(\n+                \"Forcing temperature=%s for %r on public Moonshot API\", public, model\n+            )\n+            return public\n+\n     fixed = _FIXED_TEMPERATURE_MODELS.get(normalized)\n     if fixed is not None:\n         logger.debug(\"Forcing temperature=%s for model %r (fixed map)\", fixed, model)\n         return fixed\n-    bare = normalized.rsplit(\"/\", 1)[-1]\n     if bare in _KIMI_THINKING_MODELS:\n         logger.debug(\"Forcing temperature=1.0 for kimi thinking model %r\", model)\n         return 1.0","@@ -2417,7 +2450,7 @@ def _build_call_kwargs(\n         \"timeout\": timeout,\n     }\n \n-    fixed_temperature = _fixed_temperature_for_model(model)\n+    fixed_temperature = _fixed_temperature_for_model(model, base_url)\n     if fixed_temperature is not None:\n         temperature = fixed_temperature\n ","@@ -2598,11 +2631,14 @@ def call_llm(\n                      task, resolved_provider or \"auto\", final_model or \"default\",\n                      f\" at {_base_info}\" if _base_info and \"openrouter\" not in _base_info else \"\")\n \n+    # Pass the client's actual base_url (not just resolved_base_url) so\n+    # endpoint-specific temperature overrides can distinguish\n+    # api.moonshot.ai vs api.kimi.com/coding even on auto-detected routes.\n     kwargs = _build_call_kwargs(\n         resolved_provider, final_model, messages,\n         temperature=temperature, max_tokens=[redacted:secret-assignment]\n         tools=tools, timeout=effective_timeout, extra_body=extra_body,\n-        base_url=resolved_base_url)\n+        base_url=_base_info or resolved_base_url)\n \n     # Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)\n     _client_base = str(getattr(client, \"base_url\", \"\") or \"\")","@@ -2656,7 +2692,8 @@ def call_llm(\n                     fb_label, fb_model, messages,\n                     temperature=temperature, max_tokens=[redacted:secret-assignment]\n                     tools=tools, timeout=effective_timeout,\n-                    extra_body=extra_body)\n+                    extra_body=extra_body,\n+                    base_url=str(getattr(fb_client, \"base_url\", \"\") or \"\"))\n                 return _validate_llm_response(\n                     fb_client.chat.completions.create(**fb_kwargs), task)\n         raise","@@ -2791,14 +2828,17 @@ async def async_call_llm(\n \n     effective_timeout = timeout if timeout is not None else _get_task_timeout(task)\n \n+    # Pass the client's actual base_url (not just resolved_base_url) so\n+    # endpoint-specific temperature overrides can distinguish\n+    # api.moonshot.ai vs api.kimi.com/coding even on auto-detected routes.\n+    _client_base = str(getattr(client, \"base_url\", \"\") or \"\")\n     kwargs = _build_call_kwargs(\n         resolved_provider, final_model, messages,\n         temperature=temperature, max_tokens=[redacted:secret-assignment]\n         tools=tools, timeout=effective_timeout, extra_body=extra_body,\n-        base_url=resolved_base_url)\n+        base_url=_client_base or resolved_base_url)\n \n     # Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)\n-    _client_base = str(getattr(client, \"base_url\", \"\") or \"\")\n     if _is_anthropic_compat_endpoint(resolved_provider, _client_base):\n         kwargs[\"messages\"] = _convert_openai_images_to_anthropic(kwargs[\"messages\"])\n ","@@ -2834,7 +2874,8 @@ async def async_call_llm(\n                     fb_label, fb_model, messages,\n                     temperature=temperature, max_tokens=[redacted:secret-assignment]\n                     tools=tools, timeout=effective_timeout,\n-                    extra_body=extra_body)\n+                    extra_body=extra_body,\n+                    base_url=str(getattr(fb_client, \"base_url\", \"\") or \"\"))\n                 # Convert sync fallback client to async\n                 async_fb, async_fb_model = _to_async_client(fb_client, fb_model or \"\")\n                 if async_fb_model and async_fb_model != fb_kwargs.get(\"model\"):"]},{"file":"mini_swe_runner.py","hunks":["@@ -43,13 +43,16 @@\n load_dotenv()\n \n \n-def _effective_temperature_for_model(model: str) -> Optional[float]:\n+def _effective_temperature_for_model(\n+    model: str,\n+    base_url: Optional[str] = None,\n+) -> Optional[float]:\n     \"\"\"Return a fixed temperature for models with strict sampling contracts.\"\"\"\n     try:\n         from agent.auxiliary_client import _fixed_temperature_for_model\n     except Exception:\n         return None\n-    return _fixed_temperature_for_model(model)\n+    return _fixed_temperature_for_model(model, base_url)\n \n \n ","@@ -457,7 +460,10 @@ def run_task(self, task: str) -> Dict[str, Any]:\n                         \"tools\": self.tools,\n                         \"timeout\": 300.0,\n                     }\n-                    fixed_temperature = _effective_temperature_for_model(self.model)\n+                    fixed_temperature = _effective_temperature_for_model(\n+                        self.model,\n+                        str(getattr(self.client, \"base_url\", \"\") or \"\"),\n+                    )\n                     if fixed_temperature is not None:\n                         api_kwargs[\"temperature\"] = fixed_temperature\n "]},{"file":"run_agent.py","hunks":["@@ -7173,7 +7173,7 @@ def _build_api_kwargs(self, api_messages: list) -> dict:\n         except Exception:\n             _fixed_temperature_for_model = None\n         if _fixed_temperature_for_model is not None:\n-            fixed_temperature = _fixed_temperature_for_model(self.model)\n+            fixed_temperature = _fixed_temperature_for_model(self.model, self.base_url)\n             if fixed_temperature is not None:\n                 api_kwargs[\"temperature\"] = fixed_temperature\n         if self._is_qwen_portal():","@@ -7619,7 +7619,7 @@ def flush_memories(self, messages: list = None, min_turns: int = None):\n             _aux_available = True\n             # Use the fixed-temperature override (e.g. kimi-for-coding → 0.6) if\n             # the model has a strict contract; otherwise the historical 0.3 default.\n-            _flush_temperature = _fixed_temperature_for_model(self.model)\n+            _flush_temperature = _fixed_temperature_for_model(self.model, self.base_url)\n             if _flush_temperature is None:\n                 _flush_temperature = 0.3\n             try:","@@ -8675,7 +8675,7 @@ def _handle_max_iterations(self, messages: list, api_call_count: int) -> str:\n             except Exception:\n                 _fixed_temperature_for_model = None\n             _summary_temperature = (\n-                _fixed_temperature_for_model(self.model)\n+                _fixed_temperature_for_model(self.model, self.base_url)\n                 if _fixed_temperature_for_model is not None\n                 else None\n             )"]},{"file":"scripts/release.py","hunks":["@@ -107,6 +107,7 @@\n     \"239876380+handsdiff@users.noreply.github.com\": \"handsdiff\",\n     \"[redacted:email]\": \"etherman-os\",\n     \"[redacted:email]\": \"mark-ramsell\",\n+    \"[redacted:email]\": \"taeng0204\",\n     \"[redacted:email]\": \"gpickett00\",\n     \"[redacted:email]\": \"wakamex\",\n     \"[redacted:email]\": \"clawdia-nash\","]},{"file":"tests/agent/test_auxiliary_client.py","hunks":["@@ -832,6 +832,92 @@ def test_non_restricted_model_preserves_temperature(self, model):\n \n         assert kwargs[\"temperature\"] == 0.3\n \n+    # ── Endpoint-aware overrides: api.moonshot.ai vs api.kimi.com/coding ──\n+    # The public Moonshot chat endpoint and the Coding Plan endpoint enforce\n+    # different temperature contracts for the same model name.  `kimi-k2.5` on\n+    # api.moonshot.ai rejects 0.6 with HTTP 400 \"only 1 is allowed for this\n+    # model\", while the Coding Plan docs mandate 0.6.  Override must pick the\n+    # right value per base_url.\n+\n+    @pytest.mark.parametrize(\n+        \"base_url\",\n+        [\n+            \"https://api.moonshot.ai/v1\",\n+            \"https://api.moonshot.ai/v1/\",\n+            \"https://API.MOONSHOT.AI/v1\",\n+        ],\n+    )\n+    def test_kimi_k2_5_public_api_forces_temperature_1(self, base_url):\n+        \"\"\"kimi-k2.5 on the public Moonshot API only accepts temperature=1.\"\"\"\n+        from agent.auxiliary_client import _build_call_kwargs\n+\n+        kwargs = _build_call_kwargs(\n+            provider=\"kimi-coding\",\n+            model=\"kimi-k2.5\",\n+            messages=[{\"role\": \"user\", \"content\": \"hello\"}],\n+            temperature=0.1,\n+            base_url=base_url,\n+        )\n+\n+        assert kwargs[\"temperature\"] == 1.0\n+\n+    def test_kimi_k2_5_coding_plan_keeps_temperature_0_6(self):\n+        \"\"\"kimi-k2.5 on api.kimi.com/coding keeps the Coding Plan's 0.6 lock.\"\"\"\n+        from agent.auxiliary_client import _build_call_kwargs\n+\n+        kwargs = _build_call_kwargs(\n+            provider=\"kimi-coding\",\n+            model=\"kimi-k2.5\",\n+            messages=[{\"role\": \"user\", \"content\": \"hello\"}],\n+            temperature=0.1,\n+            base_url=\"https://api.kimi.com/coding/v1\",\n+        )\n+\n+        assert kwargs[\"temperature\"] == 0.6\n+\n+    def test_kimi_k2_5_no_base_url_falls_back_to_coding_plan_lock(self):\n+        \"\"\"Without a base_url hint, the Coding Plan default (0.6) applies.\n+\n+        Preserves PR #12144 backward compatibility for callers that don't thread\n+        the client's base_url through.\n+        \"\"\"\n+        from agent.auxiliary_client import _build_call_kwargs\n+\n+        kwargs = _build_call_kwargs(\n+            provider=\"kimi-coding\",\n+            model=\"kimi-k2.5\",\n+            messages=[{\"role\": \"user\", \"content\": \"hello\"}],\n+            temperature=0.1,\n+        )\n+\n+        assert kwargs[\"temperature\"] == 0.6\n+\n+    @pytest.mark.parametrize(\n+        \"model,expected\",\n+        [\n+            # Only kimi-k2.5 diverges on api.moonshot.ai; the rest keep the\n+            # Coding Plan lock (empirically verified against Moonshot in April\n+            # 2026: turbo-preview accepts 0.6, thinking-turbo accepts 1.0).\n+            (\"kimi-k2-turbo-preview\", 0.6),\n+            (\"kimi-k2-0905-preview\", 0.6),\n+            (\"kimi-k2-thinking\", 1.0),\n+            (\"kimi-k2-thinking-turbo\", 1.0),\n+            (\"moonshotai/kimi-k2-thinking-turbo\", 1.0),\n+        ],\n+    )\n+    def test_other_kimi_k2_family_unchanged_on_public_api(self, model, expected):\n+        from agent.auxiliary_client import _build_call_kwargs\n+\n+        kwargs = _build_call_kwargs(\n+            provider=\"kimi-coding\",\n+            model=model,\n+            messages=[{\"role\": \"user\", \"content\": \"hello\"}],\n+            temperature=0.1,\n+            base_url=\"https://api.moonshot.ai/v1\",\n+        )\n+\n+        assert kwargs[\"temperature\"] == expected\n+\n \n # ---------------------------------------------------------------------------\n # async_call_llm payment / connection fallback (#7512 bug 2)"]},{"file":"tests/run_agent/test_run_agent.py","hunks":["@@ -918,6 +918,26 @@ def test_basic_kwargs(self, agent):\n         assert kwargs[\"messages\"] is messages\n         assert kwargs[\"timeout\"] == 1800.0\n \n+    def test_public_moonshot_kimi_k2_5_forces_temperature_1(self, agent):\n+        agent.base_url = \"https://api.moonshot.ai/v1\"\n+        agent._base_url_lower = agent.base_url.lower()\n+        agent.model = \"kimi-k2.5\"\n+        messages = [{\"role\": \"user\", \"content\": \"hi\"}]\n+\n+        kwargs = agent._build_api_kwargs(messages)\n+\n+        assert kwargs[\"temperature\"] == 1.0\n+\n+    def test_kimi_coding_endpoint_keeps_kimi_k2_5_at_0_6(self, agent):\n+        agent.base_url = \"https://api.kimi.com/coding/v1\"\n+        agent._base_url_lower = agent.base_url.lower()\n+        agent.model = \"kimi-k2.5\"\n+        messages = [{\"role\": \"user\", \"content\": \"hi\"}]\n+\n+        kwargs = agent._build_api_kwargs(messages)\n+\n+        assert kwargs[\"temperature\"] == 0.6\n+\n     def test_provider_preferences_injected(self, agent):\n         agent.base_url = \"https://openrouter.ai/api/v1\"\n         agent.providers_allowed = [\"Anthropic\"]"]},{"file":"tests/test_mini_swe_runner.py","hunks":["@@ -26,3 +26,30 @@ def test_run_task_forces_kimi_fixed_temperature():\n \n     assert result[\"completed\"] is True\n     assert client.chat.completions.create.call_args.kwargs[\"temperature\"] == 0.6\n+\n+\n+def test_run_task_public_moonshot_kimi_k2_5_forces_temperature_1():\n+    with patch(\"openai.OpenAI\") as mock_openai:\n+        client = MagicMock()\n+        client.base_url = \"https://api.moonshot.ai/v1\"\n+        client.chat.completions.create.return_value = SimpleNamespace(\n+            choices=[SimpleNamespace(message=SimpleNamespace(content=\"done\", tool_calls=[]))]\n+        )\n+        mock_openai.return_value = client\n+\n+        from mini_swe_runner import MiniSWERunner\n+\n+        runner = MiniSWERunner(\n+            model=\"kimi-k2.5\",\n+            base_url=\"https://api.moonshot.ai/v1\",\n+            api_key=\"[redacted:secret-assignment]\",\n+            env_type=\"local\",\n+            max_iterations=1,\n+        )\n+        runner._create_env = MagicMock()\n+        runner._cleanup_env = MagicMock()\n+\n+        result = runner.run_task(\"2+2\")\n+\n+    assert result[\"completed\"] is True\n+    assert client.chat.completions.create.call_args.kwargs[\"temperature\"] == 1.0"]},{"file":"tests/test_trajectory_compressor.py","hunks":["@@ -54,6 +54,30 @@ def test_generate_summary_custom_client_forces_kimi_temperature():\n     assert compressor.client.chat.completions.create.call_args.kwargs[\"temperature\"] == 0.6\n \n \n+def test_generate_summary_public_moonshot_kimi_k2_5_forces_temperature_1():\n+    config = CompressionConfig(\n+        summarization_model=\"kimi-k2.5\",\n+        base_url=\"https://api.moonshot.ai/v1\",\n+        temperature=0.3,\n+        summary_target_tokens=100,\n+        max_retries=1,\n+    )\n+    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)\n+    compressor.config = config\n+    compressor.logger = MagicMock()\n+    compressor._use_call_llm = False\n+    compressor.client = MagicMock()\n+    compressor.client.chat.completions.create.return_value = SimpleNamespace(\n+        choices=[SimpleNamespace(message=SimpleNamespace(content=\"[CONTEXT SUMMARY]: summary\"))]\n+    )\n+\n+    metrics = TrajectoryMetrics()\n+    result = compressor._generate_summary(\"tool output\", metrics)\n+\n+    assert result.startswith(\"[CONTEXT SUMMARY]:\")\n+    assert compressor.client.chat.completions.create.call_args.kwargs[\"temperature\"] == 1.0\n+\n+\n # ---------------------------------------------------------------------------\n # CompressionConfig\n # ---------------------------------------------------------------------------"]},{"file":"tests/test_trajectory_compressor_async.py","hunks":["@@ -141,3 +141,31 @@ async def test_generate_summary_async_custom_client_forces_kimi_temperature():\n \n     assert result.startswith(\"[CONTEXT SUMMARY]:\")\n     assert async_client.chat.completions.create.call_args.kwargs[\"temperature\"] == 0.6\n+\n+\n[redacted:email]\n+async def test_generate_summary_async_public_moonshot_kimi_k2_5_forces_temperature_1():\n+    from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics\n+\n+    config = CompressionConfig(\n+        summarization_model=\"kimi-k2.5\",\n+        base_url=\"https://api.moonshot.ai/v1\",\n+        temperature=0.3,\n+        summary_target_tokens=100,\n+        max_retries=1,\n+    )\n+    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)\n+    compressor.config = config\n+    compressor.logger = MagicMock()\n+    compressor._use_call_llm = False\n+    async_client = MagicMock()\n+    async_client.chat.completions.create = MagicMock(return_value=SimpleNamespace(\n+        choices=[SimpleNamespace(message=SimpleNamespace(content=\"[CONTEXT SUMMARY]: summary\"))]\n+    ))\n+    compressor._get_async_client = MagicMock(return_value=async_client)\n+\n+    metrics = TrajectoryMetrics()\n+    result = await compressor._generate_summary_async(\"tool output\", metrics)\n+\n+    assert result.startswith(\"[CONTEXT SUMMARY]:\")\n+    assert async_client.chat.completions.create.call_args.kwargs[\"temperature\"] == 1.0"]},{"file":"trajectory_compressor.py","hunks":["@@ -54,14 +54,18 @@\n load_hermes_dotenv(hermes_home=_hermes_home, project_env=_project_env)\n \n \n-def _effective_temperature_for_model(model: str, requested_temperature: float) -> float:\n+def _effective_temperature_for_model(\n+    model: str,\n+    requested_temperature: float,\n+    base_url: Optional[str] = None,\n+) -> float:\n     \"\"\"Apply fixed model temperature contracts to direct client calls.\"\"\"\n     try:\n         from agent.auxiliary_client import _fixed_temperature_for_model\n     except Exception:\n         return requested_temperature\n \n-    fixed_temperature = _fixed_temperature_for_model(model)\n+    fixed_temperature = _fixed_temperature_for_model(model, base_url)\n     if fixed_temperature is not None:\n         return fixed_temperature\n     return requested_temperature","@@ -583,6 +587,7 @@ def _generate_summary(self, content: str, metrics: TrajectoryMetrics) -> str:\n                 summary_temperature = _effective_temperature_for_model(\n                     self.config.summarization_model,\n                     self.config.temperature,\n+                    self.config.base_url,\n                 )\n                 \n                 if getattr(self, '_use_call_llm', False):","@@ -649,6 +654,7 @@ async def _generate_summary_async(self, content: str, metrics: TrajectoryMetrics\n                 summary_temperature = _effective_temperature_for_model(\n                     self.config.summarization_model,\n                     self.config.temperature,\n+                    self.config.base_url,\n                 )\n                 \n                 if getattr(self, '_use_call_llm', False):\n"]}]},{"kind":"pr","number":12806,"title":"fix: include api.moonshot.cn in public API temperature override (#12745)","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":12861,"title":"fix(kimi): include api.moonshot.cn in public temperature override","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/auxiliary_client.py","tests/agent/test_auxiliary_client.py","tests/run_agent/test_run_agent.py","tests/test_trajectory_compressor.py","tests/test_trajectory_compressor_async.py"],"fix_hunks":[{"file":"agent/auxiliary_client.py","hunks":["@@ -155,7 +155,7 @@ def _fixed_temperature_for_model(\n \n     # Public Moonshot API has a stricter contract for some models than the\n     # Coding Plan endpoint — check it first so it wins on conflict.\n-    if base_url and \"api.moonshot.ai\" in base_url.lower():\n+    if base_url and (\"api.moonshot.ai\" in base_url.lower() or \"api.moonshot.cn\" in base_url.lower()):\n         public = _KIMI_PUBLIC_API_OVERRIDES.get(bare)\n         if public is not None:\n             logger.debug("]},{"file":"tests/agent/test_auxiliary_client.py","hunks":["@@ -845,6 +845,8 @@ def test_non_restricted_model_preserves_temperature(self, model):\n             \"https://api.moonshot.ai/v1\",\n             \"https://api.moonshot.ai/v1/\",\n             \"https://API.MOONSHOT.AI/v1\",\n+            \"https://api.moonshot.cn/v1\",\n+            \"https://api.moonshot.cn/v1/\",\n         ],\n     )\n     def test_kimi_k2_5_public_api_forces_temperature_1(self, base_url):"]},{"file":"tests/run_agent/test_run_agent.py","hunks":["@@ -928,6 +928,16 @@ def test_public_moonshot_kimi_k2_5_forces_temperature_1(self, agent):\n \n         assert kwargs[\"temperature\"] == 1.0\n \n+    def test_public_moonshot_cn_kimi_k2_5_forces_temperature_1(self, agent):\n+        agent.base_url = \"https://api.moonshot.cn/v1\"\n+        agent._base_url_lower = agent.base_url.lower()\n+        agent.model = \"kimi-k2.5\"\n+        messages = [{\"role\": \"user\", \"content\": \"hi\"}]\n+\n+        kwargs = agent._build_api_kwargs(messages)\n+\n+        assert kwargs[\"temperature\"] == 1.0\n+\n     def test_kimi_coding_endpoint_keeps_kimi_k2_5_at_0_6(self, agent):\n         agent.base_url = \"https://api.kimi.com/coding/v1\"\n         agent._base_url_lower = agent.base_url.lower()"]},{"file":"tests/test_trajectory_compressor.py","hunks":["@@ -78,6 +78,30 @@ def test_generate_summary_public_moonshot_kimi_k2_5_forces_temperature_1():\n     assert compressor.client.chat.completions.create.call_args.kwargs[\"temperature\"] == 1.0\n \n \n+def test_generate_summary_public_moonshot_cn_kimi_k2_5_forces_temperature_1():\n+    config = CompressionConfig(\n+        summarization_model=\"kimi-k2.5\",\n+        base_url=\"https://api.moonshot.cn/v1\",\n+        temperature=0.3,\n+        summary_target_tokens=100,\n+        max_retries=1,\n+    )\n+    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)\n+    compressor.config = config\n+    compressor.logger = MagicMock()\n+    compressor._use_call_llm = False\n+    compressor.client = MagicMock()\n+    compressor.client.chat.completions.create.return_value = SimpleNamespace(\n+        choices=[SimpleNamespace(message=SimpleNamespace(content=\"[CONTEXT SUMMARY]: summary\"))]\n+    )\n+\n+    metrics = TrajectoryMetrics()\n+    result = compressor._generate_summary(\"tool output\", metrics)\n+\n+    assert result.startswith(\"[CONTEXT SUMMARY]:\")\n+    assert compressor.client.chat.completions.create.call_args.kwargs[\"temperature\"] == 1.0\n+\n+\n # ---------------------------------------------------------------------------\n # CompressionConfig\n # ---------------------------------------------------------------------------"]},{"file":"tests/test_trajectory_compressor_async.py","hunks":["@@ -169,3 +169,32 @@ async def test_generate_summary_async_public_moonshot_kimi_k2_5_forces_temperatu\n \n     assert result.startswith(\"[CONTEXT SUMMARY]:\")\n     assert async_client.chat.completions.create.call_args.kwargs[\"temperature\"] == 1.0\n+\n+\n+\n[redacted:email]\n+async def test_generate_summary_async_public_moonshot_cn_kimi_k2_5_forces_temperature_1():\n+    from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics\n+\n+    config = CompressionConfig(\n+        summarization_model=\"kimi-k2.5\",\n+        base_url=\"https://api.moonshot.cn/v1\",\n+        temperature=0.3,\n+        summary_target_tokens=100,\n+        max_retries=1,\n+    )\n+    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)\n+    compressor.config = config\n+    compressor.logger = MagicMock()\n+    compressor._use_call_llm = False\n+    async_client = MagicMock()\n+    async_client.chat.completions.create = MagicMock(return_value=SimpleNamespace(\n+        choices=[SimpleNamespace(message=SimpleNamespace(content=\"[CONTEXT SUMMARY]: summary\"))]\n+    ))\n+    compressor._get_async_client = MagicMock(return_value=async_client)\n+\n+    metrics = TrajectoryMetrics()\n+    result = await compressor._generate_summary_async(\"tool output\", metrics)\n+\n+    assert result.startswith(\"[CONTEXT SUMMARY]:\")\n+    assert async_client.chat.completions.create.call_args.kwargs[\"temperature\"] == 1.0\n"]}]},{"kind":"pr","number":12862,"title":"fix(vision): honor auxiliary.vision.temperature from config","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["scripts/release.py","tests/tools/test_browser_camofox.py","tests/tools/test_browser_console.py","tests/tools/test_vision_tools.py","tools/browser_camofox.py","tools/browser_tool.py","tools/vision_tools.py"]},{"kind":"pr","number":51157,"title":"fix(auxiliary): OMIT temperature for gpt-5.5 family (#51083)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"ada97e3bac36","number":0},{"kind":"pr","value":"#115850","number":115850},{"kind":"pr","value":"#51083","number":51083},{"kind":"pr","value":"#51157","number":51157}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":53495,"title":"fix(temperature): set temperature=1 for Kimi coding models on /coding/ endpoint (#53455)","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":115850,"title":"fix(auxiliary): vision and other auxiliary calls to gpt-5.x / o-series no longer pay a temperature 400 round-trip (#51083, salvage #51157)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/auxiliary_client.py","tests/agent/test_auxiliary_client.py","tests/agent/test_auxiliary_parameter_rung_chaining.py","tests/agent/test_injected_param_strip_retry_registry.py","tests/agent/test_unsupported_temperature_retry.py"]}],"trust":{"default_tier":"verified","includes_suggested":false}}