Skip to content

feat(host-mode): preview the resolved default Turn host - #4456

Merged
huangruiteng merged 2 commits into
mainfrom
codex/host-mode-resolved-default-preview-20260915
Sep 15, 2026
Merged

huangruiteng merged 2 commits into
mainfrom
codex/host-mode-resolved-default-preview-20260915

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

What changed

The host-mode plan previewed loopx turn plan ... --host generic-cli --execution-mode isolated-headless for the headless Turn modes. generic-cli
is the compatibility adapter path, not the shipped default, so the printed
preview asked every operator for a host the plan itself does not resolve.

The preview now keeps the shipped host resolution (no pinned --host), so the
command an operator copies runs on the host that loopx turn plan/run-once
would resolve. The pinned compatibility variant is still printed, as
plan_command_rollback, and the mapping states which it is
(host_selection: resolved_default or pinned).

Changed surfaces

  • loopx/host_mode_planner.py: isolated_headless_turn and shell_service
    preview the resolved default host, report host_selection, and carry the
    pinned generic-cli command as plan_command_rollback. The same policy
    applies to hybrid-handoff target commands.
  • loopx/semantics/inventory_v0.json: regenerated for the new named constants.
  • examples/host-mode-plan-smoke.py, examples/project/host-mode-plan-cli-smoke.py:
    assert the resolved-default preview and the rollback command.
  • docs/reference/protocols/host-mode-plan-v0.md: example payload and the
    isolated_headless_turn acceptance check.

No frontend change is needed: the plan payload keeps turn_mapping.host, so
existing display sinks keep rendering the declared host, and the new fields are
additive.

Validation

  • python examples/host-mode-plan-smoke.py -> ok
  • python examples/project/host-mode-plan-cli-smoke.py -> ok
  • python -m pytest tests/test_host_mode_planner.py tests/test_host_parity_smoke.py tests/test_turn_loop_disposition.py -> 62 passed
  • python -m pytest tests/test_turn_default_host_binding.py tests/test_turn_managed_executor_binding.py -> 37 passed
  • python examples/semantic-vocabulary-drift-smoke.py -> ok
  • python examples/docs-governance-smoke.py -> ok
  • loopx canary premerge --from-git-diff -> 0 failures across catalog, risk-profile, and public-boundary checks

tests/test_host_loop_activation.py has 11 pre-existing failures on main
(reproduced on a pristine main checkout) that this change does not touch.

The headless Turn modes previewed a command that pinned '--host generic-cli', the compatibility adapter path, even though loopx turn plan/run-once resolve their default host from the operator credential. The preview now keeps that resolution and the mode reports host_selection=resolved_default, while the pinned generic-cli variant stays reachable as plan_command_rollback so the compatibility path is one printed command away instead of something an operator has to reconstruct.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Update the host-mode plan protocol reference: the selected mapping carries host_selection and plan_command_rollback, the isolated_headless_turn acceptance check expects a preview without a pinned --host, and the pinned generic-cli command is documented as the rollback variant.

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.

动机

loopx turn plan 的预览命令此前把无人值守意图固定写成 --host generic-cli --execution-mode isolated-headless,而 generic-cli 是兼容适配器路径、不是 shipped default:计划本身并不解析这个 host,却让每个 operator 都按兼容路径去抄命令。这个缺陷在 RFC harness-selection-dsh-pi-v0(docs/architecture/rfcs/harness-selection-dsh-pi-v0.md:124-130)里已经被明确记录为待决项——原文说无人值守映射打印的是 --host generic-cli 而不是已选 dsh 默认值,"作为回滚路径本身没错,但没有被标注为回滚路径",并留了"写出解析后的默认值 / 提供 dsh 变体 / 把该命令标注为回滚路径"三个选项。本 PR 走的是"写出解析结果 + 标注回滚路径"这一支:预览命令不再 pin host,pinned 兼容变体保留为 plan_command_rollback,并新增 host_selection 明确这次是哪种。评审 head:697098a969127e373121ef5d9258c84ad52fac0f,base main(merge base 4f33d5ac6),5 文件 +62/-8。

