From d4595a344d3590b6bf7491807ec2f6bb1790b1f2 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/5] 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 c2e3993a5a..efc009d931 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 88ccc05222..a9e46b85c2 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 @@ -39,14 +46,18 @@ managed_profile_unavailable_reason, ) -# 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 @@ -84,15 +95,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 eaaadbbb12..76b078becd 100644 --- a/loopx/semantics/inventory_v0.json +++ b/loopx/semantics/inventory_v0.json @@ -904,7 +904,7 @@ "python_closed_sets": 492, "python_literal_aliases": 8, "typescript_const_arrays": 40, - "named_string_constants": 2049, + "named_string_constants": 2050, "schema_version_names": 756, "schema_version_same_runtime_forks": 7, "cross_runtime_twins": 166, From ecf5c990c4c13e7e12c655e5a38d36e9be6d60b1 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/5] 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 791c425446..bda5ca0f26 100644 --- a/examples/loopx-turn-managed-executor-binding-smoke.py +++ b/examples/loopx-turn-managed-executor-binding-smoke.py @@ -246,21 +246,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), @@ -301,8 +303,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( @@ -311,6 +314,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 0ba80ac797..b0720c9b2c 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, @@ -218,15 +219,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, @@ -234,21 +240,21 @@ 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 36c1f8be887326fdd056ef828fd4d98914405202 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/5] 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 | 32 ++++++++++-------- .../rfcs/harness-selection-dsh-pi-v0.zh-CN.md | 25 ++++++++------ .../deepseek-harness-connector.md | 12 ++++--- docs/reference/protocols/host-mode-plan-v0.md | 7 ++-- docs/reference/protocols/loopx-turn-v0.md | 33 ++++++++++++------- examples/host-mode-plan-smoke.py | 10 +++--- loopx/host_mode_planner.py | 16 ++++----- 7 files changed, 80 insertions(+), 55 deletions(-) diff --git a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md index a63a503de4..ac9de3c7c0 100644 --- a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md +++ b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md @@ -60,7 +60,7 @@ dated 2026-09-15 and is written to land with the managed stack: | Role | Source | Selection today | Promotion gate | | --- | --- | --- | --- | -| Default managed execution host | LoopX Turn plus the `dsh` host adapter, bound to an operator-supplied model endpoint | shipped product default: `dsh` for bounded managed Turns, environment-independent; `LOOPX_TURN_HOST` re-points it and an explicit `--host` wins (PR #4443) | keep the typed host request/result, independent validation, and the operator-owned credential boundary; do not replace it without an equal or stronger contract | +| Default managed execution host | LoopX Turn plus the `dsh` host adapter, bound to an operator-supplied model endpoint | shipped product default, credential-resolved: the managed `dsh` host when the operator credential is configured, the individual `codex-cli` host when it is not; `LOOPX_TURN_HOST` re-points whichever resolved and an explicit `--host` wins (PR #4443, default resolution with this change) | keep the typed host request/result, independent validation, and the operator-owned credential boundary; do not replace it without an equal or stronger contract | | Steward channel executor | the interactive Chat transport the steward answers on | shipped product default, credential-conditional: the managed host (`dsh`) when the operator credential is configured, `codex` when it is not; `LOOPX_MANAGER_ENDPOINT` re-points it and an explicit endpoint wins; selection landed in PR #4446, the conditional default and the segment transport land with this change | the segment transport's typed limits (no streaming, no cross-turn host session, read-only sandbox) stay disclosed and read back, and no managed lane may depend on an individual subscription | | Supported alternative Turn host | LoopX Turn plus the `codex-cli` adapter | explicitly selectable; it is the `individual` executor kind, so it is billed to one person's CLI login | no managed lane may silently depend on an individual's personal CLI subscription; an individual lane must be selected, not reached by default | | L1 event source and session-owning runtime candidate | DSH | opt-in, not promoted; the bounded Turn host role is the default row above | the C0, C1, overhead, retention and Mode B rows in this document being run and reviewed | @@ -76,14 +76,17 @@ Flash) at reasoning effort `high`, an endpoint 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, and after the revision recorded below its default is stated as one conditional rule instead of one host name: @@ -111,10 +114,13 @@ Credentials authenticate the selected profile; they never choose it. 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 c4dd4c7d1b..5d4e5b27d5 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 @@ -50,7 +50,7 @@ C1、开销、保留与 Mode B 各行。 | 角色 | 来源 | 当前选型 | 晋级门槛 | | --- | --- | --- | --- | -| 默认托管执行宿主 | 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 传输 | 出货默认值,按凭据分派:配置了 operator 凭据时为托管宿主(`dsh`),未配置时为 `codex`;`LOOPX_MANAGER_ENDPOINT` 可改指,显式端点优先;选型由 PR #4446 落地,条件默认值与单段传输随本次变更落地 | 单段传输的类型化边界(无流式、无跨 turn 宿主会话、沙箱只读)必须持续披露并可回读;任何托管通道都不得依赖个人订阅 | | 受支持的替代 Turn 宿主 | LoopX Turn 加 `codex-cli` 适配器 | 可显式选择;它属于 `individual` 执行器类型,账落在某个人的 CLI 登录上 | 任何托管通道都不得静默依赖某个人的 CLI 订阅;个人通道必须被显式选择,而不是默认走到 | | L1 事件源与会话归属 runtime 候选 | DSH | opt-in,未晋级;有界 Turn 宿主角色见上一行默认值 | 本文 C0、C1、开销、保留与 Mode B 各行被真实执行并通过评审 | @@ -63,12 +63,14 @@ DSH 绑定是 DSH Turn 宿主 + provider `deepseek-official` + 模型 `deepseek- (DeepSeek V4.1 Flash)+ 推理档位 `high`,端点取自运维方环境(`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 订阅是否可用、是否还有额度或是否已登录;没有运维方 +凭据的通道也不会悄悄借用别人的订阅。 管家通道是**另一个**面;经下文记录的修订后,它的默认值用一条条件规则表达,而不是 一个宿主名:配置了 operator 凭据时通道选择托管宿主(`dsh`),未配置时为 `codex`。 @@ -90,9 +92,12 @@ LoopX **选择**托管有界 Turn 的默认宿主,而从不由环境推断 该绑定的证据按来源区分: -- 仓库覆盖、无需任何 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 98f976e91d..3cd454a7e9 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. What runs on that host is a separate resolution. The managed execution profile defaults to `deepseek-official` / `deepseek-v4-flash` / `high`, overridden by diff --git a/docs/reference/protocols/host-mode-plan-v0.md b/docs/reference/protocols/host-mode-plan-v0.md index ef0a2d1fa6..ae675dd486 100644 --- a/docs/reference/protocols/host-mode-plan-v0.md +++ b/docs/reference/protocols/host-mode-plan-v0.md @@ -149,8 +149,11 @@ quota guard command, and required proofs. that this concrete host has already been resolved for the run; the runtime resolves the concrete host from its own explicit product default (`loopx/control_plane/turn_driver/host_binding.py`) when `plan_command` runs, - and `LOOPX_TURN_HOST` or an explicit `--host` re-points that default. An - operator credential authenticates the selected host; it does not select one. + and `LOOPX_TURN_HOST` or an explicit `--host` re-points that default. That + default is resolved from the operator credential, so this preview stays + deliberately credential-invariant: it pins no host and reports the resolution + as undone, instead of freezing one machine's credential facts into a plan that + other lanes read. - `host_selection` is `resolved_default` when the command deliberately leaves host resolution to `loopx turn plan`/`run-once`, and `pinned` when the command carries an explicit `--host`. diff --git a/docs/reference/protocols/loopx-turn-v0.md b/docs/reference/protocols/loopx-turn-v0.md index 81648650fa..a03e3fdbc1 100644 --- a/docs/reference/protocols/loopx-turn-v0.md +++ b/docs/reference/protocols/loopx-turn-v0.md @@ -99,15 +99,21 @@ 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` | @@ -141,11 +147,14 @@ effort is named in the same line. Credentials authenticate the selected profile; discovering `DEEPSEEK_API_KEY` never changes provider, model, or effort on its own. -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` @@ -158,7 +167,7 @@ whether it can launch here. When it cannot, `available` is `false`, | `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 | | `invalid_reasoning_effort` | the resolved execution profile names a reasoning effort the host adapter does not support | pass a supported `--dsh-reasoning-effort`, or clear the overriding environment variable | The same readback also carries `unavailable_remediation`, which names those diff --git a/examples/host-mode-plan-smoke.py b/examples/host-mode-plan-smoke.py index f4efce3449..2549f85a25 100755 --- a/examples/host-mode-plan-smoke.py +++ b/examples/host-mode-plan-smoke.py @@ -140,11 +140,11 @@ def test_headless_preview_ignores_operator_credential() -> None: os.environ.pop(credential_env, None) else: os.environ[credential_env] = previous - # `loopx turn plan`/`run-once` ship one explicit product default that - # `LOOPX_TURN_HOST` or an explicit `--host` re-points, and an operator - # credential only authenticates the host that was already selected. A - # preview whose shape changed when the credential appeared would re-introduce - # a credential-selected Turn host, so the shape is pinned here instead. + # `loopx turn plan`/`run-once` resolve one shipped default that + # `LOOPX_TURN_HOST` or an explicit `--host` re-points. The preview never + # performs that resolution, so its shape must not depend on the credential + # that would resolve it at run time: a preview that changed shape once a + # credential appeared would freeze one machine's resolution into a plan. assert with_credential == without_credential, (without_credential, with_credential) assert without_credential["host_selection"] == "resolved_default", without_credential assert "--host" not in without_credential["plan_command"], without_credential diff --git a/loopx/host_mode_planner.py b/loopx/host_mode_planner.py index e5050bab0d..b20dcd7304 100644 --- a/loopx/host_mode_planner.py +++ b/loopx/host_mode_planner.py @@ -121,14 +121,14 @@ _INTENT_PRIMARY_MODE = {meta["intent"]: mode for mode, meta in _MODE_METADATA.items()} # The headless Turn modes preview the *shipped* host resolution instead of -# pinning one host. `loopx turn plan`/`run-once` ship one explicit product -# default, owned by `control_plane.turn_driver.host_binding.selected_turn_host`, -# which `LOOPX_TURN_HOST` or an explicit `--host` re-points; an operator -# credential authenticates that host and never selects it, so a preview that -# pinned `generic-cli` would quietly ask every operator for the compatibility -# adapter path. The declared host stays in the mapping and in the rollback -# command, because the mode's scheduler context and capability requirements are -# still stated for it. +# pinning one host. `loopx turn plan`/`run-once` resolve one shipped default, +# owned by `control_plane.turn_driver.host_binding.selected_turn_host`, which +# `LOOPX_TURN_HOST` or an explicit `--host` re-points; that default is itself +# resolved from the operator credential, so a preview that pinned +# `generic-cli` would quietly ask every operator for the compatibility adapter +# path instead of the host their own machine will run. The declared host stays +# in the mapping and in the rollback command, because the mode's scheduler +# context and capability requirements are still stated for it. RESOLVED_DEFAULT_TURN_HOST_MODES = frozenset( {MODE_ISOLATED_HEADLESS_TURN, MODE_SHELL_SERVICE} ) From c1777fa4e717a7a795894d8707e9c3cf812a80cf Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Wed, 16 Sep 2026 05:35:16 +0800 Subject: [PATCH 4/5] refactor(turn): name the default host without a default-prefixed alias An operator-credential-resolved default made the `MANAGED_DEFAULT_TURN_HOST` and `INDIVIDUAL_DEFAULT_TURN_HOST` aliases conditional claims stated as unconditional names: "default" is only true for the credential branch each alias belongs to, so a later caller reading them would re-import the assumption that one host is always the default. The alias names are removed and the selection now returns the host constants directly; "default" stays in `selected_turn_host` and its source value, which is where it is true. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../control_plane/turn_driver/host_binding.py | 6 ++---- tests/test_turn_default_host_binding.py | 19 +++++++++---------- tests/test_turn_managed_executor_binding.py | 8 ++++---- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/loopx/control_plane/turn_driver/host_binding.py b/loopx/control_plane/turn_driver/host_binding.py index a9e46b85c2..f23ffa0913 100644 --- a/loopx/control_plane/turn_driver/host_binding.py +++ b/loopx/control_plane/turn_driver/host_binding.py @@ -52,8 +52,6 @@ # *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_EXPLICIT_CONFIG = "explicit_config" TURN_HOST_SOURCE_OPERATOR_CREDENTIAL = "operator_credential" @@ -105,9 +103,9 @@ def selected_turn_host( if explicit: return explicit, TURN_HOST_SOURCE_EXPLICIT_CONFIG if configured_operator_credential(environ): - return MANAGED_DEFAULT_TURN_HOST, TURN_HOST_SOURCE_OPERATOR_CREDENTIAL + return MANAGED_TURN_HOST, TURN_HOST_SOURCE_OPERATOR_CREDENTIAL return ( - INDIVIDUAL_DEFAULT_TURN_HOST, + INDIVIDUAL_TURN_HOST, TURN_HOST_SOURCE_NO_OPERATOR_CREDENTIAL, ) diff --git a/tests/test_turn_default_host_binding.py b/tests/test_turn_default_host_binding.py index 1df8a401a8..945e2410c9 100644 --- a/tests/test_turn_default_host_binding.py +++ b/tests/test_turn_default_host_binding.py @@ -7,8 +7,7 @@ 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, + INDIVIDUAL_TURN_HOST, MANAGED_TURN_HOST, TURN_HOST_ENV_VAR, TURN_HOST_SOURCE_EXPLICIT_CONFIG, @@ -20,12 +19,12 @@ def test_managed_credential_selects_the_managed_default_host(): - assert MANAGED_DEFAULT_TURN_HOST == MANAGED_TURN_HOST == "dsh" + assert MANAGED_TURN_HOST == "dsh" environ = {"DEEPSEEK_API_KEY": "sk-operator"} - assert resolve_default_turn_host(environ) == MANAGED_DEFAULT_TURN_HOST + assert resolve_default_turn_host(environ) == MANAGED_TURN_HOST assert selected_turn_host(environ) == ( - MANAGED_DEFAULT_TURN_HOST, + MANAGED_TURN_HOST, TURN_HOST_SOURCE_OPERATOR_CREDENTIAL, ) @@ -42,10 +41,10 @@ def test_managed_credential_selects_the_managed_default_host(): def test_no_usable_credential_defaults_to_the_individual_host(environ): """Without an operator credential the default is the host that can run.""" - assert INDIVIDUAL_DEFAULT_TURN_HOST == "codex-cli" - assert resolve_default_turn_host(environ) == INDIVIDUAL_DEFAULT_TURN_HOST + assert INDIVIDUAL_TURN_HOST == "codex-cli" + assert resolve_default_turn_host(environ) == INDIVIDUAL_TURN_HOST assert selected_turn_host(environ) == ( - INDIVIDUAL_DEFAULT_TURN_HOST, + INDIVIDUAL_TURN_HOST, TURN_HOST_SOURCE_NO_OPERATOR_CREDENTIAL, ) @@ -141,9 +140,9 @@ def test_default_execution_mode_follows_the_selected_host(command, monkeypatch): # 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.host == MANAGED_TURN_HOST assert managed.execution_mode == "isolated-headless" - assert individual_default.host == INDIVIDUAL_DEFAULT_TURN_HOST + assert individual_default.host == INDIVIDUAL_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 b0720c9b2c..e0dcd9b8b4 100644 --- a/tests/test_turn_managed_executor_binding.py +++ b/tests/test_turn_managed_executor_binding.py @@ -9,7 +9,7 @@ EXECUTOR_KIND_GENERIC, EXECUTOR_KIND_INDIVIDUAL, EXECUTOR_KIND_MANAGED, - INDIVIDUAL_DEFAULT_TURN_HOST, + INDIVIDUAL_TURN_HOST, MANAGED_EXECUTOR_BINDING_SCHEMA_VERSION, MANAGED_TURN_HOST, OPERATOR_CREDENTIAL_UNCONFIGURED, @@ -221,13 +221,13 @@ def test_other_hosts_make_no_launch_claim_and_carry_no_operator_env( @pytest.mark.parametrize( "environ, expected_host, expected_kind", [ - ({}, INDIVIDUAL_DEFAULT_TURN_HOST, EXECUTOR_KIND_INDIVIDUAL), + ({}, INDIVIDUAL_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), + ({"DEEPSEEK_API_KEY": " "}, INDIVIDUAL_TURN_HOST, EXECUTOR_KIND_INDIVIDUAL), ], ) def test_default_resolution_reads_back_the_executor_it_selected( @@ -254,7 +254,7 @@ 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) == INDIVIDUAL_DEFAULT_TURN_HOST + assert resolve_default_turn_host(environ) == INDIVIDUAL_TURN_HOST assert binding["endpoint_env"] is None From b9c0fc440f29f51f24c3b53a6f030c4d18c1c2f0 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Wed, 16 Sep 2026 06:03:36 +0800 Subject: [PATCH 5/5] docs(rfc): keep the alternative-host gate consistent with the resolved default Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../rfcs/harness-selection-dsh-pi-v0.md | 19 ++++++++++++++----- .../rfcs/harness-selection-dsh-pi-v0.zh-CN.md | 14 ++++++++++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md index ac9de3c7c0..150372c7c2 100644 --- a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md +++ b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md @@ -62,7 +62,7 @@ dated 2026-09-15 and is written to land with the managed stack: | --- | --- | --- | --- | | Default managed execution host | LoopX Turn plus the `dsh` host adapter, bound to an operator-supplied model endpoint | shipped product default, credential-resolved: the managed `dsh` host when the operator credential is configured, the individual `codex-cli` host when it is not; `LOOPX_TURN_HOST` re-points whichever resolved and an explicit `--host` wins (PR #4443, default resolution with this change) | keep the typed host request/result, independent validation, and the operator-owned credential boundary; do not replace it without an equal or stronger contract | | Steward channel executor | the interactive Chat transport the steward answers on | shipped product default, credential-conditional: the managed host (`dsh`) when the operator credential is configured, `codex` when it is not; `LOOPX_MANAGER_ENDPOINT` re-points it and an explicit endpoint wins; selection landed in PR #4446, the conditional default and the segment transport land with this change | the segment transport's typed limits (no streaming, no cross-turn host session, read-only sandbox) stay disclosed and read back, and no managed lane may depend on an individual subscription | -| Supported alternative Turn host | LoopX Turn plus the `codex-cli` adapter | explicitly selectable; it is the `individual` executor kind, so it is billed to one person's CLI login | no managed lane may silently depend on an individual's personal CLI subscription; an individual lane must be selected, not reached by default | +| Supported alternative Turn host | LoopX Turn plus the `codex-cli` adapter | explicitly selectable, and the credential-resolved default of the managed row above on a machine with no operator credential; it is the `individual` executor kind, so it is billed to one person's CLI login | no managed lane may *silently* depend on an individual's personal CLI subscription: the individual host is reached only as that credential-resolved default and is read back as `no_operator_credential`, never substituted for a host the operator selected | | L1 event source and session-owning runtime candidate | DSH | opt-in, not promoted; the bounded Turn host role is the default row above | the C0, C1, overhead, retention and Mode B rows in this document being run and reviewed | | Optional visible host loop | Pi | not a managed runtime | declare a per-binding session mode with readback, prove single-executor behavior under restart, "conversation is not a receipt", non-authoritative host-local state, and one real-host restart row | @@ -88,6 +88,13 @@ 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. +This change also rewrites the promotion gate on the supported alternative host +in the table above. It read "an individual lane must be selected, not reached by +default", which the credential-resolved default contradicts. The rewritten rule +keeps the original intent -- no lane may depend on one person's login without +the operator being able to see that it did -- and names the readback that makes +the dependency visible instead of forbidding the disclosed default. + The steward channel is a **different** surface, and after the revision recorded below its default is stated as one conditional rule instead of one host name: the channel selects the managed host (`dsh`) when the operator credential is @@ -110,7 +117,9 @@ effort `high`, overridable by `LOOPX_TURN_PROVIDER` / `LOOPX_TURN_MODEL` / when it is not the shipped one; it is one line because every plan payload carries it and the agent-facing output budget is a contract, and whichever values the line names are the values that run, so an owner-set model appears as itself. -Credentials authenticate the selected profile; they never choose it. +Credentials authenticate the selected profile and never choose it; the one +thing a credential resolves is the shipped *host* default of a bounded Turn +nobody selected, and that resolution carries its own readback source. Evidence for this binding, separated by source: @@ -365,9 +374,9 @@ failure, journal and quota semantics LoopX already validates; keep B as the cheaper replacement if the upstream interface appears; evaluate C only if duplex streaming is required for the steward experience. Whichever option ships must demonstrate, for one steward session, that the model work lands on the -operator credential and that no default path reaches an individual -subscription. This document authorizes no new scheduler, retry authority or -second monitoring subsystem to make that demonstration pass. +operator credential and that no default path of the steward channel reaches an +individual subscription. This document authorizes no new scheduler, retry +authority or second monitoring subsystem to make that demonstration pass. Option A is the one that shipped, and its demonstration is a repository smoke rather than a live transcript: `examples/loopx-steward-managed-chat-smoke.py` 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 5d4e5b27d5..db59ca174f 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 @@ -52,7 +52,7 @@ C1、开销、保留与 Mode B 各行。 | --- | --- | --- | --- | | 默认托管执行宿主 | LoopX Turn 加 `dsh` 宿主适配器,并绑定到运维方提供的模型端点 | 出货默认值:配置了运维方凭据时托管有界 Turn 走 `dsh`,没有凭据时走个体 `codex-cli`;显式 `LOOPX_TURN_HOST` 可改指,显式 `--host` 优先 | 保持类型化 host request/result、独立验证与凭据归属运维方的边界;没有同等或更强的契约不替换 | | 管家通道执行器 | 管家回答所依赖的交互式 Chat 传输 | 出货默认值,按凭据分派:配置了 operator 凭据时为托管宿主(`dsh`),未配置时为 `codex`;`LOOPX_MANAGER_ENDPOINT` 可改指,显式端点优先;选型由 PR #4446 落地,条件默认值与单段传输随本次变更落地 | 单段传输的类型化边界(无流式、无跨 turn 宿主会话、沙箱只读)必须持续披露并可回读;任何托管通道都不得依赖个人订阅 | -| 受支持的替代 Turn 宿主 | LoopX Turn 加 `codex-cli` 适配器 | 可显式选择;它属于 `individual` 执行器类型,账落在某个人的 CLI 登录上 | 任何托管通道都不得静默依赖某个人的 CLI 订阅;个人通道必须被显式选择,而不是默认走到 | +| 受支持的替代 Turn 宿主 | LoopX Turn 加 `codex-cli` 适配器 | 可显式选择,也是上一行托管默认值在没有 operator 凭据的机器上的解析结果;它属于 `individual` 执行器类型,账落在某个人的 CLI 登录上 | 任何托管通道都不得*静默*依赖某个人的 CLI 订阅:个体宿主只会作为那条凭据解析默认值被走到,并以 `no_operator_credential` 回读,绝不被替换成运维方已选定的宿主 | | L1 事件源与会话归属 runtime 候选 | DSH | opt-in,未晋级;有界 Turn 宿主角色见上一行默认值 | 本文 C0、C1、开销、保留与 Mode B 各行被真实执行并通过评审 | | 可选的可见宿主循环 | Pi | 不是 managed runtime | 先声明按绑定持久化且可回读的会话模式,证明重启下的单执行器行为、"对话不是回执"、宿主本地状态非权威,并提供一条真实宿主重启行 | @@ -72,6 +72,11 @@ LoopX **选择**托管有界 Turn 的默认宿主,而从不由启动时的意 通道不会依赖某个开发者本机 CLI 订阅是否可用、是否还有额度或是否已登录;没有运维方 凭据的通道也不会悄悄借用别人的订阅。 +本次变更同时改写了上表中"受支持的替代 Turn 宿主"的晋级门槛:它原文是"个人通道必须被 +显式选择,而不是默认走到",而上面的凭据解析默认值与它冲突。改写后的规则保留原意—— +任何通道都不得在运维方看不见的情况下依赖某个人的登录——并改为指明让这层依赖可见的 +回读,而不是禁止这条已披露的默认值。 + 管家通道是**另一个**面;经下文记录的修订后,它的默认值用一条条件规则表达,而不是 一个宿主名:配置了 operator 凭据时通道选择托管宿主(`dsh`),未配置时为 `codex`。 `LOOPX_MANAGER_ENDPOINT` 可改指,显式端点优先。因此发现凭据选择的是一个**自洽的** @@ -88,7 +93,8 @@ LoopX **选择**托管有界 Turn 的默认宿主,而从不由启动时的意 `DSH_MODEL`。回读是一行 `execution_profile`:出货形态为 `deepseek-v4-flash@high`, 仅当 provider 不是出货值时前置为 `/…`。之所以只有一行,是因为每个 plan 载荷都携带它,而面向 agent 的输出预算是一份契约;该行写出什么值,就是实际会跑的值, -因此 owner 自己设定的模型会以自身出现。凭据为选定档位提供认证,从不参与选型。 +因此 owner 自己设定的模型会以自身出现。凭据为选定档位提供认证,从不参与选型;凭据 +唯一解析的是"无人显式选择时有界 Turn 的出货宿主默认值",且该解析自带来源回读。 该绑定的证据按来源区分: @@ -296,8 +302,8 @@ dsh 片段**,把通道可见的有界历史与当前消息交给它,并返 选型规则:优先 A,因为它复用 LoopX 已经验证过的 Turn 权威、typed host failure、 journal 与配额语义;B 作为上游接口出现时的低成本替代;只有在管家体验确需双工 流式时才评估 C。无论采用哪条路线,都必须证明「一次管家会话的模型工作落在 -operator 凭据上,且不存在任何默认指向个人订阅的路径」。本文件不授权为此新增 -scheduler、重试权限或第二套监控子系统。 +operator 凭据上,且管家通道自身不存在任何默认指向个人订阅的路径」。本文件不授权为此 +新增 scheduler、重试权限或第二套监控子系统。 落地的是路线 A,其证明是一条仓库 smoke 而不是真实会话原文: `examples/loopx-steward-managed-chat-smoke.py` 用真实内置 dsh 片段对接本地 mock 模型