From 5712e208ee4c0d372afd1d7c8c322efcd00ddf6a Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:30:48 +0800 Subject: [PATCH 1/3] chore(dsh): pin the managed dsh host to the latest released SDK The `deepseek-harness` extra now pins `deepseek-harness-sdk==0.1.5rc1` and its bundled `deepseek-harness-runtime-bin==0.1.5rc1`, the newest release channel published for dsh (PyPI for the wheels, `latest` on npm for `@deepseek-ai/dsh`). The Python client surface is unchanged from the previously pinned `0.1.2a3`; the bump moves the bundled dsh runtime behind the managed host. Validation (hermetic, local mock LLM, no provider call): - `examples/loopx-turn-dsh-real-e2e-smoke.py --host generic-cli` and `--host dsh` both pass against 0.1.2a3 (control) and 0.1.5rc1. - `tests/test_dsh_goal_mode.py` (55 passed), `examples/dsh-turn-host-adapter-smoke.py` (11 checks), `examples/loopx-turn-dsh-e2e-smoke.py`, `examples/loopx-turn-dsh-builtin-host-e2e-smoke.py` all pass. Signed-off-by: Codex Product Agent Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- docs/integrations/deepseek-harness-connector.md | 10 +++++++++- loopx/dsh_goal_mode/README.md | 2 +- pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/integrations/deepseek-harness-connector.md b/docs/integrations/deepseek-harness-connector.md index 1b73bf7a6b..e22f82d05c 100644 --- a/docs/integrations/deepseek-harness-connector.md +++ b/docs/integrations/deepseek-harness-connector.md @@ -29,7 +29,7 @@ LoopX quota should-run - A `deepseek-harness` agent type in LoopX onboarding so users can request the exact host instead of the generic `other-agent`. - Optional dependency `loopx[deepseek-harness]` for the validated - `deepseek-harness-sdk==0.1.2a3` Python client. + `deepseek-harness-sdk==0.1.5rc1` Python client. ## Install @@ -39,6 +39,14 @@ Install LoopX's optional DeepSeek Harness extra: python -m pip install 'loopx[deepseek-harness]' ``` +The pin tracks the newest published dsh release channel rather than an +unreleased tag: `0.1.5rc1` for the PyPI SDK/runtime wheels and `0.1.5-rc.1` for +the npm `@deepseek-ai/dsh` `latest` tag. Upstream also publishes newer +`next`/`alpha` tags that are not the released channel. This release keeps the +Python client surface of the previously pinned `0.1.2a3` and moves the bundled +dsh runtime; LoopX selects the SDK's default `sdk` profile unless the operator +supplies an explicit cordis composition. + The DeepSeek Harness SDK spawns the bundled `dsh-jsonrpc-agent` runtime. It uses the explicit adapter configuration plus normal provider environment variables: diff --git a/loopx/dsh_goal_mode/README.md b/loopx/dsh_goal_mode/README.md index ec8a63e2ce..a775df8d55 100644 --- a/loopx/dsh_goal_mode/README.md +++ b/loopx/dsh_goal_mode/README.md @@ -86,7 +86,7 @@ contract failure instead of collapsing into `unknown`. ## Requirements - Optional dependency group `loopx[deepseek-harness]`, currently pinned to the - validated `deepseek-harness-sdk==0.1.2a3` API, or a compatible runner via + validated `deepseek-harness-sdk==0.1.5rc1` API, or a compatible runner via `--dsh-runner`. - A dsh `cordis.yml` plus any `DEEPSEEK_API_KEY` / `DEEPSEEK_BASE_URL` settings for the real runtime. diff --git a/pyproject.toml b/pyproject.toml index f559e7b879..444d633463 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ Changelog = "https://github.com/huangruiteng/loopx/releases" [project.optional-dependencies] deepseek-harness = [ - "deepseek-harness-sdk==0.1.2a3", + "deepseek-harness-sdk==0.1.5rc1", ] test = [ "jsonschema>=4.23,<5", From b09a372f2c75211d4c10842299770a07375980d4 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Tue, 15 Sep 2026 09:39:21 +0800 Subject: [PATCH 2/3] feat(turn): bind the default Turn host to the operator credential `loopx turn run-once` defaulted to `generic-cli` and `loopx turn plan` hard-coded `codex-cli`, so a machine with an operator-supplied model credential still needed an explicit `--host dsh` to keep governed Turns off an individual CLI subscription. Default host resolution now follows one typed rule in `control_plane/turn_driver/host_binding.py`: a non-empty `DEEPSEEK_API_KEY` selects the `dsh` host, and an unconfigured credential keeps `codex-cli`. `DEEPSEEK_BASE_URL` chooses the endpoint but does not switch hosts, an explicit `--host` still wins, and `turn plan` now accepts the `dsh` host it can already route. Tests that encoded the former `generic-cli` default pass it explicitly now, and `tests/test_turn_default_host_binding.py` covers the resolution matrix plus both CLI defaults. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../loopx-turn-path-delta-acceptance-smoke.py | 2 + loopx/cli_commands/turn_registration.py | 15 +++- .../control_plane/turn_driver/host_binding.py | 48 ++++++++++++ tests/control_plane/test_cli_output_budget.py | 2 + tests/test_loopx_turn_driver.py | 10 +++ tests/test_turn_default_host_binding.py | 77 +++++++++++++++++++ 6 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 loopx/control_plane/turn_driver/host_binding.py create mode 100644 tests/test_turn_default_host_binding.py diff --git a/examples/loopx-turn-path-delta-acceptance-smoke.py b/examples/loopx-turn-path-delta-acceptance-smoke.py index 4c5c97f4cd..e6193a6b9b 100644 --- a/examples/loopx-turn-path-delta-acceptance-smoke.py +++ b/examples/loopx-turn-path-delta-acceptance-smoke.py @@ -194,6 +194,8 @@ def _run_turn( "json", "turn", "run-once", + "--host", + "generic-cli", "--goal-id", GOAL_ID, "--agent-id", diff --git a/loopx/cli_commands/turn_registration.py b/loopx/cli_commands/turn_registration.py index b11e9fbcbd..c6e3bde8d9 100644 --- a/loopx/cli_commands/turn_registration.py +++ b/loopx/cli_commands/turn_registration.py @@ -5,8 +5,13 @@ import argparse from collections.abc import Callable +from ..control_plane.turn_driver.host_binding import resolve_default_turn_host from ..paths import default_public_scan_root +# Explicit host choices stay per-command: planning may name any host the Turn +# driver routes, while run-once only ships built-in adapters for these three. +PLANNED_TURN_HOST_CHOICES = ["codex-cli", "claude-code", "dsh", "generic-cli"] +RUN_ONCE_TURN_HOST_CHOICES = ["codex-cli", "dsh", "generic-cli"] AddFormat = Callable[[argparse.ArgumentParser], None] @@ -42,7 +47,11 @@ def register_turn_commands( help="Build one typed read-only host decision without launching or writing.", ) add_subcommand_format(plan) - _add_turn_decision_arguments(plan, default_host="codex-cli") + _add_turn_decision_arguments( + plan, + default_host=resolve_default_turn_host(), + host_choices=list(PLANNED_TURN_HOST_CHOICES), + ) plan.add_argument( "--include-transaction-detail", action="store_true", @@ -84,8 +93,8 @@ def register_turn_commands( add_subcommand_format(run_once) _add_turn_decision_arguments( run_once, - default_host="generic-cli", - host_choices=["codex-cli", "dsh", "generic-cli"], + default_host=resolve_default_turn_host(), + host_choices=list(RUN_ONCE_TURN_HOST_CHOICES), execution_mode_choices=["isolated-headless"], default_execution_mode="isolated-headless", ) diff --git a/loopx/control_plane/turn_driver/host_binding.py b/loopx/control_plane/turn_driver/host_binding.py new file mode 100644 index 0000000000..5afab83a51 --- /dev/null +++ b/loopx/control_plane/turn_driver/host_binding.py @@ -0,0 +1,48 @@ +"""Credential-resolved default Turn host binding. + +The default host for ``loopx turn plan`` and ``loopx turn run-once`` is decided +by what the operator configured, not by the harness alone: + +- a configured operator model credential selects the DeepSeek Harness host + (``dsh``), so the bounded Turn runs on the operator-supplied endpoint instead + of any individual's CLI subscription; +- with no operator credential configured the default stays the Codex CLI host + (``codex-cli``). + +Resolution is a pure function of the environment so the command defaults, the +Turn plan readback, and tests quote one rule instead of drifting apart. +""" + +from __future__ import annotations + +import os +from collections.abc import Mapping + +HOST_WITH_OPERATOR_CREDENTIAL = "dsh" +HOST_WITHOUT_OPERATOR_CREDENTIAL = "codex-cli" + +# Credentials the DSH Turn host already reads for its provider. Presence is the +# whole signal: the binding never invents a credential or falls back to a +# personal subscription when one is configured. +OPERATOR_CREDENTIAL_ENV_VARS = ("DEEPSEEK_API_KEY",) +OPERATOR_ENDPOINT_ENV_VAR = "DEEPSEEK_BASE_URL" + + +def configured_operator_credential( + environ: Mapping[str, str] | None = None, +) -> str | None: + """Return the configured operator credential env var name, else ``None``.""" + + source = os.environ if environ is None else environ + for name in OPERATOR_CREDENTIAL_ENV_VARS: + if str(source.get(name, "") or "").strip(): + return name + return None + + +def resolve_default_turn_host(environ: Mapping[str, str] | None = None) -> str: + """Return the shipped default Turn host for this operator environment.""" + + if configured_operator_credential(environ) is not None: + return HOST_WITH_OPERATOR_CREDENTIAL + return HOST_WITHOUT_OPERATOR_CREDENTIAL diff --git a/tests/control_plane/test_cli_output_budget.py b/tests/control_plane/test_cli_output_budget.py index 9f65be4bb4..8c16665160 100644 --- a/tests/control_plane/test_cli_output_budget.py +++ b/tests/control_plane/test_cli_output_budget.py @@ -729,6 +729,8 @@ def _mode_variant_commands( + [ "turn", "run-once", + "--host", + "generic-cli", "--goal-id", GOAL_ID, "--agent-id", diff --git a/tests/test_loopx_turn_driver.py b/tests/test_loopx_turn_driver.py index a55c52b954..bd98280dc2 100644 --- a/tests/test_loopx_turn_driver.py +++ b/tests/test_loopx_turn_driver.py @@ -1713,6 +1713,8 @@ def test_turn_run_once_cli_commits_validated_result_and_one_quota_slot( "json", "turn", "run-once", + "--host", + "generic-cli", "--goal-id", "loopx-turn-fixture", "--agent-id", @@ -1782,6 +1784,8 @@ def test_turn_run_once_cli_commits_validated_result_and_one_quota_slot( "json", "turn", "run-once", + "--host", + "generic-cli", "--goal-id", "loopx-turn-fixture", "--agent-id", @@ -1954,6 +1958,8 @@ def test_turn_run_once_cli_completes_selected_todo_after_validation( "json", "turn", "run-once", + "--host", + "generic-cli", "--goal-id", "loopx-turn-fixture", "--agent-id", @@ -2195,6 +2201,8 @@ def _turn_run_once_completion_argv( "json", "turn", "run-once", + "--host", + "generic-cli", "--goal-id", "loopx-turn-fixture", "--agent-id", @@ -2811,6 +2819,8 @@ def test_turn_run_once_cli_rejects_unproven_host_claim_before_writeback( "json", "turn", "run-once", + "--host", + "generic-cli", "--goal-id", "loopx-turn-fixture", "--agent-id", diff --git a/tests/test_turn_default_host_binding.py b/tests/test_turn_default_host_binding.py new file mode 100644 index 0000000000..74e3cb9a50 --- /dev/null +++ b/tests/test_turn_default_host_binding.py @@ -0,0 +1,77 @@ +"""The default Turn host follows the operator's configured credential.""" + +from __future__ import annotations + +import pytest + +from loopx.cli import build_parser +from loopx.control_plane.turn_driver.host_binding import ( + HOST_WITHOUT_OPERATOR_CREDENTIAL, + HOST_WITH_OPERATOR_CREDENTIAL, + configured_operator_credential, + resolve_default_turn_host, +) + + +@pytest.mark.parametrize( + ("environ", "expected"), + [ + ({}, HOST_WITHOUT_OPERATOR_CREDENTIAL), + ({"DEEPSEEK_API_KEY": "sk-operator"}, HOST_WITH_OPERATOR_CREDENTIAL), + ({"DEEPSEEK_API_KEY": ""}, HOST_WITHOUT_OPERATOR_CREDENTIAL), + ({"DEEPSEEK_API_KEY": " "}, HOST_WITHOUT_OPERATOR_CREDENTIAL), + ( + {"DEEPSEEK_BASE_URL": "https://example.invalid"}, + HOST_WITHOUT_OPERATOR_CREDENTIAL, + ), + ], +) +def test_default_host_follows_credential_presence(environ, expected): + assert resolve_default_turn_host(environ) == expected + + +def test_configured_credential_names_the_env_var(): + assert ( + configured_operator_credential({"DEEPSEEK_API_KEY": "sk-operator"}) + == "DEEPSEEK_API_KEY" + ) + assert configured_operator_credential({}) is None + + +def _turn_argv(command: str) -> list[str]: + argv = ["turn", command, "--goal-id", "goal-x", "--agent-id", "agent-x"] + if command == "run-once": + argv.extend(["--project", "."]) + return argv + + +@pytest.mark.parametrize("command", ["plan", "run-once"]) +def test_cli_defaults_to_dsh_when_the_operator_credential_is_configured( + command, monkeypatch +): + monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-operator") + + args = build_parser().parse_args(_turn_argv(command)) + + assert args.host == HOST_WITH_OPERATOR_CREDENTIAL + + +@pytest.mark.parametrize("command", ["plan", "run-once"]) +def test_cli_defaults_to_codex_cli_without_an_operator_credential( + command, monkeypatch +): + monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False) + + args = build_parser().parse_args(_turn_argv(command)) + + assert args.host == HOST_WITHOUT_OPERATOR_CREDENTIAL + + +def test_explicit_host_still_wins_over_the_credential_default(monkeypatch): + monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-operator") + + args = build_parser().parse_args( + [*_turn_argv("run-once"), "--host", "generic-cli"] + ) + + assert args.host == "generic-cli" From bcade15be1e86f62fdb08117cb4ab464955b5d2b Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Tue, 15 Sep 2026 09:39:27 +0800 Subject: [PATCH 3/3] docs(turn): disclose the credential-resolved default Turn host Record the shipped default-host rule on the connector surface: a configured `DEEPSEEK_API_KEY` selects `dsh`, no configured credential keeps `codex-cli`, `DEEPSEEK_BASE_URL` does not switch hosts on its own, an explicit `--host` wins, and the resolved value is what `turn plan` reports back. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../deepseek-harness-connector.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/integrations/deepseek-harness-connector.md b/docs/integrations/deepseek-harness-connector.md index e22f82d05c..9d5a52d30b 100644 --- a/docs/integrations/deepseek-harness-connector.md +++ b/docs/integrations/deepseek-harness-connector.md @@ -66,6 +66,28 @@ appropriate. See the [DeepSeek Harness Python SDK reference](https://github.com/deepseek-ai/deepseek-harness/blob/master/python/sdk/README.md) for runtime selection and configuration. +## Default Host Selection + +`loopx turn plan` and `loopx turn run-once` bind their default `--host` to the +operator's own provider credential rather than to the harness alone: + +| Operator configuration | Default `--host` | +| --- | --- | +| `DEEPSEEK_API_KEY` is set to a non-empty value | `dsh` | +| no such credential is configured | `codex-cli` | + +A machine with an operator-supplied model credential therefore runs governed +Turns against that endpoint instead of an individual CLI subscription, and a +machine without one keeps the Codex CLI host. Surrounding whitespace does not +count as a configured credential, so an exported-but-empty variable stays on +the `codex-cli` default. + +`DEEPSEEK_BASE_URL` chooses the endpoint the credential is used against; on its +own it does not change the default host. An explicit `--host` always wins, and +the resolved value is what `loopx turn plan` reports back. `loopx turn run-once` +accepts the `codex-cli`, `dsh`, and `generic-cli` hosts it ships adapters for; +`loopx turn plan` additionally accepts the planning-only `claude-code` host. + ## Onboard ```bash