Skip to content

feat(turn): resolve the default host from the operator credential - #4479

Merged
huangruiteng merged 5 commits into
mainfrom
codex/credential-resolved-default-turn-host-20260916
Sep 15, 2026
Merged

huangruiteng merged 5 commits into
mainfrom
codex/credential-resolved-default-turn-host-20260916

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

动机

loopx turn 的默认宿主此前是环境无关的 dsh:在一台没有
DEEPSEEK_API_KEY 的机器上,默认的 loopx turn plan / run-once 会以
operator_credential_unconfigured 直接失败关闭,operator 必须先知道要补
--host codex-cli 才能把 Turn 跑起来。

本 PR 把默认改成 owner 指定的那一条:配了厂商凭据就走托管 dsh,没配就走
本机本来就能用的 codex-cli
。显式的 --host / LOOPX_TURN_HOST 永远优先,
凭据只解析"本来没有主人"的出厂默认,绝不改写已经做出的选择。

改动

  1. turn_driver/host_binding.selected_turn_host() 增加凭据分支:显式选择 →
    explicit_config;有凭据 → dsh / operator_credential;无凭据 →
    codex-cli / no_operator_credential。两个方向都带来源读回,operator 能
    区分"产品默认"和"我选过"。
  2. managed_executor_binding 保持既有读回(executor / executor_kind /
    execution_profile / available / unavailable_reason / remediation);typed
    operator_credential_unconfigured 拒绝现在只在 operator 显式选择托管宿主
    而没有凭据时触发,不再由默认路径触发。
  3. 文档同步:loopx-turn-v0.md、host-mode-plan-v0.md、
    docs/integrations/deepseek-harness-connector.md,以及 DSH/Pi RFC 的英文与
    zh-CN 双镜像。RFC 里被这次变更推翻的语句(托管替代宿主的升格门禁、凭据只
    认证不选择、管家通道"没有默认路径走到个体订阅")就地改写并把改写理由写进
    正文,而不是留下自相矛盾的两段话。
  4. 连带一致性:host_mode_planner.py 的依据说明、两个 Turn smoke 的断言、
    cli_commands/turn_registration.py 与 operator_credential.py 的措辞对齐。
  5. loopx/semantics/inventory_v0.json 重生成(named_string_constants
    2049 → 2050)。

与 #4454 的关系

本 PR 是 #4454 的修复版,不是新方向。#4454 上的三条 review finding 在这里
逐条修掉:

  • P1 stale semantic inventory:分支 rebase 到当前 main 后重生成清单,
    --check 已最新。
  • P3 pin 建议:显式选择优先、凭据只解析默认、以及"Turn 宿主解析"与"管家通道
    条件默认"是两个独立读回这一点,写进了两份镜像。
  • P3 *_DEFAULT_TURN_HOST 别名:删除,选择直接返回宿主常量。

修完 finding 后分支必须 rebase + force-push,GitHub 因此拒绝重开 #4454
(state cannot be changed. The ... branch was force-pushed or recreated.),
所以修复结果以本 PR 交付,#4454 的讨论保留为变更记录。差异:本 PR 的 head 是
rebase 后的 b9c0fc440,相对当时记录的 1a1ab47e2 只多了对当前 main 的
rebase 与随之重生成的语义清单。

验证

真实 CLI 读回(PYTHONPATH=<worktree> python -m loopx.entrypoint turn plan --goal-id loopx-meta --agent-id codex-managed-steward-product):

  • 无 DEEPSEEK_API_KEY → executor: codex-cli、executor_kind: individual;
  • 配置凭据 → executor: dsh、executor_kind: managed、
    execution_profile: deepseek-v4-flash@high、available: true。

自动化检查:

  • 262 个聚焦测试通过(test_turn_default_host_binding、
    test_turn_managed_executor_binding、turn driver / executor / host-failure /
    managed-step / codex-cli / envelope);
  • 4 个公开 smoke 通过:host-mode-plan-smoke.py、
    project/host-mode-plan-cli-smoke.py、
    loopx-turn-managed-executor-binding-smoke.py、
    loopx-turn-managed-default-flow-smoke.py;
  • scripts/generate_semantic_inventory.py --check:最新;
    examples/docs-governance-smoke.py:ok;ruff check:ok;
  • loopx canary premerge --from-git-diff --timeout-seconds 420:passed,
    tier=standard,changed_files=15,surfaces=control_plane、docs_project_content、
    public_boundary、python,selected=17,failures=0,advisory_failures=0;
  • DCO:5 个 commit 全部带 Signed-off-by。