改动思路

  • 新增类型化的"这次预览的是解析默认值还是 pin 死的 host":RESOLVED_DEFAULT_TURN_HOST_MODES = {isolated_headless_turn, shell_service}、TURN_HOST_SELECTION_PINNED、TURN_HOST_SELECTION_RESOLVED_DEFAULT,并落到 turn_mapping.host_selection——不是靠散文判断,而是机器可读的枚举。
  • _turn_plan_command(..., pin_host=True):只在 pin 模式下拼 --host,其余交给 loopx turn plan/run-once 按 operator 凭据解析,避免把兼容适配器路径当成默认。
  • 兼容路径不丢:resolved-default 模式额外给出 plan_command_rollback(pin 回 generic-cli),operator 想走兼容路径时不用自己反推 flags。
  • 同一策略应用到 transition 的 target_turn_plan_command:目标是这两个模式时不 pin,目标是 visible_tui 时仍 pin 具体 host——策略一致,没有两套规则。
  • 声明面同步:协议文档 docs/reference/protocols/host-mode-plan-v0.md 的示例 payload 与验收条目、loopx/semantics/inventory_v0.json 的具名常量计数(2031→2033)、以及两个 smoke 的旧期望。

具体改动

  1. loopx/host_mode_planner.py:123-134 定义上面的常量集合;注释解释了为什么声明的 host 仍留在 mapping 与 rollback 命令里(scheduler context 与 capability 要求仍是按它陈述的)。
  2. _turn_plan_command(:267-330)增加 pin_host 开关,--host 只在 pin 时出现;agent_arg/execution_mode/scheduler_owner/capability 参数顺序不变。
  3. _build_mode_option(:528-575)计算 resolves_default_host(:543),据此生成 plan_command(不 pin)与 plan_command_rollback(:560);:649-657 把 host_selection 与 plan_command_rollback 写进 turn_mapping。
  4. :682 _transitions.target_turn_command 用 pin_host=to_mode not in RESOLVED_DEFAULT_TURN_HOST_MODES,与 mode 预览同一规则。
  5. docs/reference/protocols/host-mode-plan-v0.md:121-129,163-170:示例 payload 增加 host_selection/plan_command_rollback,验收条目 3 改成"预览不 pin host + 兼容变体报为 plan_command_rollback"。
  6. examples/host-mode-plan-smoke.py:108-124、examples/project/host-mode-plan-cli-smoke.py:66-78:把旧的 --host generic-cli 断言改成 --host not in command + plan_command_rollback 里含 --host generic-cli,并写明理由。

