# NousResearch/hermes-agent · Temperature handling for Kimi and gpt-5.5 auxiliary/vision calls

## Open now

- 1 issue, 1 PR open on GitHub.
- #53455: vision_analyze tool passes invalid temperature to kimi-k2.7-code model
- PR #53495: fix(temperature): set temperature=1 for Kimi coding models on /coding/ endpoint (#53455)

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.

## TL;DR

- 21 member(s): 1 open issue(s), 5 closed issue(s), 1 open PR(s), 5 merged PR(s), 9 PR(s) closed without merging.
- A fix was merged to main in PR #12773. Verify it addresses your case.
- **Recommended:** read PR #12773 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-12773,pr-12861,pr-12862,pr-115850`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.09`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Open related issues

- #53455: vision_analyze tool passes invalid temperature to kimi-k2.7-code model

## Closed / duplicate issues

- #4661: \[Bug\]: kimi-coding vision provider fails with 400 error on second call due to hardcoded temperature
- #9125: \[Bug\]: kimi-coding auxiliary calls fail with 400 "invalid temperature" when provider=auto
- #12404: kimi-k2.5 fails with invalid temperature error
- #12745: \[Bug\]: kimi-k2.5 incorrectly classified as instant mode (temperature=0.6) instead of thinking mode (temperature=1.0)
- #51083: \[Bug\]: auxiliary vision sends unsupported temperature to OpenAI gpt-5.5

## Prior & competing PRs