风险

  • 行为变更已显式说明:默认宿主由"环境无关 dsh(无凭据即失败关闭)"改为"由凭据
    解析"。仅在没有任何显式选择时生效,--host / LOOPX_TURN_HOST 语义不变。
  • 拒绝路径收窄是有意的:无凭据不再让默认 Turn 直接失败,而改由
    managed_executor.executor_kind / available 读回表达。显式选择 dsh 仍然
    fail closed。
  • 该默认与管家通道的绑定(凭据存在 → dsh,否则 codex)现在一致,两处读回
    不再互相矛盾,这是本 PR 的正面效果之一。

English verdict: the repaired branch is rebased onto current main and green.
The default Turn host now resolves from the operator credential (dsh when it is
configured, codex-cli otherwise) while an explicit --host/LOOPX_TURN_HOST
still wins, the typed operator_credential_unconfigured refusal is retained for
an explicit managed selection, and the RFC/gate wording the change falsifies is
rewritten in place rather than left contradictory. Supersedes #4454, which
GitHub cannot reopen after the rebase. No open review finding remains.

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>
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>
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>
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>
…d default

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

动机

有界 Turn 的默认宿主此前是环境无关的 dsh:selected_turn_host() 从不读环境,
直接返回 (dsh, product_default)。在一台没有 DEEPSEEK_API_KEY 的机器上,默认的
loopx turn plan / run-once 因此解析出一个无法认证的托管执行器,绑定读回
operator_credential_unconfigured,Turn 在 journal/host/quota 之前失败关闭。
operator 必须先知道内部退出 --host codex-cli,才能让第一个默认 Turn 跑起来;这个
成本在每台无凭据机器、每次默认调用上重复。受影响的是默认路径上的所有 operator,
以及通过同一 owner 驱动有界 Turn 的管家通道。

修复方向不需要新机制:管家通道(chat_manager.manager_channel_binding)已经用同一
个凭据事实解析执行器——配了凭据走 dsh,否则回退 codex。本 PR 把 Turn 面拉齐到
同一条规则,而不是让两个面继续各自声称"凭据能不能解析默认"。非目标(明确写在
PR 正文):不改显式 --host / LOOPX_TURN_HOST 语义,不取消显式托管选择在无凭据
时的 fail-closed 拒绝,不引入新 provider、新凭据变量或新模型默认值。

改动思路

决策 owner 不变,仍是
loopx/control_plane/turn_driver/host_binding.selected_turn_host();改动只是给这个
既有 owner 增加一条凭据分支,并把"来源"从一个 product_default 拆成三个可判别
的来源码。

入口是 loopx turn plan|run-once:turn_registration.py 把
resolve_default_turn_host() 当作 --host 默认值并据此配对执行模式,
turn.py:221 把 managed_executor_binding(...) 挂到 plan payload。权威输入是
operator 环境事实(DEEPSEEK_API_KEY / DEEPSEEK_BASE_URL,由
operator_credential.py 只报告变量名、从不读取值)加上显式选择。分支顺序把"显式
优先"写成不变量:先看显式配置,再看凭据事实。

复用判断:仓库里已经有同一规则形状的实现(管家通道按
operator_credential_configured() 条件绑定),而 Turn 面唯一的 owner 就是
selected_turn_host(),所以这里既没有新增"第二个选择器",也没有新增持久化状态——
host 与 source 都是每次调用从环境加显式配置派生出来的,没有需要迁移或对齐的
存储。失败/重试 owner 仍在 managed_executor_binding():显式选择托管宿主而又没有
凭据时,依然给出 typed reason 与 remediation。被改的只有"没有主人时的出厂默认",
operator 已经做出的选择一律不动。

具体改动

