From 31699f9c0417a572b05a79d77b2edebfc32884d0 Mon Sep 17 00:00:00 2001 From: Lee Jackson <130007945+Imagineer99@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:13:54 +0100 Subject: [PATCH] Default coding-agent servers to reasoning off (#7521) * Default coding agent servers to reasoning off * Fix reasoning startup compatibility and attach warning --- unsloth_cli/commands/start.py | 35 ++++++++++++++++++++++++++++++++ unsloth_cli/tests/test_start.py | 36 +++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/unsloth_cli/commands/start.py b/unsloth_cli/commands/start.py index ed1ee7bd5a..3ae0276473 100644 --- a/unsloth_cli/commands/start.py +++ b/unsloth_cli/commands/start.py @@ -225,6 +225,16 @@ _TOOL_CALL_NUDGING_OPTION = typer.Option( help = "Retry once with a nudge when a non-streaming passthrough tool call can't be healed. " "On by default; when the flag is omitted an inherited UNSLOTH_TOOL_CALL_NUDGE is kept.", ) +_REASONING_OPTION = typer.Option( + None, + "--reasoning", + rich_help_panel = _PANEL_SERVER, + help = ( + "llama-server reasoning mode for an auto-started coding-agent server. " + "Defaults to off so tool calls stay in the structured tool channel; use " + "'auto' or 'on' to opt back into model reasoning." + ), +) # Sampling overrides pin a value on the auto-started server (winning over the client and the # per-model recommendation). Default unset -> the model's recommended sampling is used. _TEMPERATURE_OPTION = typer.Option( @@ -479,6 +489,7 @@ class ServerOptions(NamedTuple): enable_tools: bool = False tool_call_healing: Optional[bool] = None tool_call_nudging: Optional[bool] = None + reasoning: Optional[Literal["on", "off", "auto"]] = None temperature: Optional[float] = None top_p: Optional[float] = None top_k: Optional[int] = None @@ -1020,6 +1031,10 @@ def _start_studio_server( # Own session/process group so a mid-session Ctrl+C (cancel a turn) doesn't reach the # server. It survives a successful agent session; torn down on startup/launch failure. child_env = os.environ.copy() + # Current llama-server versions read this documented env equivalent of --reasoning. + # Older managed versions ignore an unknown env variable instead of failing startup on + # an unknown passthrough CLI flag. An omitted start option still defaults to off. + child_env["LLAMA_ARG_REASONING"] = server.reasoning or "off" # Pass the marker via env so an older launcher ignores it instead of treating an # unknown CLI flag as a llama-server arg; new launchers preserve it across re-exec. child_env[_START_API_KEY_MARKER_ENV] = "1" @@ -1159,6 +1174,14 @@ def _require_studio( "and re-run to apply them.", err = True, ) + if server_options.reasoning is not None: + typer.echo( + f"Warning: an Unsloth server is already running at {base}; " + f"--reasoning {server_options.reasoning} applies only when this command starts " + "the server, so the running server keeps its current reasoning mode. Stop it " + "with `unsloth studio stop` and re-run to apply the override.", + err = True, + ) return base, None expected = os.environ.get("UNSLOTH_STUDIO_URL", "http://127.0.0.1:8888").rstrip("/") # Auto-start a local server only for an interactive launch with a model to serve, and @@ -3048,6 +3071,7 @@ def claude( enable_tools: bool = _ENABLE_TOOLS_OPTION, tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION, tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION, + reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION, temperature: Optional[float] = _TEMPERATURE_OPTION, top_p: Optional[float] = _TOP_P_OPTION, top_k: Optional[int] = _TOP_K_OPTION, @@ -3072,6 +3096,7 @@ def claude( enable_tools = enable_tools, tool_call_healing = tool_call_healing, tool_call_nudging = tool_call_nudging, + reasoning = reasoning, temperature = temperature, top_p = top_p, top_k = top_k, @@ -3166,6 +3191,7 @@ def codex( enable_tools: bool = _ENABLE_TOOLS_OPTION, tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION, tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION, + reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION, temperature: Optional[float] = _TEMPERATURE_OPTION, top_p: Optional[float] = _TOP_P_OPTION, top_k: Optional[int] = _TOP_K_OPTION, @@ -3190,6 +3216,7 @@ def codex( enable_tools = enable_tools, tool_call_healing = tool_call_healing, tool_call_nudging = tool_call_nudging, + reasoning = reasoning, temperature = temperature, top_p = top_p, top_k = top_k, @@ -3265,6 +3292,7 @@ def openclaw( enable_tools: bool = _ENABLE_TOOLS_OPTION, tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION, tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION, + reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION, temperature: Optional[float] = _TEMPERATURE_OPTION, top_p: Optional[float] = _TOP_P_OPTION, top_k: Optional[int] = _TOP_K_OPTION, @@ -3289,6 +3317,7 @@ def openclaw( enable_tools = enable_tools, tool_call_healing = tool_call_healing, tool_call_nudging = tool_call_nudging, + reasoning = reasoning, temperature = temperature, top_p = top_p, top_k = top_k, @@ -3346,6 +3375,7 @@ def opencode( enable_tools: bool = _ENABLE_TOOLS_OPTION, tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION, tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION, + reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION, temperature: Optional[float] = _TEMPERATURE_OPTION, top_p: Optional[float] = _TOP_P_OPTION, top_k: Optional[int] = _TOP_K_OPTION, @@ -3370,6 +3400,7 @@ def opencode( enable_tools = enable_tools, tool_call_healing = tool_call_healing, tool_call_nudging = tool_call_nudging, + reasoning = reasoning, temperature = temperature, top_p = top_p, top_k = top_k, @@ -3507,6 +3538,7 @@ def hermes( enable_tools: bool = _ENABLE_TOOLS_OPTION, tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION, tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION, + reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION, temperature: Optional[float] = _TEMPERATURE_OPTION, top_p: Optional[float] = _TOP_P_OPTION, top_k: Optional[int] = _TOP_K_OPTION, @@ -3533,6 +3565,7 @@ def hermes( enable_tools = enable_tools, tool_call_healing = tool_call_healing, tool_call_nudging = tool_call_nudging, + reasoning = reasoning, temperature = temperature, top_p = top_p, top_k = top_k, @@ -3564,6 +3597,7 @@ def pi( enable_tools: bool = _ENABLE_TOOLS_OPTION, tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION, tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION, + reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION, temperature: Optional[float] = _TEMPERATURE_OPTION, top_p: Optional[float] = _TOP_P_OPTION, top_k: Optional[int] = _TOP_K_OPTION, @@ -3588,6 +3622,7 @@ def pi( enable_tools = enable_tools, tool_call_healing = tool_call_healing, tool_call_nudging = tool_call_nudging, + reasoning = reasoning, temperature = temperature, top_p = top_p, top_k = top_k, diff --git a/unsloth_cli/tests/test_start.py b/unsloth_cli/tests/test_start.py index 608baa6e4c..2d4be513fc 100644 --- a/unsloth_cli/tests/test_start.py +++ b/unsloth_cli/tests/test_start.py @@ -1993,6 +1993,8 @@ def test_start_studio_server_forwards_tool_flags_via_command_and_env(monkeypatch start._start_studio_server("http://127.0.0.1:8888", "unsloth/M-GGUF", start.LoadOptions()) cmd, env = captured["command"], captured["kwargs"]["env"] assert "--disable-tools" in cmd and "--enable-tools" not in cmd + assert "--reasoning" not in cmd + assert env["LLAMA_ARG_REASONING"] == "off" assert "--gpu-memory-mode" not in cmd assert env["UNSLOTH_DISABLE_TOOL_CALL_HEALING"] == "0" assert env["UNSLOTH_TOOL_CALL_NUDGE"] == "1" @@ -2002,10 +2004,17 @@ def test_start_studio_server_forwards_tool_flags_via_command_and_env(monkeypatch "http://127.0.0.1:8888", "unsloth/M-GGUF", start.LoadOptions(), - start.ServerOptions(enable_tools = True, tool_call_healing = False, tool_call_nudging = False), + start.ServerOptions( + enable_tools = True, + tool_call_healing = False, + tool_call_nudging = False, + reasoning = "auto", + ), ) cmd, env = captured["command"], captured["kwargs"]["env"] assert "--enable-tools" in cmd and "--disable-tools" not in cmd + assert "--reasoning" not in cmd + assert env["LLAMA_ARG_REASONING"] == "auto" assert env["UNSLOTH_DISABLE_TOOL_CALL_HEALING"] == "1" assert env["UNSLOTH_TOOL_CALL_NUDGE"] == "0" @@ -2118,7 +2127,25 @@ def test_require_studio_no_sampling_warning_without_pins(monkeypatch, capsys): server_options = start.ServerOptions(enable_tools = True), ) assert base == BASE and server is None - assert "sampling" not in capsys.readouterr().err.lower() + assert capsys.readouterr().err == "" + + +@pytest.mark.parametrize("reasoning", ["on", "off", "auto"]) +def test_require_studio_warns_on_explicit_reasoning_when_reusing_server( + monkeypatch, capsys, reasoning +): + monkeypatch.setattr(start, "find_studio_server", lambda: BASE) + base, server = start._require_studio( + "unsloth/M-GGUF", + start.LoadOptions(), + serve = True, + server_options = start.ServerOptions(reasoning = reasoning), + ) + assert base == BASE and server is None + err = capsys.readouterr().err + assert "already running" in err + assert f"--reasoning {reasoning}" in err + assert "unsloth studio stop" in err def test_start_claude_parses_sampling_flags(fake_studio, monkeypatch): @@ -2153,11 +2180,14 @@ def test_start_claude_parses_sampling_flags(fake_studio, monkeypatch): "0.3", "--top-k", "40", + "--reasoning", + "on", ], ) assert result.exit_code == 0, result.output so = captured["server_options"] assert so.temperature == 0.3 and so.top_k == 40 and so.top_p is None + assert so.reasoning == "on" def test_connect_model_bare_id_matches_loaded_without_reload(fake_studio): @@ -2681,6 +2711,8 @@ def test_start_studio_server_builds_command_and_waits(monkeypatch, capsys): cmd = captured["command"] assert cmd[1] == "run" assert "--disable-tools" in cmd and "--no-cloudflare" in cmd + assert "--reasoning" not in cmd + assert captured["kwargs"]["env"]["LLAMA_ARG_REASONING"] == "off" assert cmd[cmd.index("--model") + 1] == "unsloth/Qwen3-1.7B-GGUF:UD-Q4_K_XL" assert cmd[cmd.index("--gguf-variant") + 1] == "UD-Q4_K_XL" assert cmd[cmd.index("--context-length") + 1] == "8192"