- **#4967: fix: vision tool respects auxiliary.vision.temperature from config**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `088bf9057fc879a49dd0d9fc1e2a0047a8f2aa09`, `#4661`
- **#11834: fix(kimi): force kimi-for-coding temperature to 0.6**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/auxiliary_client.py`, `run_agent.py`, `tests/agent/test_auxiliary_client.py`
  - fix hunks:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -94,6 +94,17 @@ def _normalize_aux_provider(provider: Optional[str]) -> str:
             return "custom"
         return _PROVIDER_ALIASES.get(normalized, normalized)
     
    +
    +_FIXED_TEMPERATURE_MODELS: Dict[str, float] = {
    +    "kimi-for-coding": 0.6,
    +}
    +
    +
    +def _fixed_temperature_for_model(model: Optional[str]) -> Optional[float]:
    +    """Return a required temperature override for models with strict contracts."""
    +    normalized = (model or "").strip().lower()
    +    return _FIXED_TEMPERATURE_MODELS.get(normalized)
    +
     # Default auxiliary models for direct API-key providers (cheap/fast for side tasks)
     _API_KEY_PROVIDER_AUX_MODELS: [redacted:secret-assignment] str] = {
         "gemini": "gemini-3-flash-preview",
    ```

    ```diff
    @@ -2293,6 +2304,10 @@ def _build_call_kwargs(
             "timeout": timeout,
         }
     
    +    fixed_temperature = _fixed_temperature_for_model(model)
    +    if fixed_temperature is not None:
    +        temperature = fixed_temperature
    +
         # Opus 4.7+ rejects any non-default temperature/top_p/top_k — silently
         # drop here so auxiliary callers that hardcode temperature (e.g. 0.3 on
         # flush_memories, 0 on structured-JSON extraction) don't 400 the moment
    ```
  - `run_agent.py`

    ```diff
    @@ -7207,14 +7207,22 @@ def flush_memories(self, messages: list = None, min_turns: int = None):
     
                 # Use auxiliary client for the flush call when available --
                 # it's cheaper and avoids Codex Responses API incompatibility.
    -            from agent.auxiliary_client import call_llm as _call_llm
    +            from agent.auxiliary_client import (
    +                call_llm as _call_llm,
    +                _fixed_temperature_for_model,
    +            )
                 _aux_available = True
    +            # Use the fixed-temperature override (e.g. kimi-for-coding → 0.6) if
    +            # the model has a strict contract; otherwise the historical 0.3 default.
    +            _flush_temperature = _fixed_temperature_for_model(self.model)
    +            if _flush_temperature is None:
    +                _flush_temperature = 0.3
                 try:
                     response = _call_llm(
                         task="flush_memories",
                         messages=api_messages,
                         tools=[memory_tool_def],
    -                    temperature=0.3,
    +                    temperature=_flush_temperature,
                         max_tokens=5120,
                         # timeout resolved from auxiliary.flush_memories.timeout config
                     )
    ```

    ```diff
    @@ -7226,7 +7234,7 @@ def flush_memories(self, messages: list = None, min_turns: int = None):
                     # No auxiliary client -- use the Codex Responses path directly
                     codex_kwargs = self._build_api_kwargs(api_messages)
                     codex_kwargs["tools"] = self._responses_tools([memory_tool_def])
    -                codex_kwargs["temperature"] = 0.3
    +                codex_kwargs["temperature"] = _flush_temperature
                     if "max_output_tokens" in codex_kwargs:
                         codex_kwargs["max_output_tokens"] = 5120
                     response = self._run_codex_stream(codex_kwargs)
    ```

    ```diff
    @@ -7245,7 +7253,7 @@ def flush_memories(self, messages: list = None, min_turns: int = None):
                         "model": self.model,
                         "messages": api_messages,
                         "tools": [memory_tool_def],
    -                    "temperature": 0.3,
    +                    "temperature": _flush_temperature,
                         **self._max_tokens_param(5120),
                     }
                     from agent.auxiliary_client import _get_task_timeout
    ```
  - `tests/agent/test_auxiliary_client.py`

    ```diff
    @@ -696,6 +696,95 @@ def test_500_not_connection(self):
             assert _is_connection_error(err) is False
     
     
    +class TestKimiForCodingTemperature:
    +    """kimi-for-coding now requires temperature=0.6 exactly."""
    +
    +    def test_build_call_kwargs_forces_fixed_temperature(self):
    +        from agent.auxiliary_client import _build_call_kwargs
    +
    +        kwargs = _build_call_kwargs(
    +            provider="kimi-coding",
    +            model="kimi-for-coding",
    +            messages=[{"role": "user", "content": "hello"}],
    +            temperature=0.3,
    +        )
    +
    +        assert kwargs["temperature"] == 0.6
    +
    +    def test_build_call_kwargs_injects_temperature_when_missing(self):
    +        from agent.auxiliary_client import _build_call_kwargs
    +
    +        kwargs = _build_call_kwargs(
    +            provider="kimi-coding",
    +            model="kimi-for-coding",
    +            messages=[{"role": "user", "content": "hello"}],
    +            temperature=None,
    +        )
    +
    +        assert kwargs["temperature"] == 0.6
    +
    +    def test_auto_routed_kimi_for_coding_sync_call_uses_fixed_temperature(self):
    +        client = MagicMock()
    +        client.base_url = "https://api.kimi.com/coding/v1"
    +        response = MagicMock()
    +        client.chat.completions.create.return_value = response
    +
    +        with patch(
    +            "agent.auxiliary_client._get_cached_client",
    +            return_value=(client, "kimi-for-coding"),
    +        ), patch(
    +            "agent.auxiliary_client._resolve_task_provider_model",
    +            return_value=("auto", "kimi-for-coding", None, None, None),
    +        ):
    +            result = call_llm(
    +                task="session_search",
    +                messages=[{"role": "user", "content": "hello"}],
    +                temperature=0.1,
    +            )
    +
    +        assert result is response
    +        kwargs = client.chat.completions.create.call_args.kwargs
    +        assert kwargs["model"] == "kimi-for-coding"
    +        assert kwargs["temperature"] == 0.6
    +
    +    @pytest.mark.asyncio
    +    async def test_auto_routed_kimi_for_coding_async_call_uses_fixed_temperature(self):
    +        client = MagicMock()
    +        client.base_url = "https://api.kimi.com/coding/v1"
    +        response = MagicMock()
    +        client.chat.completions.create = AsyncMock(return_value=response)
    +
    +        with patch(
    +            "agent.auxiliary_client._get_cached_client",
    +            return_value=(client, "kimi-for-coding"),
    +        ), patch(
    +            "agent.auxiliary_client._resolve_task_provider_model",
    +            return_value=("auto", "kimi-for-coding", None, None, None),
    +        ):
    +            result = await async_call_llm(
    +                task="session_search",
    +                messages=[{"role": "user", "content": "hello"}],
    +                temperature=0.1,
    +            )
    +
    +        assert result is response
    +        kwargs = client.chat.completions.create.call_args.kwargs
    +        assert kwargs["model"] == "kimi-for-coding"
    +        assert kwargs["temperature"] == 0.6
    +
    +    def test_non_kimi_model_still_preserves_temperature(self):
    +        from agent.auxiliary_client import _build_call_kwargs
    +
    +        kwargs = _build_call_kwargs(
    +            provider="kimi-coding",
    +            model="kimi-k2.5",
    +            messages=[{"role": "user", "content": "hello"}],
    +            temperature=0.3,
    +        )
    +
    +        assert kwargs["temperature"] == 0.3
    +
    +
     # ---------------------------------------------------------------------------
     # async_call_llm payment / connection fallback (#7512 bug 2)
     # ---------------------------------------------------------------------------
    ```
- **#12773: fix(kimi): honor public Moonshot temperature contract across direct call paths**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `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:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -116,8 +116,25 @@ def _normalize_aux_provider(provider: Optional[str]) -> str:
         "kimi-k2-thinking-turbo",
     })
     
    +# Moonshot's public chat endpoint (api.moonshot.ai/v1) enforces a different
    +# temperature contract than the Coding Plan endpoint above.  Empirically,
    +# `kimi-k2.5` on the public API rejects 0.6 with HTTP 400
    +# "invalid temperature: only 1 is allowed for this model" — the Coding Plan
    +# lock (0.6 for non-thinking) does not apply.  `kimi-k2-turbo-preview` and the
    +# thinking variants already match the Coding Plan contract on the public
    +# endpoint, so we only override the models that diverge.
    +# Users hit this endpoint when `KIMI_API_KEY` is a legacy `sk-*` key (the
    +# `sk-kimi-*` prefix routes to api.kimi.com/coding/v1 instead — see
    +# hermes_cli/auth.py:_kimi_base_url_for_key).
    +_KIMI_PUBLIC_API_OVERRIDES: Dict[str, float] = {
    +    "kimi-k2.5": 1.0,
    +}
    +
     
    -def _fixed_temperature_for_model(model: Optional[str]) -> Optional[float]:
    +def _fixed_temperature_for_model(
    +    model: Optional[str],
    +    base_url: Optional[str] = None,
    +) -> Optional[float]:
         """Return a required temperature override for models with strict contracts.
     
         Moonshot's kimi-for-coding endpoint rejects any non-approved temperature on
    ```

    ```diff
    @@ -125,15 +142,31 @@ def _fixed_temperature_for_model(model: Optional[str]) -> Optional[float]:
         variants require 1.0.  An optional ``vendor/`` prefix (e.g.
         ``moonshotai/kimi-k2.5``) is tolerated for aggregator routings.
     
    +    When ``base_url`` points to Moonshot's public chat endpoint
    +    (``api.moonshot.ai``), the contract changes for ``kimi-k2.5``: the public
    +    API only accepts ``temperature=1``, not 0.6.  That override takes precedence
    +    over the Coding Plan defaults above.
    +
         Returns ``None`` for every other model, including ``kimi-k2-instruct*``
         which is the separate non-coding K2 family with variable temperature.
         """
         normalized = (model or "").strip().lower()
    +    bare = normalized.rsplit("/", 1)[-1]
    +
    +    # Public Moonshot API has a stricter contract for some models than the
    +    # Coding Plan endpoint — check it first so it wins on conflict.
    +    if base_url and "api.moonshot.ai" in base_url.lower():
    +        public = _KIMI_PUBLIC_API_OVERRIDES.get(bare)
    +        if public is not None:
    +            logger.debug(
    +                "Forcing temperature=%s for %r on public Moonshot API", public, model
    +            )
    +            return public
    +
         fixed = _FIXED_TEMPERATURE_MODELS.get(normalized)
         if fixed is not None:
             logger.debug("Forcing temperature=%s for model %r (fixed map)", fixed, model)
             return fixed
    -    bare = normalized.rsplit("/", 1)[-1]
         if bare in _KIMI_THINKING_MODELS:
             logger.debug("Forcing temperature=1.0 for kimi thinking model %r", model)
             return 1.0
    ```

    ```diff
    @@ -2417,7 +2450,7 @@ def _build_call_kwargs(
             "timeout": timeout,
         }
     
    -    fixed_temperature = _fixed_temperature_for_model(model)
    +    fixed_temperature = _fixed_temperature_for_model(model, base_url)
         if fixed_temperature is not None:
             temperature = fixed_temperature
     
    ```

    ```diff
    @@ -2598,11 +2631,14 @@ def call_llm(
                          task, resolved_provider or "auto", final_model or "default",
                          f" at {_base_info}" if _base_info and "openrouter" not in _base_info else "")
     
    +    # Pass the client's actual base_url (not just resolved_base_url) so
    +    # endpoint-specific temperature overrides can distinguish
    +    # api.moonshot.ai vs api.kimi.com/coding even on auto-detected routes.
         kwargs = _build_call_kwargs(
             resolved_provider, final_model, messages,
             temperature=temperature, max_tokens=[redacted:secret-assignment]
             tools=tools, timeout=effective_timeout, extra_body=extra_body,
    -        base_url=resolved_base_url)
    +        base_url=_base_info or resolved_base_url)
     
         # Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)
         _client_base = str(getattr(client, "base_url", "") or "")
    ```

    ```diff
    @@ -2656,7 +2692,8 @@ def call_llm(
                         fb_label, fb_model, messages,
                         temperature=temperature, max_tokens=[redacted:secret-assignment]
                         tools=tools, timeout=effective_timeout,
    -                    extra_body=extra_body)
    +                    extra_body=extra_body,
    +                    base_url=str(getattr(fb_client, "base_url", "") or ""))
                     return _validate_llm_response(
                         fb_client.chat.completions.create(**fb_kwargs), task)
             raise
    ```

    ```diff
    @@ -2791,14 +2828,17 @@ async def async_call_llm(
     
         effective_timeout = timeout if timeout is not None else _get_task_timeout(task)
     
    +    # Pass the client's actual base_url (not just resolved_base_url) so
    +    # endpoint-specific temperature overrides can distinguish
    +    # api.moonshot.ai vs api.kimi.com/coding even on auto-detected routes.
    +    _client_base = str(getattr(client, "base_url", "") or "")
         kwargs = _build_call_kwargs(
             resolved_provider, final_model, messages,
             temperature=temperature, max_tokens=[redacted:secret-assignment]
             tools=tools, timeout=effective_timeout, extra_body=extra_body,
    -        base_url=resolved_base_url)
    +        base_url=_client_base or resolved_base_url)
     
         # Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)
    -    _client_base = str(getattr(client, "base_url", "") or "")
         if _is_anthropic_compat_endpoint(resolved_provider, _client_base):
             kwargs["messages"] = _convert_openai_images_to_anthropic(kwargs["messages"])
     
    ```

    ```diff
    @@ -2834,7 +2874,8 @@ async def async_call_llm(
                         fb_label, fb_model, messages,
                         temperature=temperature, max_tokens=[redacted:secret-assignment]
                         tools=tools, timeout=effective_timeout,
    -                    extra_body=extra_body)
    +                    extra_body=extra_body,
    +                    base_url=str(getattr(fb_client, "base_url", "") or ""))
                     # Convert sync fallback client to async
                     async_fb, async_fb_model = _to_async_client(fb_client, fb_model or "")
                     if async_fb_model and async_fb_model != fb_kwargs.get("model"):
    ```
  - `mini_swe_runner.py`

    ```diff
    @@ -43,13 +43,16 @@
     load_dotenv()
     
     
    -def _effective_temperature_for_model(model: str) -> Optional[float]:
    +def _effective_temperature_for_model(
    +    model: str,
    +    base_url: Optional[str] = None,
    +) -> Optional[float]:
         """Return a fixed temperature for models with strict sampling contracts."""
         try:
             from agent.auxiliary_client import _fixed_temperature_for_model
         except Exception:
             return None
    -    return _fixed_temperature_for_model(model)
    +    return _fixed_temperature_for_model(model, base_url)
     
     
     
    ```

    ```diff
    @@ -457,7 +460,10 @@ def run_task(self, task: str) -> Dict[str, Any]:
                             "tools": self.tools,
                             "timeout": 300.0,
                         }
    -                    fixed_temperature = _effective_temperature_for_model(self.model)
    +                    fixed_temperature = _effective_temperature_for_model(
    +                        self.model,
    +                        str(getattr(self.client, "base_url", "") or ""),
    +                    )
                         if fixed_temperature is not None:
                             api_kwargs["temperature"] = fixed_temperature
     
    ```
  - `run_agent.py`

    ```diff
    @@ -7173,7 +7173,7 @@ def _build_api_kwargs(self, api_messages: list) -> dict:
             except Exception:
                 _fixed_temperature_for_model = None
             if _fixed_temperature_for_model is not None:
    -            fixed_temperature = _fixed_temperature_for_model(self.model)
    +            fixed_temperature = _fixed_temperature_for_model(self.model, self.base_url)
                 if fixed_temperature is not None:
                     api_kwargs["temperature"] = fixed_temperature
             if self._is_qwen_portal():
    ```

    ```diff
    @@ -7619,7 +7619,7 @@ def flush_memories(self, messages: list = None, min_turns: int = None):
                 _aux_available = True
                 # Use the fixed-temperature override (e.g. kimi-for-coding → 0.6) if
                 # the model has a strict contract; otherwise the historical 0.3 default.
    -            _flush_temperature = _fixed_temperature_for_model(self.model)
    +            _flush_temperature = _fixed_temperature_for_model(self.model, self.base_url)
                 if _flush_temperature is None:
                     _flush_temperature = 0.3
                 try:
    ```

    ```diff
    @@ -8675,7 +8675,7 @@ def _handle_max_iterations(self, messages: list, api_call_count: int) -> str:
                 except Exception:
                     _fixed_temperature_for_model = None
                 _summary_temperature = (
    -                _fixed_temperature_for_model(self.model)
    +                _fixed_temperature_for_model(self.model, self.base_url)
                     if _fixed_temperature_for_model is not None
                     else None
                 )
    ```
  - `scripts/release.py`

    ```diff
    @@ -107,6 +107,7 @@
         "239876380+handsdiff@users.noreply.github.com": "handsdiff",
         "[redacted:email]": "etherman-os",
         "[redacted:email]": "mark-ramsell",
    +    "[redacted:email]": "taeng0204",
         "[redacted:email]": "gpickett00",
         "[redacted:email]": "wakamex",
         "[redacted:email]": "clawdia-nash",
    ```
  - `tests/agent/test_auxiliary_client.py`

    ```diff
    @@ -832,6 +832,92 @@ def test_non_restricted_model_preserves_temperature(self, model):
     
             assert kwargs["temperature"] == 0.3
     
    +    # ── Endpoint-aware overrides: api.moonshot.ai vs api.kimi.com/coding ──
    +    # The public Moonshot chat endpoint and the Coding Plan endpoint enforce
    +    # different temperature contracts for the same model name.  `kimi-k2.5` on
    +    # api.moonshot.ai rejects 0.6 with HTTP 400 "only 1 is allowed for this
    +    # model", while the Coding Plan docs mandate 0.6.  Override must pick the
    +    # right value per base_url.
    +
    +    @pytest.mark.parametrize(
    +        "base_url",
    +        [
    +            "https://api.moonshot.ai/v1",
    +            "https://api.moonshot.ai/v1/",
    +            "https://API.MOONSHOT.AI/v1",
    +        ],
    +    )
    +    def test_kimi_k2_5_public_api_forces_temperature_1(self, base_url):
    +        """kimi-k2.5 on the public Moonshot API only accepts temperature=1."""
    +        from agent.auxiliary_client import _build_call_kwargs
    +
    +        kwargs = _build_call_kwargs(
    +            provider="kimi-coding",
    +            model="kimi-k2.5",
    +            messages=[{"role": "user", "content": "hello"}],
    +            temperature=0.1,
    +            base_url=base_url,
    +        )
    +
    +        assert kwargs["temperature"] == 1.0
    +
    +    def test_kimi_k2_5_coding_plan_keeps_temperature_0_6(self):
    +        """kimi-k2.5 on api.kimi.com/coding keeps the Coding Plan's 0.6 lock."""
    +        from agent.auxiliary_client import _build_call_kwargs
    +
    +        kwargs = _build_call_kwargs(
    +            provider="kimi-coding",
    +            model="kimi-k2.5",
    +            messages=[{"role": "user", "content": "hello"}],
    +            temperature=0.1,
    +            base_url="https://api.kimi.com/coding/v1",
    +        )
    +
    +        assert kwargs["temperature"] == 0.6
    +
    +    def test_kimi_k2_5_no_base_url_falls_back_to_coding_plan_lock(self):
    +        """Without a base_url hint, the Coding Plan default (0.6) applies.
    +
    +        Preserves PR #12144 backward compatibility for callers that don't thread
    +        the client's base_url through.
    +        """
    +        from agent.auxiliary_client import _build_call_kwargs
    +
    +        kwargs = _build_call_kwargs(
    +            provider="kimi-coding",
    +            model="kimi-k2.5",
    +            messages=[{"role": "user", "content": "hello"}],
    +            temperature=0.1,
    +        )
    +
    +        assert kwargs["temperature"] == 0.6
    +
    +    @pytest.mark.parametrize(
    +        "model,expected",
    +        [
    +            # Only kimi-k2.5 diverges on api.moonshot.ai; the rest keep the
    +            # Coding Plan lock (empirically verified against Moonshot in April
    +            # 2026: turbo-preview accepts 0.6, thinking-turbo accepts 1.0).
    +            ("kimi-k2-turbo-preview", 0.6),
    +            ("kimi-k2-0905-preview", 0.6),
    +            ("kimi-k2-thinking", 1.0),
    +            ("kimi-k2-thinking-turbo", 1.0),
    +            ("moonshotai/kimi-k2-thinking-turbo", 1.0),
    +        ],
    +    )
    +    def test_other_kimi_k2_family_unchanged_on_public_api(self, model, expected):
    +        from agent.auxiliary_client import _build_call_kwargs
    +
    +        kwargs = _build_call_kwargs(
    +            provider="kimi-coding",
    +            model=model,
    +            messages=[{"role": "user", "content": "hello"}],
    +            temperature=0.1,
    +            base_url="https://api.moonshot.ai/v1",
    +        )
    +
    +        assert kwargs["temperature"] == expected
    +
     
     # ---------------------------------------------------------------------------
     # async_call_llm payment / connection fallback (#7512 bug 2)
    ```
  - `tests/run_agent/test_run_agent.py`

    ```diff
    @@ -918,6 +918,26 @@ def test_basic_kwargs(self, agent):
             assert kwargs["messages"] is messages
             assert kwargs["timeout"] == 1800.0
     
    +    def test_public_moonshot_kimi_k2_5_forces_temperature_1(self, agent):
    +        agent.base_url = "https://api.moonshot.ai/v1"
    +        agent._base_url_lower = agent.base_url.lower()
    +        agent.model = "kimi-k2.5"
    +        messages = [{"role": "user", "content": "hi"}]
    +
    +        kwargs = agent._build_api_kwargs(messages)
    +
    +        assert kwargs["temperature"] == 1.0
    +
    +    def test_kimi_coding_endpoint_keeps_kimi_k2_5_at_0_6(self, agent):
    +        agent.base_url = "https://api.kimi.com/coding/v1"
    +        agent._base_url_lower = agent.base_url.lower()
    +        agent.model = "kimi-k2.5"
    +        messages = [{"role": "user", "content": "hi"}]
    +
    +        kwargs = agent._build_api_kwargs(messages)
    +
    +        assert kwargs["temperature"] == 0.6
    +
         def test_provider_preferences_injected(self, agent):
             agent.base_url = "https://openrouter.ai/api/v1"
             agent.providers_allowed = ["Anthropic"]
    ```
  - `tests/test_mini_swe_runner.py`

    ```diff
    @@ -26,3 +26,30 @@ def test_run_task_forces_kimi_fixed_temperature():
     
         assert result["completed"] is True
         assert client.chat.completions.create.call_args.kwargs["temperature"] == 0.6
    +
    +
    +def test_run_task_public_moonshot_kimi_k2_5_forces_temperature_1():
    +    with patch("openai.OpenAI") as mock_openai:
    +        client = MagicMock()
    +        client.base_url = "https://api.moonshot.ai/v1"
    +        client.chat.completions.create.return_value = SimpleNamespace(
    +            choices=[SimpleNamespace(message=SimpleNamespace(content="done", tool_calls=[]))]
    +        )
    +        mock_openai.return_value = client
    +
    +        from mini_swe_runner import MiniSWERunner
    +
    +        runner = MiniSWERunner(
    +            model="kimi-k2.5",
    +            base_url="https://api.moonshot.ai/v1",
    +            api_key="[redacted:secret-assignment]",
    +            env_type="local",
    +            max_iterations=1,
    +        )
    +        runner._create_env = MagicMock()
    +        runner._cleanup_env = MagicMock()
    +
    +        result = runner.run_task("2+2")
    +
    +    assert result["completed"] is True
    +    assert client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
    ```
  - `tests/test_trajectory_compressor.py`

    ```diff
    @@ -54,6 +54,30 @@ def test_generate_summary_custom_client_forces_kimi_temperature():
         assert compressor.client.chat.completions.create.call_args.kwargs["temperature"] == 0.6
     
     
    +def test_generate_summary_public_moonshot_kimi_k2_5_forces_temperature_1():
    +    config = CompressionConfig(
    +        summarization_model="kimi-k2.5",
    +        base_url="https://api.moonshot.ai/v1",
    +        temperature=0.3,
    +        summary_target_tokens=100,
    +        max_retries=1,
    +    )
    +    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
    +    compressor.config = config
    +    compressor.logger = MagicMock()
    +    compressor._use_call_llm = False
    +    compressor.client = MagicMock()
    +    compressor.client.chat.completions.create.return_value = SimpleNamespace(
    +        choices=[SimpleNamespace(message=SimpleNamespace(content="[CONTEXT SUMMARY]: summary"))]
    +    )
    +
    +    metrics = TrajectoryMetrics()
    +    result = compressor._generate_summary("tool output", metrics)
    +
    +    assert result.startswith("[CONTEXT SUMMARY]:")
    +    assert compressor.client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
    +
    +
     # ---------------------------------------------------------------------------
     # CompressionConfig
     # ---------------------------------------------------------------------------
    ```
  - `tests/test_trajectory_compressor_async.py`

    ```diff
    @@ -141,3 +141,31 @@ async def test_generate_summary_async_custom_client_forces_kimi_temperature():
     
         assert result.startswith("[CONTEXT SUMMARY]:")
         assert async_client.chat.completions.create.call_args.kwargs["temperature"] == 0.6
    +
    +
    [redacted:email]
    +async def test_generate_summary_async_public_moonshot_kimi_k2_5_forces_temperature_1():
    +    from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics
    +
    +    config = CompressionConfig(
    +        summarization_model="kimi-k2.5",
    +        base_url="https://api.moonshot.ai/v1",
    +        temperature=0.3,
    +        summary_target_tokens=100,
    +        max_retries=1,
    +    )
    +    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
    +    compressor.config = config
    +    compressor.logger = MagicMock()
    +    compressor._use_call_llm = False
    +    async_client = MagicMock()
    +    async_client.chat.completions.create = MagicMock(return_value=SimpleNamespace(
    +        choices=[SimpleNamespace(message=SimpleNamespace(content="[CONTEXT SUMMARY]: summary"))]
    +    ))
    +    compressor._get_async_client = MagicMock(return_value=async_client)
    +
    +    metrics = TrajectoryMetrics()
    +    result = await compressor._generate_summary_async("tool output", metrics)
    +
    +    assert result.startswith("[CONTEXT SUMMARY]:")
    +    assert async_client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
    ```
  - `trajectory_compressor.py`

    ```diff
    @@ -54,14 +54,18 @@
     load_hermes_dotenv(hermes_home=_hermes_home, project_env=_project_env)
     
     
    -def _effective_temperature_for_model(model: str, requested_temperature: float) -> float:
    +def _effective_temperature_for_model(
    +    model: str,
    +    requested_temperature: float,
    +    base_url: Optional[str] = None,
    +) -> float:
         """Apply fixed model temperature contracts to direct client calls."""
         try:
             from agent.auxiliary_client import _fixed_temperature_for_model
         except Exception:
             return requested_temperature
     
    -    fixed_temperature = _fixed_temperature_for_model(model)
    +    fixed_temperature = _fixed_temperature_for_model(model, base_url)
         if fixed_temperature is not None:
             return fixed_temperature
         return requested_temperature
    ```

    ```diff
    @@ -583,6 +587,7 @@ def _generate_summary(self, content: str, metrics: TrajectoryMetrics) -> str:
                     summary_temperature = _effective_temperature_for_model(
                         self.config.summarization_model,
                         self.config.temperature,
    +                    self.config.base_url,
                     )
                     
                     if getattr(self, '_use_call_llm', False):
    ```

    ```diff
    @@ -649,6 +654,7 @@ async def _generate_summary_async(self, content: str, metrics: TrajectoryMetrics
                     summary_temperature = _effective_temperature_for_model(
                         self.config.summarization_model,
                         self.config.temperature,
    +                    self.config.base_url,
                     )
                     
                     if getattr(self, '_use_call_llm', False):
    ```
- **#12806: fix: include api.moonshot.cn in public API temperature override (#12745)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#12861: fix(kimi): include api.moonshot.cn in public temperature override**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `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:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -155,7 +155,7 @@ def _fixed_temperature_for_model(
     
         # Public Moonshot API has a stricter contract for some models than the
         # Coding Plan endpoint — check it first so it wins on conflict.
    -    if base_url and "api.moonshot.ai" in base_url.lower():
    +    if base_url and ("api.moonshot.ai" in base_url.lower() or "api.moonshot.cn" in base_url.lower()):
             public = _KIMI_PUBLIC_API_OVERRIDES.get(bare)
             if public is not None:
                 logger.debug(
    ```
  - `tests/agent/test_auxiliary_client.py`

    ```diff
    @@ -845,6 +845,8 @@ def test_non_restricted_model_preserves_temperature(self, model):
                 "https://api.moonshot.ai/v1",
                 "https://api.moonshot.ai/v1/",
                 "https://API.MOONSHOT.AI/v1",
    +            "https://api.moonshot.cn/v1",
    +            "https://api.moonshot.cn/v1/",
             ],
         )
         def test_kimi_k2_5_public_api_forces_temperature_1(self, base_url):
    ```
  - `tests/run_agent/test_run_agent.py`

    ```diff
    @@ -928,6 +928,16 @@ def test_public_moonshot_kimi_k2_5_forces_temperature_1(self, agent):
     
             assert kwargs["temperature"] == 1.0
     
    +    def test_public_moonshot_cn_kimi_k2_5_forces_temperature_1(self, agent):
    +        agent.base_url = "https://api.moonshot.cn/v1"
    +        agent._base_url_lower = agent.base_url.lower()
    +        agent.model = "kimi-k2.5"
    +        messages = [{"role": "user", "content": "hi"}]
    +
    +        kwargs = agent._build_api_kwargs(messages)
    +
    +        assert kwargs["temperature"] == 1.0
    +
         def test_kimi_coding_endpoint_keeps_kimi_k2_5_at_0_6(self, agent):
             agent.base_url = "https://api.kimi.com/coding/v1"
             agent._base_url_lower = agent.base_url.lower()
    ```
  - `tests/test_trajectory_compressor.py`

    ```diff
    @@ -78,6 +78,30 @@ def test_generate_summary_public_moonshot_kimi_k2_5_forces_temperature_1():
         assert compressor.client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
     
     
    +def test_generate_summary_public_moonshot_cn_kimi_k2_5_forces_temperature_1():
    +    config = CompressionConfig(
    +        summarization_model="kimi-k2.5",
    +        base_url="https://api.moonshot.cn/v1",
    +        temperature=0.3,
    +        summary_target_tokens=100,
    +        max_retries=1,
    +    )
    +    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
    +    compressor.config = config
    +    compressor.logger = MagicMock()
    +    compressor._use_call_llm = False
    +    compressor.client = MagicMock()
    +    compressor.client.chat.completions.create.return_value = SimpleNamespace(
    +        choices=[SimpleNamespace(message=SimpleNamespace(content="[CONTEXT SUMMARY]: summary"))]
    +    )
    +
    +    metrics = TrajectoryMetrics()
    +    result = compressor._generate_summary("tool output", metrics)
    +
    +    assert result.startswith("[CONTEXT SUMMARY]:")
    +    assert compressor.client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
    +
    +
     # ---------------------------------------------------------------------------
     # CompressionConfig
     # ---------------------------------------------------------------------------
    ```
  - `tests/test_trajectory_compressor_async.py`

    ```diff
    @@ -169,3 +169,32 @@ async def test_generate_summary_async_public_moonshot_kimi_k2_5_forces_temperatu
     
         assert result.startswith("[CONTEXT SUMMARY]:")
         assert async_client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
    +
    +
    +
    [redacted:email]
    +async def test_generate_summary_async_public_moonshot_cn_kimi_k2_5_forces_temperature_1():
    +    from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics
    +
    +    config = CompressionConfig(
    +        summarization_model="kimi-k2.5",
    +        base_url="https://api.moonshot.cn/v1",
    +        temperature=0.3,
    +        summary_target_tokens=100,
    +        max_retries=1,
    +    )
    +    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
    +    compressor.config = config
    +    compressor.logger = MagicMock()
    +    compressor._use_call_llm = False
    +    async_client = MagicMock()
    +    async_client.chat.completions.create = MagicMock(return_value=SimpleNamespace(
    +        choices=[SimpleNamespace(message=SimpleNamespace(content="[CONTEXT SUMMARY]: summary"))]
    +    ))
    +    compressor._get_async_client = MagicMock(return_value=async_client)
    +
    +    metrics = TrajectoryMetrics()
    +    result = await compressor._generate_summary_async("tool output", metrics)
    +
    +    assert result.startswith("[CONTEXT SUMMARY]:")
    +    assert async_client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
    ```
- **#12862: fix(vision): honor auxiliary.vision.temperature from config**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `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`
- **#115850: fix(auxiliary): vision and other auxiliary calls to gpt-5.x / o-series no longer pay a temperature 400 round-trip (#51083, salvage #51157)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `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`
- **#9191: fix(auxiliary): omit temperature for auto-routed kimi calls**. closed without merge
  - maintainer direction: category `stale_abandoned`; implication `active`; refs `#9125`
- **#11825: fix(kimi): force kimi-for-coding temperature to 0.6**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#11834`
- **#12668: fix(kimi): kimi-k2.5 needs temperature=1.0 on api.moonshot.ai endpoint**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#12773`
- **#12680: fix(kimi): honor public Moonshot temperature contract across direct call paths**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#12773`
- **#12760: fix: move kimi-k2.5 from instant to thinking models**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#12773`
- **#12772: fix: move kimi-k2.5 to thinking models**. closed without merge
  - maintainer direction: category `stale_abandoned`; implication `active`
- **#51157: fix(auxiliary): OMIT temperature for gpt-5.5 family (#51083)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `ada97e3bac36`, `#115850`, `#51083`, `#51157`
- **#53495: fix(temperature): set temperature=1 for Kimi coding models on /coding/ endpoint (#53455)**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `agent/auxiliary_client.py`, `tests/agent/test_arcee_trinity_overrides.py`

## Fix status

- Merged to main in PR #12773 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-09-19`.
- Subsystem: `agent`