关键代码讲解

  1. selected_turn_host()(loopx/control_plane/turn_driver/host_binding.py:91)
    是默认宿主决策的唯一 owner。改后返回 (host, source):显式 LOOPX_TURN_HOST
    → explicit_config;configured_operator_credential() 为真 → dsh /
    operator_credential;否则 → codex-cli / no_operator_credential。不变量是
    分支顺序——显式永远先判定,因此凭据只能解析出厂默认,不能改写已选宿主;走
    env_text() 还保证"设了但为空"的变量不会被当成凭据。
  2. managed_executor_binding()(同文件 :157)的读回形状不变:executor、
    executor_kind、credential_env、endpoint_env、execution_profile、
    available、unavailable_reason、unavailable_remediation。变化在于默认路径
    不再落到需要凭据的托管分支;available is False 只在 LoopX 能证明该执行器在此
    无法启动时给出。
  3. configured_operator_credential()(loopx/control_plane/operator_credential.py:36)
    仍只报告"哪个变量被配置",不读值;本 PR 把它的 docstring 收敛为"只解析那些没有
    自己选择的面的出厂默认",这样这个模块的语义与它现在参与的决定一致。
  4. register_turn_commands()(loopx/cli_commands/turn_registration.py:22)
    行为不变,只是注释改为"执行模式跟随被解析出的宿主",因为宿主本身现在可以来自
    凭据而不是纯产品常量。

其余改动是契约与验证面:loopx-turn-v0.md、host-mode-plan-v0.md、DSH/Pi RFC 的
英文与 zh-CN 双镜像、connector 文档同步了新规则,并把被这次变更推翻的三句话(托管
替代宿主的升格门禁、凭据只认证不选择、管家通道"没有默认路径走到个体订阅")就地
改写
、写明改写理由,而不是留下自相矛盾的两段话;host_mode_planner.py 的依据注释、
两个 Turn smoke 的断言与 named_string_constants 2049 → 2050 的语义清单随之更新。

对主干的风险

最强回归场景:依赖"默认即托管宿主"的调用方,在无凭据机器上会静默改用 codex-cli
这个个体宿主执行,于是原本假定托管端点(provider、模型、计费)的工作跑到个人 CLI
登录上。触发状态是 DEEPSEEK_API_KEY 缺失或为空,且没有 --host/LOOPX_TURN_HOST。

阻止与该状态可见性:分支顺序决定只有"什么都没选"时才应用新默认;该状态下
managed_executor_binding() 报 executor_kind: individual、credential_env: null,
因此依赖关系在产生效果之前就可读,而不是运行后才暴露。爆炸半径是默认
loopx turn 调用与引用 resolved host 的展示面;显式选择调用方不受影响。回滚成本低:
LOOPX_TURN_HOST=dsh(或 --host dsh)即可恢复托管宿主,revert 提交则恢复原默认。

反例检查:是否有绕过 owner 的第二条默认路径?rg 'MANAGED_TURN_HOST|INDIVIDUAL_TURN_HOST'
显示生产侧唯一消费者是 turn_registration.py 经 resolve_default_turn_host(),常量也
由 host_binding 自己持有,因此不存在第二个默认。真实边界:凭据两个方向都通过真实
CLI 入口跑过(凭据用合成非敏感值,未读取、未打印、未提交任何真实凭据);未验证项是
托管端点的实际计费行为——本次验证的是宿主解析、绑定读回与拒绝路径,不是一次真实
托管 Turn。显式 --host dsh + 空凭据的拒绝仍然成立(available: false、
operator_credential_unconfigured、remediation configure_operator_credential +
select_individual_host),已在 reviewed head 实测。

我的整体评价

方向与仓库既有规则一致,机制是"既有 owner 加一个分支 + 可判别来源码",没有新增抽象、
标志、schema 或持久化状态;被推翻的 RFC 语句就地改写而不是叠加第二份说法;验证覆盖
默认两个方向、显式覆盖、拒绝路径与仓库原生门禁(262 个聚焦测试、4 个公开 smoke、
canary premerge 17 项 0 失败、语义清单/文档治理/ruff 干净、5 个 commit DCO)。
这是 #4454 的修复版:其三条 review finding(P1 语义清单过期、P3 pin 建议、P3
*_DEFAULT_TURN_HOST 别名)在本 head 已逐条修掉;因修复需要 rebase + force-push,
GitHub 拒绝重开 #4454("was force-pushed or recreated"),故以本 PR 交付。