我在受审 head 上实测:pytest -q tests/test_host_mode_planner.py → 2 passed;examples/host-mode-plan-smoke.py → ok;examples/project/host-mode-plan-cli-smoke.py → ok;直接调用 planner 打印 transition,确认进入 isolated_headless_turn 的目标命令不再带 --host、进入 visible_tui 的仍带 --host codex-cli。另外把该 head 与最新 main 0741865f0 合并后 scripts/generate_semantic_inventory.py --check → semantic inventory up to date(这一步是因为刚有 #4454 那类"在自己 base 上绿、合并后红"的计数器陷阱,这里不存在)。

对主干的风险

这是默认行为变更,但已按仓库要求披露。 受影响面是 isolated_headless_turn 与 shell_service 两个模式的预览命令,以及进入前者的 transition 预览;协议文档、两个 smoke(原来编码旧默认的那条断言被改写并说明原因)、具名常量计数与 PR 描述都同步了。turn_mapping.host 仍是声明 host,display sink 的读取路径不变(PR 描述明确写了这一点,新字段是 additive)。CI 在 head 上全绿(pytest、kernel-static-checks、dashboard-acceptance、test-shard 1–4、stage2c、node min/forward、merge-gate、Sign-off)。

三处不阻塞的问题:

  1. P3 语义文档:turn_mapping.host(如 generic-cli)与既有 host_resolution: "resolved" 现在和新的 host_selection: "resolved_default" 并排出现,而 plan_command 已经不 pin 任何 host。机器消费者只读 host/host_resolution 时仍可能得出"具体 host 已解析为 generic-cli"。目前这条区分只写在 PR 描述里,协议文档 host-mode-plan-v0.md 没有定义 host 是"该模式的声明 host / scheduler 上下文",也没说具体 host 由 loopx turn plan 在运行时按 operator 凭据解析。建议在协议文档该字段旁补一句定义(未来若有 v1,再考虑字段改名)。
  2. P3 文档漂移:RFC harness-selection-dsh-pi-v0(英文 :124-130、中文镜像 :102-106)仍把这件事实描述成"仍需在三个选项之间做决定",而本 PR 已经按其中一个选项落地。合并前顺手把该 RFC 段落标成已决(或指向本 PR),否则同一仓库里会留下互相矛盾的现状描述。
  3. P3 覆盖与描述:host_mode_planner.py:682 的 transition 策略变化目前没有任何断言覆盖——examples/host-mode-plan-smoke.py:347 的 transition 循环只检查 preserves_agent_id/spends_quota/guard_command,target_turn_plan_command 里"不 pin host"的事实只被间接覆盖;将来有人把它改回 pin 死也不会红。同时 PR 描述写"The same policy applies to hybrid-handoff target commands",而代码实际影响的是目标为 isolated_headless_turn / shell_service 的 transition(现有四条 transition 里没有目标为 hybrid_handoff 的),措辞与实现不一致。建议在既有 smoke 里补一行 transition 断言,并修正这句描述。

剩余未验证项:本轮没有跑真实 Turn(也就是没有验证 --host dsh 这类由凭据解析出来的默认在真实执行路径上的行为),也没有复跑作者自报的 loopx canary premerge --from-git-diff;本 lane 只做 review、不做合并动作。

我的整体评价

APPROVE。这个改动修的是标准里已经记下来的缺陷(预览命令把兼容路径当成默认),做法是"预览写出解析结果 + 把 pinned 变体显式标成回滚路径",正好落在 RFC 给出的三个选项之内;新增的 host_selection 是类型化枚举而不是散文判断,transition 与 mode 预览共用同一规则,协议文档/smoke/常量计数/PR 描述都同步了,默认变更的披露符合仓库要求。三处 P3(协议文档对 host 的定义、RFC 段落已决但未更新、transition 断言缺口与描述措辞)都不阻塞合并,建议在同一批里顺手补掉。提醒一句:这是 host-mode/quota 预览面,owner 若走自合并,仍需先按仓库要求跑 loopx canary premerge --from-git-diff 并把结果写进 PR 评论。

English verdict: APPROVE at 697098a969127e373121ef5d9258c84ad52fac0f. The change fixes an issue the repository already recorded as a defect: the preview printed --host generic-cli for the headless Turn modes even though that is the compatibility adapter path, not the shipped resolution. The preview now keeps the shipped resolution (--host omitted) and reports the pinned compatibility variant as plan_command_rollback with a machine-readable host_selection enum; transitions follow the same rule, and the protocol doc, both smokes (the assertion that encoded the old default was rewritten with its reason), the semantic inventory counter (2031 -> 2033) and the PR description were updated together. Verified at the exact head: pytest -q tests/test_host_mode_planner.py (2 passed), both smokes ok, transitions into isolated_headless_turn no longer pin a host while visible_tui transitions still do, and the semantic inventory check is up to date after merging this head into the current main 0741865f0 (the counter trap that hit #4454 does not apply). CI is green at this head. Three non-blocking P3s: the protocol doc should define turn_mapping.host as the mode's declared/scheduler-context host now that host_resolution: resolved and host_selection: resolved_default sit next to a command that pins nothing; the RFC harness-selection-dsh-pi-v0 still describes this mapping as an open decision; the transition command policy has no assertion and the PR body describes the affected surface as hybrid-handoff although no transition targets that mode. No merge performed in this round.

@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)

动机

loopx turn plan 的预览命令此前把无人值守意图固定写成 --host generic-cli --execution-mode isolated-headless,而 generic-cli 是兼容适配器路径、不是 shipped default:计划本身并不解析这个 host,却让每个 operator 都按兼容路径去抄命令。这个缺陷在 RFC harness-selection-dsh-pi-v0(docs/architecture/rfcs/harness-selection-dsh-pi-v0.md:124-130)里已经被明确记录为待决项——原文说无人值守映射打印的是 --host generic-cli 而不是已选 dsh 默认值,"作为回滚路径本身没错,但没有被标注为回滚路径",并留了"写出解析后的默认值 / 提供 dsh 变体 / 把该命令标注为回滚路径"三个选项。本 PR 走的是"写出解析结果 + 标注回滚路径"这一支:预览命令不再 pin host,pinned 兼容变体保留为 plan_command_rollback,并新增 host_selection 明确这次是哪种。评审 head:697098a969127e373121ef5d9258c84ad52fac0f,base main(merge base 4f33d5ac6),5 文件 +62/-8。

改动思路

  • 新增类型化的"这次预览的是解析默认值还是 pin 死的 host":RESOLVED_DEFAULT_TURN_HOST_MODES = {isolated_headless_turn, shell_service}、TURN_HOST_SELECTION_PINNED、TURN_HOST_SELECTION_RESOLVED_DEFAULT,并落到 turn_mapping.host_selection——不是靠散文判断,而是机器可读的枚举。
  • _turn_plan_command(..., pin_host=True):只在 pin 模式下拼 --host,其余交给 loopx turn plan/run-once 按 operator 凭据解析,避免把兼容适配器路径当成默认。
  • 兼容路径不丢:resolved-default 模式额外给出 plan_command_rollback(pin 回 generic-cli),operator 想走兼容路径时不用自己反推 flags。
  • 同一策略应用到 transition 的 target_turn_plan_command:目标是这两个模式时不 pin,目标是 visible_tui 时仍 pin 具体 host——策略一致,没有两套规则。
  • 声明面同步:协议文档 docs/reference/protocols/host-mode-plan-v0.md 的示例 payload 与验收条目、loopx/semantics/inventory_v0.json 的具名常量计数(2031→2033)、以及两个 smoke 的旧期望。

具体改动

  1. loopx/host_mode_planner.py:123-134 定义上面的常量集合;注释解释了为什么声明的 host 仍留在 mapping 与 rollback 命令里(scheduler context 与 capability 要求仍是按它陈述的)。
  2. _turn_plan_command(:267-330)增加 pin_host 开关,--host 只在 pin 时出现;agent_arg/execution_mode/scheduler_owner/capability 参数顺序不变。
  3. _build_mode_option(:528-575)计算 resolves_default_host(:543),据此生成 plan_command(不 pin)与 plan_command_rollback(:560);:649-657 把 host_selection 与 plan_command_rollback 写进 turn_mapping。
  4. :682 _transitions.target_turn_command 用 pin_host=to_mode not in RESOLVED_DEFAULT_TURN_HOST_MODES,与 mode 预览同一规则。
  5. docs/reference/protocols/host-mode-plan-v0.md:121-129,163-170:示例 payload 增加 host_selection/plan_command_rollback,验收条目 3 改成"预览不 pin host + 兼容变体报为 plan_command_rollback"。
  6. examples/host-mode-plan-smoke.py:108-124、examples/project/host-mode-plan-cli-smoke.py:66-78:把旧的 --host generic-cli 断言改成 --host not in command + plan_command_rollback 里含 --host generic-cli,并写明理由。

我在受审 head 上实测:pytest -q tests/test_host_mode_planner.py → 2 passed;examples/host-mode-plan-smoke.py → ok;examples/project/host-mode-plan-cli-smoke.py → ok;直接调用 planner 打印 transition,确认进入 isolated_headless_turn 的目标命令不再带 --host、进入 visible_tui 的仍带 --host codex-cli。另外把该 head 与最新 main 0741865f0 合并后 scripts/generate_semantic_inventory.py --check → semantic inventory up to date(这一步是因为刚有 #4454 那类"在自己 base 上绿、合并后红"的计数器陷阱,这里不存在)。

对主干的风险

这是默认行为变更,但已按仓库要求披露。 受影响面是 isolated_headless_turn 与 shell_service 两个模式的预览命令,以及进入前者的 transition 预览;协议文档、两个 smoke(原来编码旧默认的那条断言被改写并说明原因)、具名常量计数与 PR 描述都同步了。turn_mapping.host 仍是声明 host,display sink 的读取路径不变(PR 描述明确写了这一点,新字段是 additive)。CI 在 head 上全绿(pytest、kernel-static-checks、dashboard-acceptance、test-shard 1–4、stage2c、node min/forward、merge-gate、Sign-off)。

三处不阻塞的问题:

  1. P3 语义文档:turn_mapping.host(如 generic-cli)与既有 host_resolution: "resolved" 现在和新的 host_selection: "resolved_default" 并排出现,而 plan_command 已经不 pin 任何 host。机器消费者只读 host/host_resolution 时仍可能得出"具体 host 已解析为 generic-cli"。目前这条区分只写在 PR 描述里,协议文档 host-mode-plan-v0.md 没有定义 host 是"该模式的声明 host / scheduler 上下文",也没说具体 host 由 loopx turn plan 在运行时按 operator 凭据解析。建议在协议文档该字段旁补一句定义(未来若有 v1,再考虑字段改名)。
  2. P3 文档漂移:RFC harness-selection-dsh-pi-v0(英文 :124-130、中文镜像 :102-106)仍把这件事实描述成"仍需在三个选项之间做决定",而本 PR 已经按其中一个选项落地。合并前顺手把该 RFC 段落标成已决(或指向本 PR),否则同一仓库里会留下互相矛盾的现状描述。
  3. P3 覆盖与描述:host_mode_planner.py:682 的 transition 策略变化目前没有任何断言覆盖——examples/host-mode-plan-smoke.py:347 的 transition 循环只检查 preserves_agent_id/spends_quota/guard_command,target_turn_plan_command 里"不 pin host"的事实只被间接覆盖;将来有人把它改回 pin 死也不会红。同时 PR 描述写"The same policy applies to hybrid-handoff target commands",而代码实际影响的是目标为 isolated_headless_turn / shell_service 的 transition(现有四条 transition 里没有目标为 hybrid_handoff 的),措辞与实现不一致。建议在既有 smoke 里补一行 transition 断言,并修正这句描述。

剩余未验证项:本轮没有跑真实 Turn(也就是没有验证 --host dsh 这类由凭据解析出来的默认在真实执行路径上的行为),也没有复跑作者自报的 loopx canary premerge --from-git-diff;本 lane 只做 review、不做合并动作。

我的整体评价

APPROVE。这个改动修的是标准里已经记下来的缺陷(预览命令把兼容路径当成默认),做法是"预览写出解析结果 + 把 pinned 变体显式标成回滚路径",正好落在 RFC 给出的三个选项之内;新增的 host_selection 是类型化枚举而不是散文判断,transition 与 mode 预览共用同一规则,协议文档/smoke/常量计数/PR 描述都同步了,默认变更的披露符合仓库要求。三处 P3(协议文档对 host 的定义、RFC 段落已决但未更新、transition 断言缺口与描述措辞)都不阻塞合并,建议在同一批里顺手补掉。提醒一句:这是 host-mode/quota 预览面,owner 若走自合并,仍需先按仓库要求跑 loopx canary premerge --from-git-diff 并把结果写进 PR 评论。

English verdict: APPROVE at 697098a969127e373121ef5d9258c84ad52fac0f. The change fixes an issue the repository already recorded as a defect: the preview printed --host generic-cli for the headless Turn modes even though that is the compatibility adapter path, not the shipped resolution. The preview now keeps the shipped resolution (--host omitted) and reports the pinned compatibility variant as plan_command_rollback with a machine-readable host_selection enum; transitions follow the same rule, and the protocol doc, both smokes (the assertion that encoded the old default was rewritten with its reason), the semantic inventory counter (2031 -> 2033) and the PR description were updated together. Verified at the exact head: pytest -q tests/test_host_mode_planner.py (2 passed), both smokes ok, transitions into isolated_headless_turn no longer pin a host while visible_tui transitions still do, and the semantic inventory check is up to date after merging this head into the current main 0741865f0 (the counter trap that hit #4454 does not apply). CI is green at this head. Three non-blocking P3s: the protocol doc should define turn_mapping.host as the mode's declared/scheduler-context host now that host_resolution: resolved and host_selection: resolved_default sit next to a command that pins nothing; the RFC harness-selection-dsh-pi-v0 still describes this mapping as an open decision; the transition command policy has no assertion and the PR body describes the affected surface as hybrid-handoff although no transition targets that mode. No merge performed in this round.

@huangruiteng
huangruiteng merged commit 2e310d7 into main Sep 15, 2026
28 checks passed
@huangruiteng
huangruiteng deleted the codex/host-mode-resolved-default-preview-20260915 branch September 15, 2026 16:05
huangruiteng added a commit that referenced this pull request Sep 15, 2026
…olicy (#4462)

Three follow-ups from the review of #4456.

`turn_mapping.host` (`generic-cli`) sat next to `host_resolution: resolved`
and the new `host_selection: resolved_default` while `plan_command` pinned no
host, so a consumer that read only `host`/`host_resolution` could still
conclude that the concrete host had resolved to the compatibility adapter.
The protocol doc now defines the field as the mode's *declared* host and
scheduler context and states that the concrete host is resolved when
`plan_command` runs; it also documents `host_selection` and
`plan_command_rollback`.

The RFC recorded the unattended mapping as an open decision between three
options. The shipped behavior is one of them, so the paragraph now records the
decision (write out the resolved default, label the pinned compatibility
variant as the rollback path) instead of contradicting the code; the Chinese
mirror is updated with it.

The transition policy at `host_mode_planner.py` target commands had no
assertion: `examples/host-mode-plan-smoke.py` only checked
`preserves_agent_id`, `spends_quota` and the guard command. It now asserts that
targets which resolve their host pin no `--host`, and that targets needing a
visible identity still pin `--host codex-cli` with
`--execution-mode interactive-visible`. This also supersedes the merged PR
description, which described the affected transitions as hybrid-handoff
although no transition targets that mode.

Validation: `examples/host-mode-plan-smoke.py` ok,
`examples/project/host-mode-plan-cli-smoke.py` ok,
`pytest tests/test_host_mode_planner.py -q` 2 passed, `ruff check` clean, and
`loopx canary premerge --from-git-diff --timeout-seconds 300` gate=passed with
11 executed checks and 0 failures.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Co-authored-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
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