From 54e6b60650c524a9183505a991d38507651bde7a Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:12:59 +0800 Subject: [PATCH 1/3] feat(turn): resolve the default host from the operator credential The shipped default was the managed dsh host regardless of the credential, so a lane without one failed closed on operator_credential_unconfigured. Resolve the default from the operator's own credential facts instead: a configured credential keeps the managed dsh default, and no credential resolves the individual codex-cli host that can actually run here. An explicit --host or LOOPX_TURN_HOST still wins over either default, so a credential never re-points a host the operator already selected; it only resolves the default that would otherwise have to be chosen without any evidence. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- loopx/cli_commands/turn_registration.py | 2 +- loopx/control_plane/operator_credential.py | 24 ++++---- .../control_plane/turn_driver/host_binding.py | 55 ++++++++++++------- loopx/semantics/inventory_v0.json | 2 +- 4 files changed, 50 insertions(+), 33 deletions(-) diff --git a/loopx/cli_commands/turn_registration.py b/loopx/cli_commands/turn_registration.py index f92b2a4304..b4249ad752 100644 --- a/loopx/cli_commands/turn_registration.py +++ b/loopx/cli_commands/turn_registration.py @@ -53,7 +53,7 @@ def register_turn_commands( # The default host and the default execution mode are one decision: the # selected managed host runs bounded headless Turns, so pairing it with a # visible interactive mode would produce a default plan that cannot be - # scheduled. The mode follows the *selected* host, never the environment. + # scheduled. The mode follows the *selected* host, whatever resolved it. resolved_default_host = resolve_default_turn_host() resolved_default_execution_mode = ( "isolated-headless" diff --git a/loopx/control_plane/operator_credential.py b/loopx/control_plane/operator_credential.py index 0d47e889f6..ea4c328a50 100644 --- a/loopx/control_plane/operator_credential.py +++ b/loopx/control_plane/operator_credential.py @@ -1,17 +1,17 @@ """Operator-supplied model credential facts shared by LoopX host surfaces. -This module reports credential *facts* and nothing else. It never selects a -host, an endpoint, or a model, and it never reads a credential value. - -Selection is a separate, explicit decision owned by the surface that runs the -work: the governed Turn host comes from -``turn_driver.host_binding.selected_turn_host`` and the steward channel endpoint -comes from ``chat_manager.manager_channel_binding``. Both report the credential -facts quoted from here so their readback cannot drift apart, and both treat the -credential as authentication for the configuration the operator selected -- -never as a reason to change it. Discovering that a credential exists may help -the operator set a surface up, but it must not silently re-point a surface that -is already configured. +This module reports credential *facts* and nothing else. It reads no credential +value, and it resolves nothing by itself. + +Selection is a separate decision owned by the surface that runs the work: the +governed Turn host comes from ``turn_driver.host_binding.selected_turn_host`` +and the steward channel endpoint comes from +``chat_manager.manager_channel_binding``. Both report the credential facts +quoted from here so their readback cannot drift apart, and both treat the +credential as authentication for the configuration that runs. A configured +credential is never a reason to re-point an explicitly selected surface: it +resolves only the shipped default of a surface that would otherwise have to run +on an individual CLI login. """ from __future__ import annotations diff --git a/loopx/control_plane/turn_driver/host_binding.py b/loopx/control_plane/turn_driver/host_binding.py index 278f040301..8dd0bf8e17 100644 --- a/loopx/control_plane/turn_driver/host_binding.py +++ b/loopx/control_plane/turn_driver/host_binding.py @@ -1,15 +1,22 @@ -"""Explicit Turn host selection and managed executor readback. - -The Turn host is **selected, never inferred**. LoopX ships one explicit product -default, the operator may override it explicitly, and a discovered credential -only authenticates the host that was already selected. The presence of -``DEEPSEEK_API_KEY`` therefore never changes where a Turn runs; setting it is -what makes the selected managed host authenticated. - -- ``MANAGED_DEFAULT_TURN_HOST`` (``dsh``) is the shipped default: the managed - execution unit the steward drives runs on the DeepSeek Harness host. -- ``LOOPX_TURN_HOST`` re-points that default without repeating ``--host``. -- an explicit ``--host`` always wins over both. +"""Credential-resolved default Turn host and managed executor readback. + +The Turn host is **selected, never inferred from a launch-time surprise**. An +explicit ``--host`` or ``LOOPX_TURN_HOST`` is always honoured, and the shipped +default is resolved once from the operator's own credential facts. + +- an operator credential (``DEEPSEEK_API_KEY``) selects the managed default + ``dsh``: the managed execution unit the steward drives runs on the DeepSeek + Harness host, billed to the operator's own endpoint; +- with no credential configured the individual default ``codex-cli`` applies + instead, because the managed host cannot be authenticated without one -- and + refusing to run is worse than running the individual CLI host this machine + can already use; +- an explicit selection is never re-pointed by a credential: configuring or + removing ``DEEPSEEK_API_KEY`` moves the shipped default only, never a host + the operator already selected. + +Both defaults are read back with their source, so an operator can always tell a +product default from an explicit selection instead of inferring it. ``managed_executor_binding`` turns the selection plus the operator environment into the readback a caller can act on before a Turn runs: which executor the @@ -31,14 +38,18 @@ env_text, ) -# Explicit selection surfaces. The default is a product decision recorded here -# once; nothing in this module reads the environment to decide *which* host runs. +# The shipped default is resolved from one fact: whether the operator configured +# a credential for the managed endpoint. An explicit selection always wins over +# this default, and nothing else in this module reads the environment to decide +# *which* host runs. MANAGED_TURN_HOST = "dsh" INDIVIDUAL_TURN_HOST = "codex-cli" MANAGED_DEFAULT_TURN_HOST = MANAGED_TURN_HOST +INDIVIDUAL_DEFAULT_TURN_HOST = INDIVIDUAL_TURN_HOST TURN_HOST_ENV_VAR = "LOOPX_TURN_HOST" -TURN_HOST_SOURCE_PRODUCT_DEFAULT = "product_default" TURN_HOST_SOURCE_EXPLICIT_CONFIG = "explicit_config" +TURN_HOST_SOURCE_OPERATOR_CREDENTIAL = "operator_credential" +TURN_HOST_SOURCE_NO_OPERATOR_CREDENTIAL = "no_operator_credential" MANAGED_EXECUTOR_BINDING_SCHEMA_VERSION = "managed_executor_binding_v0" # Executor kinds name where a Turn's model work is billed and bounded rather @@ -68,15 +79,21 @@ def selected_turn_host( ) -> tuple[str, str]: """Return the selected default Turn host and the source that selected it. - Selection is environment-independent: the shipped product default applies - until the operator re-points it explicitly with ``LOOPX_TURN_HOST``. A - configured credential is never a selection signal. + An explicit ``LOOPX_TURN_HOST`` wins. Otherwise the operator's own + credential facts resolve the shipped default: a configured operator + credential runs the managed host on that credential, and its absence runs + the individual CLI host instead of a managed host nothing can authenticate. """ explicit = env_text(TURN_HOST_ENV_VAR, environ) if explicit: return explicit, TURN_HOST_SOURCE_EXPLICIT_CONFIG - return MANAGED_DEFAULT_TURN_HOST, TURN_HOST_SOURCE_PRODUCT_DEFAULT + if configured_operator_credential(environ): + return MANAGED_DEFAULT_TURN_HOST, TURN_HOST_SOURCE_OPERATOR_CREDENTIAL + return ( + INDIVIDUAL_DEFAULT_TURN_HOST, + TURN_HOST_SOURCE_NO_OPERATOR_CREDENTIAL, + ) def resolve_default_turn_host(environ: Mapping[str, str] | None = None) -> str: diff --git a/loopx/semantics/inventory_v0.json b/loopx/semantics/inventory_v0.json index 0653aae0ae..88b0f6d211 100644 --- a/loopx/semantics/inventory_v0.json +++ b/loopx/semantics/inventory_v0.json @@ -907,7 +907,7 @@ "python_closed_sets": 495, "python_literal_aliases": 8, "typescript_const_arrays": 40, - "named_string_constants": 2030, + "named_string_constants": 2031, "schema_version_names": 756, "schema_version_same_runtime_forks": 7, "cross_runtime_twins": 166, From 3ddeedf7a77047acb6a4f11ef0699900958cf986 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:13:04 +0800 Subject: [PATCH 2/3] test(turn): qualify the credential-resolved default host Rewrite the two host-binding test modules so they encode the resolved default instead of the previous fixed one, and update both public smokes to prove it end to end: without a credential the default plan resolves to codex-cli and claims no managed credential, and an explicitly selected dsh host still fails closed on the typed operator_credential_unconfigured reason. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../loopx-turn-managed-default-flow-smoke.py | 32 ++++---- ...opx-turn-managed-executor-binding-smoke.py | 28 ++++--- tests/test_turn_default_host_binding.py | 79 +++++++++++++------ tests/test_turn_managed_executor_binding.py | 38 +++++---- 4 files changed, 108 insertions(+), 69 deletions(-) diff --git a/examples/loopx-turn-managed-default-flow-smoke.py b/examples/loopx-turn-managed-default-flow-smoke.py index 0bb978854c..4cd8bd72df 100644 --- a/examples/loopx-turn-managed-default-flow-smoke.py +++ b/examples/loopx-turn-managed-default-flow-smoke.py @@ -1,20 +1,23 @@ #!/usr/bin/env python3 -"""Qualify the explicit operator default flow for one bounded managed Turn. +"""Qualify the credential-resolved default flow for one bounded managed Turn. -The shipped operator rule is *selection first*: the default host is the managed -``dsh`` executor by product decision, the operator credential only authenticates -that selection, and discovering a credential never re-points a Turn. That rule +The shipped operator rule is *explicit selection first*: an explicit ``--host`` +or ``LOOPX_TURN_HOST`` is honoured, and the shipped default is resolved from the +operator's own credential facts -- a configured operator credential runs the +managed ``dsh`` host on that credential, and its absence runs the individual +``codex-cli`` host instead of a managed host nothing can authenticate. That rule is only usable if the *default* command (no explicit ``--host``) actually starts -the managed Turn and reports what ran. +the resolved host and reports what ran. This smoke is hermetic: a local mock OpenAI-compatible SSE server stands in for the model endpoint, so no operator key and no individual CLI subscription is consumed. It proves, through the public CLI only: -1. no credential: the default host is still ``dsh``, reported as an unauthenticated - managed executor with the typed ``operator_credential_unconfigured`` reason; -2. credential: the same default host reports its credential environment, its - billing boundary, and its launchability before any work runs; +1. no credential: the default host is the individual ``codex-cli`` executor, so + the default flow still runs here and claims no managed credential; +2. credential: the default host resolves to the managed ``dsh`` executor and + reports its credential environment, its billing boundary, and its + launchability before any work runs; 3. credential: ``turn run-once`` without ``--host`` starts the real dsh runtime, commits one validated Turn, and reports the mode/executor/status readback; 4. credential but an unavailable managed runtime: the same default flow fails @@ -570,16 +573,15 @@ def log_message(self, _format: str, *args: object) -> None: effects = summary["managed_default_run"]["effects"] or {} ok = ( unbound_default_exit == 0 - and summary["default_without_credential"]["host_kind"] == "dsh" + and summary["default_without_credential"]["host_kind"] == "codex-cli" and summary["default_without_credential"]["execution_mode"] - == "isolated-headless" + == "interactive-visible" and summary["default_without_credential"]["executor_kind"] - == EXECUTOR_KIND_MANAGED + == EXECUTOR_KIND_INDIVIDUAL and summary["default_without_credential"]["credential_env"] is None and summary["default_without_credential"]["operator_credential_bound"] is False - and summary["default_without_credential"]["available"] is False - and summary["default_without_credential"]["unavailable_reason"] - == OPERATOR_CREDENTIAL_UNCONFIGURED + and summary["default_without_credential"]["available"] is None + and summary["default_without_credential"]["unavailable_reason"] is None and individual_exit == 0 and summary["explicit_individual_host"]["host_kind"] == "codex-cli" and summary["explicit_individual_host"]["executor_kind"] diff --git a/examples/loopx-turn-managed-executor-binding-smoke.py b/examples/loopx-turn-managed-executor-binding-smoke.py index 56e71afbbc..39b38a45dd 100644 --- a/examples/loopx-turn-managed-executor-binding-smoke.py +++ b/examples/loopx-turn-managed-executor-binding-smoke.py @@ -243,21 +243,23 @@ def main() -> int: root = Path(directory) project, runtime, workspace, registry = _write_fixture(root) - # 1. The shipped default is the managed host, and without the operator - # credential it refuses instead of borrowing a personal login. + # 1. Without the operator credential the shipped default is the + # individual CLI host, so a default plan still runs here instead of + # gating on a managed host nothing can authenticate. with _operator_credential(None), _harness_runtime(available=True): exit_code, payload = _run_cli(_plan_command(registry, runtime, project)) assert exit_code == 0, payload - assert payload["host"]["kind"] == "dsh", payload - unbound = _managed_binding(payload) - assert unbound["operator_credential_bound"] is False, unbound - assert unbound["available"] is False, unbound - assert unbound["unavailable_reason"] == OPERATOR_CREDENTIAL_UNCONFIGURED, ( - unbound + assert payload["host"]["kind"] == "codex-cli", payload + uncredentialed_default = payload["managed_executor"] + assert ( + uncredentialed_default["executor_kind"] == EXECUTOR_KIND_INDIVIDUAL + ), uncredentialed_default + assert uncredentialed_default["operator_credential_bound"] is False, ( + uncredentialed_default ) + assert uncredentialed_default["available"] is None, uncredentialed_default - # 2. Configuring the credential authenticates that same selection; it - # does not get to pick a different host. + # 2. The credential resolves and authenticates the managed default. with ( _operator_credential("sk-fixture-operator"), _harness_runtime(available=True), @@ -298,8 +300,9 @@ def main() -> int: assert individual["available"] is None, individual assert individual["operator_credential_bound"] is False, individual - # 5. Executing the unauthenticated managed default fails closed: typed - # status, no host invocation, no journal, and no quota slot spend. + # 5. Executing an explicitly selected managed host without the + # credential fails closed: typed status, no host invocation, no + # journal, and no quota slot spend. with _operator_credential(None), _harness_runtime(available=True): exit_code, refusal = _run_cli( _run_once_command( @@ -308,6 +311,7 @@ def main() -> int: project, workspace, instance="managed-executor-unauthenticated", + host="dsh", ) ) assert exit_code == 1, refusal diff --git a/tests/test_turn_default_host_binding.py b/tests/test_turn_default_host_binding.py index 115e9efeea..1df8a401a8 100644 --- a/tests/test_turn_default_host_binding.py +++ b/tests/test_turn_default_host_binding.py @@ -1,4 +1,4 @@ -"""The Turn host is selected explicitly; a credential only authenticates it.""" +"""The default Turn host follows the operator credential; explicit selection wins.""" from __future__ import annotations @@ -7,40 +7,65 @@ from loopx.cli import build_parser from loopx.control_plane.operator_credential import configured_operator_credential from loopx.control_plane.turn_driver.host_binding import ( + INDIVIDUAL_DEFAULT_TURN_HOST, MANAGED_DEFAULT_TURN_HOST, MANAGED_TURN_HOST, TURN_HOST_ENV_VAR, TURN_HOST_SOURCE_EXPLICIT_CONFIG, - TURN_HOST_SOURCE_PRODUCT_DEFAULT, + TURN_HOST_SOURCE_NO_OPERATOR_CREDENTIAL, + TURN_HOST_SOURCE_OPERATOR_CREDENTIAL, resolve_default_turn_host, selected_turn_host, ) -def test_default_host_is_the_managed_product_default(): +def test_managed_credential_selects_the_managed_default_host(): assert MANAGED_DEFAULT_TURN_HOST == MANAGED_TURN_HOST == "dsh" - assert resolve_default_turn_host({}) == MANAGED_DEFAULT_TURN_HOST - assert selected_turn_host({}) == ( + environ = {"DEEPSEEK_API_KEY": "sk-operator"} + + assert resolve_default_turn_host(environ) == MANAGED_DEFAULT_TURN_HOST + assert selected_turn_host(environ) == ( MANAGED_DEFAULT_TURN_HOST, - TURN_HOST_SOURCE_PRODUCT_DEFAULT, + TURN_HOST_SOURCE_OPERATOR_CREDENTIAL, ) @pytest.mark.parametrize( "environ", [ - {"DEEPSEEK_API_KEY": "sk-operator"}, {"DEEPSEEK_API_KEY": ""}, {"DEEPSEEK_API_KEY": " "}, {"DEEPSEEK_BASE_URL": "https://example.invalid"}, - {"DEEPSEEK_API_KEY": "sk-operator", "DEEPSEEK_BASE_URL": "https://x.invalid"}, + {}, ], ) -def test_a_credential_never_changes_the_selected_host(environ): - """Discovering a credential must not re-point a Turn by itself.""" +def test_no_usable_credential_defaults_to_the_individual_host(environ): + """Without an operator credential the default is the host that can run.""" - assert resolve_default_turn_host(environ) == MANAGED_DEFAULT_TURN_HOST - assert selected_turn_host(environ)[1] == TURN_HOST_SOURCE_PRODUCT_DEFAULT + assert INDIVIDUAL_DEFAULT_TURN_HOST == "codex-cli" + assert resolve_default_turn_host(environ) == INDIVIDUAL_DEFAULT_TURN_HOST + assert selected_turn_host(environ) == ( + INDIVIDUAL_DEFAULT_TURN_HOST, + TURN_HOST_SOURCE_NO_OPERATOR_CREDENTIAL, + ) + + +@pytest.mark.parametrize( + "environ", + [ + {TURN_HOST_ENV_VAR: "codex-cli", "DEEPSEEK_API_KEY": "sk-operator"}, + {TURN_HOST_ENV_VAR: "codex-cli"}, + {TURN_HOST_ENV_VAR: "dsh", "DEEPSEEK_API_KEY": ""}, + ], +) +def test_an_explicit_selection_ignores_the_credential(environ): + """A credential resolves the shipped default only, never an explicit host.""" + + assert selected_turn_host(environ) == ( + environ[TURN_HOST_ENV_VAR], + TURN_HOST_SOURCE_EXPLICIT_CONFIG, + ) + assert resolve_default_turn_host(environ) == environ[TURN_HOST_ENV_VAR] def test_explicit_config_repoints_the_default_host(): @@ -70,20 +95,22 @@ def _turn_argv(command: str) -> list[str]: @pytest.mark.parametrize("command", ["plan", "run-once"]) @pytest.mark.parametrize( - "environ", - [{}, {"DEEPSEEK_API_KEY": "sk-operator"}, {"DEEPSEEK_API_KEY": " "}], + "environ, expected_host", + [ + ({}, "codex-cli"), + ({"DEEPSEEK_API_KEY": "sk-operator"}, "dsh"), + ({"DEEPSEEK_API_KEY": " "}, "codex-cli"), + ], ) -def test_cli_defaults_to_the_selected_host_regardless_of_credentials( - command, environ, monkeypatch +def test_cli_default_follows_the_operator_credential( + command, environ, expected_host, monkeypatch ): for name in ("DEEPSEEK_API_KEY", TURN_HOST_ENV_VAR): monkeypatch.delenv(name, raising=False) for name, value in environ.items(): monkeypatch.setenv(name, value) - assert ( - build_parser().parse_args(_turn_argv(command)).host == MANAGED_DEFAULT_TURN_HOST - ) + assert build_parser().parse_args(_turn_argv(command)).host == expected_host @pytest.mark.parametrize("command", ["plan", "run-once"]) @@ -106,17 +133,17 @@ def test_explicit_host_flag_wins_over_the_default(monkeypatch): def test_default_execution_mode_follows_the_selected_host(command, monkeypatch): for name in ("DEEPSEEK_API_KEY", TURN_HOST_ENV_VAR): monkeypatch.delenv(name, raising=False) - managed = build_parser().parse_args(_turn_argv(command)) + individual_default = build_parser().parse_args(_turn_argv(command)) - monkeypatch.setenv(TURN_HOST_ENV_VAR, "codex-cli") - individual = build_parser().parse_args(_turn_argv(command)) + monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-operator") + managed = build_parser().parse_args(_turn_argv(command)) - # The selected managed host runs bounded headless Turns; pairing it with a - # visible interactive mode would make the shipped default unschedulable. + # The managed host runs bounded headless Turns; pairing it with a visible + # interactive mode would make that default unschedulable. # run-once ships only the isolated-headless mode, so it keeps that either way. assert managed.host == MANAGED_DEFAULT_TURN_HOST assert managed.execution_mode == "isolated-headless" - assert individual.host == "codex-cli" - assert individual.execution_mode == ( + assert individual_default.host == INDIVIDUAL_DEFAULT_TURN_HOST + assert individual_default.execution_mode == ( "interactive-visible" if command == "plan" else "isolated-headless" ) diff --git a/tests/test_turn_managed_executor_binding.py b/tests/test_turn_managed_executor_binding.py index fddd1b9e99..1bd1112321 100644 --- a/tests/test_turn_managed_executor_binding.py +++ b/tests/test_turn_managed_executor_binding.py @@ -9,6 +9,7 @@ EXECUTOR_KIND_GENERIC, EXECUTOR_KIND_INDIVIDUAL, EXECUTOR_KIND_MANAGED, + INDIVIDUAL_DEFAULT_TURN_HOST, MANAGED_EXECUTOR_BINDING_SCHEMA_VERSION, MANAGED_TURN_HOST, OPERATOR_CREDENTIAL_UNCONFIGURED, @@ -144,15 +145,20 @@ def test_other_hosts_make_no_launch_claim_and_carry_no_operator_env( @pytest.mark.parametrize( - "environ", + "environ, expected_host, expected_kind", [ - {}, - {"DEEPSEEK_API_KEY": "sk-operator"}, - {"DEEPSEEK_API_KEY": ""}, - {"DEEPSEEK_API_KEY": " "}, + ({}, INDIVIDUAL_DEFAULT_TURN_HOST, EXECUTOR_KIND_INDIVIDUAL), + ( + {"DEEPSEEK_API_KEY": "sk-operator"}, + MANAGED_TURN_HOST, + EXECUTOR_KIND_MANAGED, + ), + ({"DEEPSEEK_API_KEY": " "}, INDIVIDUAL_DEFAULT_TURN_HOST, EXECUTOR_KIND_INDIVIDUAL), ], ) -def test_default_resolution_always_names_the_managed_executor(environ): +def test_default_resolution_reads_back_the_executor_it_selected( + environ, expected_host, expected_kind +): default_host = resolve_default_turn_host(environ) binding = managed_executor_binding( default_host, @@ -160,19 +166,19 @@ def test_default_resolution_always_names_the_managed_executor(environ): module_probe=_RUNTIME, ) - # The default host comes from the product default, not from the credential, - # so the readback always describes the managed executor. Whether it may run - # is a separate, explicitly projected fact. - assert default_host == MANAGED_TURN_HOST - assert binding["executor_kind"] == EXECUTOR_KIND_MANAGED - assert binding["available"] is ( - "DEEPSEEK_API_KEY" in environ and bool(environ["DEEPSEEK_API_KEY"].strip()) - ) + # The default follows the operator credential, and the readback names the + # executor that default resolved to. Whether a managed executor may run here + # stays a separate, explicitly projected fact. + assert default_host == expected_host + assert binding["executor_kind"] == expected_kind + if expected_kind == EXECUTOR_KIND_MANAGED: + assert binding["available"] is True + assert binding["unavailable_reason"] is None -def test_endpoint_without_credential_is_reported_but_does_not_switch_host(): +def test_endpoint_without_credential_does_not_select_the_managed_default(): environ = {"DEEPSEEK_BASE_URL": "https://example.invalid"} binding = managed_executor_binding("codex-cli", environ=environ) - assert resolve_default_turn_host(environ) == MANAGED_TURN_HOST + assert resolve_default_turn_host(environ) == INDIVIDUAL_DEFAULT_TURN_HOST assert binding["endpoint_env"] is None From 2aee6ac93ef45cdc208c241d9ce5395e4d62b189 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:13:10 +0800 Subject: [PATCH 3/3] docs(turn): record the credential-resolved default host Disclose the default behavior change in the Turn protocol reference, the connector guide, and the DSH/Pi harness RFC (English and Chinese): the default host is now resolved from the operator credential, an explicit selection still wins, a lane without a credential keeps running on the individual CLI host, and an explicitly selected managed host still fails closed with the typed reason when nothing can authenticate it. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../rfcs/harness-selection-dsh-pi-v0.md | 30 ++++++++++------- .../rfcs/harness-selection-dsh-pi-v0.zh-CN.md | 25 ++++++++------ .../deepseek-harness-connector.md | 12 ++++--- docs/reference/protocols/loopx-turn-v0.md | 33 ++++++++++++------- 4 files changed, 61 insertions(+), 39 deletions(-) diff --git a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md index 765a656dcf..2471afa431 100644 --- a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md +++ b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md @@ -71,14 +71,17 @@ an endpoint from the operator environment (`DEEPSEEK_BASE_URL`) and a credential from the operator environment (`DEEPSEEK_API_KEY`). LoopX **selects** the default host for bounded managed Turns and never infers it -(`loopx/control_plane/turn_driver/host_binding.py`): the shipped default is `dsh`, -`LOOPX_TURN_HOST` re-points it, and an explicit `--host` wins over both. The -operator credential is not a selection input. This distinction is the whole -point of the binding: discovering a key is not a decision to change where work -runs, and a surface that resolves its host from the environment makes a chosen -configuration indistinguishable from an incidental one. A lane selected onto the -DSH host therefore never depends on an individual developer's CLI subscription -being available, funded, or logged in. +from a launch-time surprise (`loopx/control_plane/turn_driver/host_binding.py`): +an explicit `--host` or `LOOPX_TURN_HOST` always wins, and only when neither is +configured is the shipped default resolved from the operator's own credential +facts -- the managed `dsh` host when a credential exists, and the individual +`codex-cli` host when one does not, because an unauthenticated managed host +would refuse to run. The distinction that matters is between a *default* and a +*decision*: a credential may resolve a default that would otherwise have to pick +a host at random, but it never re-points a host the operator already selected. +A lane resolved onto the DSH host therefore never depends on an individual +developer's CLI subscription being available, funded, or logged in, and a lane +without an operator credential never silently borrows one either. The steward channel is a **different** surface with a different default. Its shipped executor is `codex`, because that is the only transport that can hold an @@ -89,10 +92,13 @@ steward onto an operator model while the executor stays on the CLI endpoint. Evidence for this binding, separated by source: -- repository-covered without any provider call: the shipped default is `dsh`, an - explicit `LOOPX_TURN_HOST` re-points it, an explicit `--host` still wins, and a - configured credential changes none of those selections (tests in PR #4443, not - yet on `main`); +- repository-covered without any provider call: with an operator credential the + shipped default is `dsh` and without one it is `codex-cli`, an explicit + `LOOPX_TURN_HOST` re-points either default, and an explicit `--host` still + wins over all of them (`tests/test_turn_default_host_binding.py`, + `tests/test_turn_managed_executor_binding.py`, + `examples/loopx-turn-managed-executor-binding-smoke.py`, + `examples/loopx-turn-managed-default-flow-smoke.py`); - local live qualification with the real SDK and runtime (`deepseek-harness-sdk==0.1.5rc1`, the pin PR #4420 proposes; `main` still pins `0.1.2a3` and the same pair also passed there): the in-process diff --git a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md index b1fabf1715..7a20f37714 100644 --- a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md +++ b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md @@ -47,7 +47,7 @@ opt-in,不因前者被晋级,仍需本文 C0、C1、开销、保留与 Mode | 角色 | 来源 | 当前选型 | 晋级门槛 | | --- | --- | --- | --- | -| 默认托管执行宿主 | LoopX Turn 加 `dsh` 宿主适配器,并绑定到运维方提供的模型端点 | 出货默认值:托管有界 Turn 走 `dsh`,与环境无关;`LOOPX_TURN_HOST` 可改指,显式 `--host` 优先;在托管栈中(PR #4443),尚未进入 `main` | 保持类型化 host request/result、独立验证与凭据归属运维方的边界;没有同等或更强的契约不替换 | +| 默认托管执行宿主 | LoopX Turn 加 `dsh` 宿主适配器,并绑定到运维方提供的模型端点 | 出货默认值:配置了运维方凭据时托管有界 Turn 走 `dsh`,没有凭据时走个体 `codex-cli`;显式 `LOOPX_TURN_HOST` 可改指,显式 `--host` 优先 | 保持类型化 host request/result、独立验证与凭据归属运维方的边界;没有同等或更强的契约不替换 | | 管家通道执行器 | 管家回答所依赖的交互式 Chat 传输 | 出货默认值:`codex`;`LOOPX_MANAGER_ENDPOINT` 可改指;在托管栈中(PR #4446),尚未进入 `main` | 托管宿主具备交互式 Chat 传输,管家通道才能选择它;凭据的存在从不是晋级信号 | | 受支持的替代 Turn 宿主 | LoopX Turn 加 `codex-cli` 适配器 | 可显式选择;它属于 `individual` 执行器类型,账落在某个人的 CLI 登录上 | 任何托管通道都不得静默依赖某个人的 CLI 订阅;个人通道必须被显式选择,而不是默认走到 | | L1 事件源与会话归属 runtime 候选 | DSH | opt-in,未晋级;有界 Turn 宿主角色见上一行默认值 | 本文 C0、C1、开销、保留与 Mode B 各行被真实执行并通过评审 | @@ -60,12 +60,14 @@ DSH 绑定是 DSH Turn 宿主 + provider `deepseek-official` + 模型 `deepseek- (DeepSeek V4.1 Flash),端点取自运维方环境(`DEEPSEEK_BASE_URL`),凭据取自 运维方环境(`DEEPSEEK_API_KEY`)。 -LoopX **选择**托管有界 Turn 的默认宿主,而从不由环境推断 -(`loopx/control_plane/turn_driver/host_binding.py`):出货默认值是 `dsh`, -`LOOPX_TURN_HOST` 可改指,显式 `--host` 优先于两者。operator 凭据不是选型输入。 -这个区分正是该绑定的意义:发现一把 key 不等于决定换运行位置;一个按环境解析宿主的 -面,会让"选定的配置"和"偶然生效的配置"无法区分。因此被选到 DSH 宿主的通道不会依赖 -某个开发者本机 CLI 订阅是否可用、是否还有额度或是否已登录。 +LoopX **选择**托管有界 Turn 的默认宿主,而从不由启动时的意外推断 +(`loopx/control_plane/turn_driver/host_binding.py`):显式 `--host` 或 +`LOOPX_TURN_HOST` 始终优先;两者都没配置时,出货默认值由运维方自己的凭据事实解析 +——配置了凭据就是托管 `dsh` 宿主,没有凭据则是个体 `codex-cli` 宿主,因为无法认证的 +托管宿主只会拒绝运行。真正需要区分的是**默认值**与**决定**:凭据可以解析一个本来 +无从选择的默认值,但它永远不会改指运维方已经显式选定的宿主。因此解析到 DSH 宿主的 +通道不会依赖某个开发者本机 CLI 订阅是否可用、是否还有额度或是否已登录;没有运维方 +凭据的通道也不会悄悄借用别人的订阅。 管家通道是**另一个**面,默认值也不同。它的出货执行器是 `codex`,因为这是今天唯一 能承载交互式管家会话的传输;`LOOPX_MANAGER_ENDPOINT` 可改指,凭据不能改指。它的 @@ -74,9 +76,12 @@ CLI 的情况下把管家悄悄换成 operator 模型。 该绑定的证据按来源区分: -- 仓库覆盖、无需任何 provider 调用:出货默认值是 `dsh`,显式 `LOOPX_TURN_HOST` - 可改指,显式 `--host` 仍然优先,且配置凭据不改变以上任何一项选择 - (PR #4443 的测试,尚未进入 `main`); +- 仓库覆盖、无需任何 provider 调用:配置了运维方凭据时出货默认值是 `dsh`,没有时 + 是 `codex-cli`;显式 `LOOPX_TURN_HOST` 可改指任一默认值,显式 `--host` 优先于 + 全部(`tests/test_turn_default_host_binding.py`、 + `tests/test_turn_managed_executor_binding.py`、 + `examples/loopx-turn-managed-executor-binding-smoke.py`、 + `examples/loopx-turn-managed-default-flow-smoke.py`); - 本地真实验证:在真实 SDK 与 runtime(`deepseek-harness-sdk==0.1.5rc1`,即 PR #4420 提出的固定版本;`main` 今天仍固定在 `0.1.2a3`,同一对路径在那里也通过)下, 进程内 `--host dsh` 路径与 `generic-cli` 子进程路径均通过; diff --git a/docs/integrations/deepseek-harness-connector.md b/docs/integrations/deepseek-harness-connector.md index acf0edc114..0d0fa31676 100644 --- a/docs/integrations/deepseek-harness-connector.md +++ b/docs/integrations/deepseek-harness-connector.md @@ -141,11 +141,13 @@ classification precedence, plus the hermetic verification smoke ## Host Selection And Managed Executor Readback -The Turn host is **selected, never inferred**. `dsh` is the shipped default -because it is the managed execution unit the steward drives; `LOOPX_TURN_HOST` -re-points that default, and an explicit `--host` (or `--host-adapter-command-json`) -wins over both. A configured `DEEPSEEK_API_KEY` only *authenticates* the selected -host: discovering a credential never changes where a Turn runs. +The Turn host is **selected, never inferred from an incidental environment**. An +explicit `--host` (or `--host-adapter-command-json`) or `LOOPX_TURN_HOST` always +wins. With neither configured, the shipped default is resolved from the +operator's own credential facts: `dsh` is the default when `DEEPSEEK_API_KEY` is +configured, because it is the managed execution unit the steward drives and that +credential authenticates it, and `codex-cli` is the default when no credential is +configured, because an unauthenticated managed host would refuse to run. Both `loopx turn plan` and `loopx turn run-once` report a `managed_executor` block, so a caller reads the planned executor instead of inferring it from a diff --git a/docs/reference/protocols/loopx-turn-v0.md b/docs/reference/protocols/loopx-turn-v0.md index 62062c7f41..e72375859d 100644 --- a/docs/reference/protocols/loopx-turn-v0.md +++ b/docs/reference/protocols/loopx-turn-v0.md @@ -99,24 +99,33 @@ See [DeepSeek Harness connector](../../integrations/deepseek-harness-connector.m ### Host Selection -The Turn host is **selected, never inferred**. `loopx turn plan` and -`loopx turn run-once` default to the managed `dsh` host, the operator may -re-point that default with `LOOPX_TURN_HOST` or one explicit `--host`, and a -configured operator credential only *authenticates* the host that was already -selected. Discovering `DEEPSEEK_API_KEY` must never re-point a Turn by itself. +The Turn host is **selected, never inferred from an incidental environment**. An +explicit `--host` or `LOOPX_TURN_HOST` always wins; only when the operator +configured neither is the shipped default resolved from the operator's own +credential facts: + +- operator credential configured: the default host is the managed `dsh` + executor, which that credential authenticates; +- no operator credential configured: the default host is the individual + `codex-cli` executor, because a managed host nothing can authenticate would + otherwise refuse to run at all. | surface | value | | --- | --- | -| shipped default host | `dsh` (managed executor) | +| shipped default host, credential configured | `dsh` (managed executor) | +| shipped default host, no credential | `codex-cli` (individual executor) | | explicit default selector | `LOOPX_TURN_HOST` | | per-command override | `--host codex-cli\|claude-code\|dsh\|generic-cli` (plan), `codex-cli\|dsh\|generic-cli` (run-once) | | authenticating credential | `DEEPSEEK_API_KEY`, optional endpoint `DEEPSEEK_BASE_URL` | -This is a default behavior change for the affected lanes: `run-once` moved from -`generic-cli` to `dsh`, and `plan` from `codex-cli` to `dsh`. `--host -generic-cli` and `--host codex-cli` remain the explicit compatibility and -rollback paths, and a machine that wants the former default should set -`LOOPX_TURN_HOST=generic-cli` (or `codex-cli`) once instead of relying on the +This is a default behavior change for the affected lanes. Both `plan` and +`run-once` previously defaulted to `dsh` regardless of the credential, so a lane +without one failed closed on `operator_credential_unconfigured`; the default is +now credential-resolved and a lane without a credential keeps running on the +individual CLI host. `--host dsh` remains the explicit managed path and still +fails closed with the same typed reason when nothing can authenticate it, +`--host generic-cli` remains the compatibility path, and a machine that wants +one fixed host should set `LOOPX_TURN_HOST` once instead of relying on the ambient environment. `plan` and `run-once` payloads carry the executor readback `managed_executor` @@ -129,7 +138,7 @@ whether it can launch here. When it cannot, `available` is `false` and | `unavailable_reason` | meaning | remediation | | --- | --- | --- | | `dsh_runtime_unavailable` | the DeepSeek Harness runtime is not importable and no explicit runner hook was supplied | install the released runtime, pass its runner hook, or select `--host codex-cli` | -| `operator_credential_unconfigured` | the managed host is selected but no operator credential or runner hook would authenticate it | set `DEEPSEEK_API_KEY`, or select `--host codex-cli` explicitly | +| `operator_credential_unconfigured` | the managed host is selected but no operator credential or runner hook would authenticate it | set `DEEPSEEK_API_KEY`, or select `--host codex-cli` explicitly; the shipped default already resolves to `codex-cli` until a credential exists | `run-once --execute` fails closed on that verdict: status `unavailable`, no host invocation, no Journal write, and no quota slot spend. An explicitly selected