English verdict: APPROVE — the repaired change is rebased onto current main and
green. One decision owner (selected_turn_host()) now resolves the shipped
default from the operator credential facts while an explicit
--host/LOOPX_TURN_HOST still wins and the typed
operator_credential_unconfigured refusal is retained for an explicit managed
selection; the credential is still never read as a value. Verified on the real
CLI entrypoint in both credential states plus the refusal path, with 262 focused
tests, 4 public smokes, and loopx canary premerge (17 checks, 0 failures) at
the reviewed head. No blocking finding remains; the only unverified dimension is
live managed-endpoint billing behavior.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

Reviewed exact head: b9c0fc440f29f51f24c3b53a6f030c4d18c1c2f0 (base main @
b132d0bfb); every claim below was re-verified at this head.

动机

有界 Turn 的默认宿主此前是环境无关的 dsh:selected_turn_host() 从不读环境,
直接返回 (dsh, product_default)。在一台没有 DEEPSEEK_API_KEY 的机器上,默认的
loopx turn plan / run-once 因此解析出一个无法认证的托管执行器,绑定读回
operator_credential_unconfigured,Turn 在 journal/host/quota 之前失败关闭。
operator 必须先知道内部退出 --host codex-cli,才能让第一个默认 Turn 跑起来;这个
成本在每台无凭据机器、每次默认调用上重复。受影响的是默认路径上的所有 operator,
以及通过同一 owner 驱动有界 Turn 的管家通道。

修复方向不需要新机制:管家通道(chat_manager.manager_channel_binding)已经用同一
个凭据事实解析执行器——配了凭据走 dsh,否则回退 codex。本 PR 把 Turn 面拉齐到
同一条规则,而不是让两个面继续各自声称"凭据能不能解析默认"。非目标(明确写在
PR 正文):不改显式 --host / LOOPX_TURN_HOST 语义,不取消显式托管选择在无凭据
时的 fail-closed 拒绝,不引入新 provider、新凭据变量或新模型默认值。

改动思路

决策 owner 不变,仍是
loopx/control_plane/turn_driver/host_binding.selected_turn_host();改动只是给这个
既有 owner 增加一条凭据分支,并把"来源"从一个 product_default 拆成三个可判别
的来源码。

入口是 loopx turn plan|run-once:turn_registration.py 把
resolve_default_turn_host() 当作 --host 默认值并据此配对执行模式,
turn.py:221 把 managed_executor_binding(...) 挂到 plan payload。权威输入是
operator 环境事实(DEEPSEEK_API_KEY / DEEPSEEK_BASE_URL,由
operator_credential.py 只报告变量名、从不读取值)加上显式选择。分支顺序把"显式
优先"写成不变量:先看显式配置,再看凭据事实。

复用判断:仓库里已经有同一规则形状的实现(管家通道按
operator_credential_configured() 条件绑定),而 Turn 面唯一的 owner 就是
selected_turn_host(),所以这里既没有新增"第二个选择器",也没有新增持久化状态——
host 与 source 都是每次调用从环境加显式配置派生出来的,没有需要迁移或对齐的
存储。失败/重试 owner 仍在 managed_executor_binding():显式选择托管宿主而又没有
凭据时,依然给出 typed reason 与 remediation。被改的只有"没有主人时的出厂默认",
operator 已经做出的选择一律不动。

具体改动

关键代码讲解

  1. selected_turn_host()(loopx/control_plane/turn_driver/host_binding.py:91)
    是默认宿主决策的唯一 owner。改后返回 (host, source):显式 LOOPX_TURN_HOST
    → explicit_config;configured_operator_credential() 为真 → dsh /
    operator_credential;否则 → codex-cli / no_operator_credential。不变量是
    分支顺序——显式永远先判定,因此凭据只能解析出厂默认,不能改写已选宿主;走
    env_text() 还保证"设了但为空"的变量不会被当成凭据。
  2. managed_executor_binding()(同文件 :157)的读回形状不变:executor、
    executor_kind、credential_env、endpoint_env、execution_profile、
    available、unavailable_reason、unavailable_remediation。变化在于默认路径
    不再落到需要凭据的托管分支;available is False 只在 LoopX 能证明该执行器在此
    无法启动时给出。
  3. configured_operator_credential()(loopx/control_plane/operator_credential.py:36)
    仍只报告"哪个变量被配置",不读值;本 PR 把它的 docstring 收敛为"只解析那些没有
    自己选择的面的出厂默认",这样这个模块的语义与它现在参与的决定一致。
  4. register_turn_commands()(loopx/cli_commands/turn_registration.py:22)
    行为不变,只是注释改为"执行模式跟随被解析出的宿主",因为宿主本身现在可以来自
    凭据而不是纯产品常量。

其余改动是契约与验证面:loopx-turn-v0.md、host-mode-plan-v0.md、DSH/Pi RFC 的
英文与 zh-CN 双镜像、connector 文档同步了新规则,并把被这次变更推翻的三句话(托管
替代宿主的升格门禁、凭据只认证不选择、管家通道"没有默认路径走到个体订阅")就地
改写
、写明改写理由,而不是留下自相矛盾的两段话;host_mode_planner.py 的依据注释、
两个 Turn smoke 的断言与 named_string_constants 2049 → 2050 的语义清单随之更新。

对主干的风险

最强回归场景:依赖"默认即托管宿主"的调用方,在无凭据机器上会静默改用 codex-cli
这个个体宿主执行,于是原本假定托管端点(provider、模型、计费)的工作跑到个人 CLI
登录上。触发状态是 DEEPSEEK_API_KEY 缺失或为空,且没有 --host/LOOPX_TURN_HOST。

阻止与该状态可见性:分支顺序决定只有"什么都没选"时才应用新默认;该状态下
managed_executor_binding() 报 executor_kind: individual、credential_env: null,
因此依赖关系在产生效果之前就可读,而不是运行后才暴露。爆炸半径是默认
loopx turn 调用与引用 resolved host 的展示面;显式选择调用方不受影响。回滚成本低:
LOOPX_TURN_HOST=dsh(或 --host dsh)即可恢复托管宿主,revert 提交则恢复原默认。

反例检查:是否有绕过 owner 的第二条默认路径?rg 'MANAGED_TURN_HOST|INDIVIDUAL_TURN_HOST'
显示生产侧唯一消费者是 turn_registration.py 经 resolve_default_turn_host(),常量也
由 host_binding 自己持有,因此不存在第二个默认。真实边界:凭据两个方向都通过真实
CLI 入口跑过(凭据用合成非敏感值,未读取、未打印、未提交任何真实凭据);未验证项是
托管端点的实际计费行为——本次验证的是宿主解析、绑定读回与拒绝路径,不是一次真实
托管 Turn。显式 --host dsh + 空凭据的拒绝仍然成立(available: false、
operator_credential_unconfigured、remediation configure_operator_credential +
select_individual_host),已在 reviewed head 实测。

我的整体评价

方向与仓库既有规则一致,机制是"既有 owner 加一个分支 + 可判别来源码",没有新增抽象、
标志、schema 或持久化状态;被推翻的 RFC 语句就地改写而不是叠加第二份说法;验证覆盖
默认两个方向、显式覆盖、拒绝路径与仓库原生门禁(262 个聚焦测试、4 个公开 smoke、
canary premerge 17 项 0 失败、语义清单/文档治理/ruff 干净、5 个 commit DCO)。
这是 #4454 的修复版:其三条 review finding(P1 语义清单过期、P3 pin 建议、P3
*_DEFAULT_TURN_HOST 别名)在本 head 已逐条修掉;因修复需要 rebase + force-push,
GitHub 拒绝重开 #4454("was force-pushed or recreated"),故以本 PR 交付。

English verdict: APPROVE — the repaired change is rebased onto current main and
green. One decision owner (selected_turn_host()) now resolves the shipped
default from the operator credential facts while an explicit
--host/LOOPX_TURN_HOST still wins and the typed
operator_credential_unconfigured refusal is retained for an explicit managed
selection; the credential is still never read as a value. Verified on the real
CLI entrypoint in both credential states plus the refusal path, with 262 focused
tests, 4 public smokes, and loopx canary premerge (17 checks, 0 failures) at
the reviewed head. No blocking finding remains; the only unverified dimension is
live managed-endpoint billing behavior.

@huangruiteng
huangruiteng merged commit 001e0c3 into main Sep 15, 2026
22 checks passed
@huangruiteng
huangruiteng deleted the codex/credential-resolved-default-turn-host-20260916 branch September 15, 2026 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant