Skip to content

Commit 292b85e

Browse files
authored
docs(host-mode): define the declared host and assert the transition policy (#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>
1 parent f22879d commit 292b85e

4 files changed

Lines changed: 60 additions & 16 deletions

File tree

‎docs/architecture/rfcs/harness-selection-dsh-pi-v0.md‎

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,21 @@ Open gaps before this binding is a promoted production default:
118118
(`@deepseek-ai/dsh-tool-fs`, `@deepseek-ai/dsh-tool-bash`). Without them a live
119119
model can answer but cannot act, and the Turn ends in a validation failure
120120
rather than in work.
121-
- the host-mode plan still maps the unattended intent to the compatibility path:
122-
`isolated_headless_turn` carries `turn_host: generic-cli`
123-
(`loopx/host_mode_planner.py`), so the `loopx turn plan` command it prints
124-
names `--host generic-cli` instead of the selected `dsh` default recorded above,
125-
which is correct as a labelled rollback path but is not labelled as one. The
126-
plan's `--host-identity` list deliberately covers visible
127-
hosts only, because a headless-only host such as `dsh` cannot own a visible
128-
session; the unattended mapping itself still needs a decision between naming
129-
the resolved default, offering a `dsh` variant, or labelling the emitted
130-
command as the rollback path.
121+
- the host-mode plan used to map the unattended intent to the compatibility
122+
path: `isolated_headless_turn` carried `turn_host: generic-cli`
123+
(`loopx/host_mode_planner.py`), so the `loopx turn plan` command it printed
124+
named `--host generic-cli` instead of the selected `dsh` default recorded
125+
above, which is correct as a labelled rollback path but was not labelled as
126+
one. **Decided and shipped:** the plan takes the "write out the resolved
127+
default" option. The preview command pins no host, the pinned compatibility
128+
variant is reported as `plan_command_rollback`, and the typed
129+
`turn_mapping.host_selection` states which of the two a command is; targets
130+
that genuinely need a visible identity (transitions into `visible_tui`) still
131+
pin their host. `docs/reference/protocols/host-mode-plan-v0.md` defines
132+
`turn_mapping.host` as the mode's declared host and scheduler context rather
133+
than as an already-resolved concrete host. The plan's `--host-identity` list
134+
still covers visible hosts only, because a headless-only host such as `dsh`
135+
cannot own a visible session.
131136

132137
## Evidence Baseline
133138

‎docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,17 @@ CLI 的情况下把管家悄悄换成 operator 模型。
9797
- LoopX 的 DSH Turn 组合必须显式列出托管动作所需的工具行
9898
(`@deepseek-ai/dsh-tool-fs`、`@deepseek-ai/dsh-tool-bash`)。缺少它们时,真实模型
9999
只能作答而无法动手,Turn 会以验证失败而不是产出工作结束。
100-
- 宿主模式计划仍把无人值守意图映射到兼容路径:`isolated_headless_turn` 的
100+
- 宿主模式计划此前把无人值守意图映射到兼容路径:`isolated_headless_turn` 的
101101
`turn_host` 取 `generic-cli`(`loopx/host_mode_planner.py`),因此它打印的
102102
`loopx turn plan` 命令写的是 `--host generic-cli`,而不是上文记录的已选 `dsh`
103-
默认值;作为回滚路径本身没错,但没有被标注为回滚路径。该计划的
104-
`--host-identity` 列表只覆盖可见宿主是有意为之——像
105-
`dsh` 这种仅 headless 的宿主无法拥有可见会话;但无人值守映射本身仍需在"写出解析
106-
后的默认值 / 提供 `dsh` 变体 / 把该命令标注为回滚路径"之间做出决定。
103+
默认值;作为回滚路径本身没错,但没有被标注为回滚路径。**已决并已落地:**计划采用
104+
"写出解析后的默认值"这一支——预览命令不再 pin 任何 host,pin 死的兼容变体报为
105+
`plan_command_rollback`,类型化的 `turn_mapping.host_selection` 说明该命令属于
106+
哪一种;真正需要可见身份的路径(转入 `visible_tui` 的 transition)仍然 pin 具体
107+
宿主。`docs/reference/protocols/host-mode-plan-v0.md` 已把 `turn_mapping.host`
108+
定义为该模式的声明宿主与调度上下文,而不是"具体宿主已经解析完成"。该计划的
109+
`--host-identity` 列表仍只覆盖可见宿主,因为像 `dsh` 这种仅 headless 的宿主无法
110+
拥有可见会话。
107111

108112
## 证据基线
109113

‎docs/reference/protocols/host-mode-plan-v0.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,23 @@ Each `mode_options[]` entry includes the connector id, readiness, required
142142
host capabilities, Turn mapping when one exists, scheduler execution context,
143143
quota guard command, and required proofs.
144144

145+
`selected_turn_mapping` fields mean:
146+
147+
- `host` is the mode's **declared** host: the scheduler context the readiness
148+
statement and capability requirements are written against. It is not a claim
149+
that this concrete host has already been resolved for the run; the runtime
150+
resolves the concrete host (including a credential- or configuration-resolved
151+
default) when `plan_command` runs.
152+
- `host_selection` is `resolved_default` when the command deliberately leaves
153+
host resolution to `loopx turn plan`/`run-once`, and `pinned` when the command
154+
carries an explicit `--host`.
155+
- `plan_command` is the command to run for this mapping. When `host_selection`
156+
is `resolved_default` it pins no host, so it cannot freeze the compatibility
157+
adapter path as the product default.
158+
- `plan_command_rollback` is the pinned compatibility variant for an operator
159+
who deliberately wants that path instead of the resolved default. It is
160+
present only when the mapping resolves its host.
161+
145162
## Functional Points
146163

147164
The selector provides four concrete functions:
@@ -150,7 +167,8 @@ The selector provides four concrete functions:
150167
users and agents to infer visible/headless/gateway/timer behavior manually.
151168
2. **Turn mapping:** for unattended execution, print the exact `loopx turn plan`
152169
preview that preserves host, execution mode, scheduler owner, agent id, and
153-
available capabilities.
170+
available capabilities, while leaving the concrete host to the runtime's
171+
resolved default and reporting the pinned variant as the rollback command.
154172
3. **Readiness surface:** report which advertised capabilities are missing
155173
before a mode can be trusted.
156174
4. **Safe handoff plan:** name transitions such as visible bootstrap to

‎examples/host-mode-plan-smoke.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,23 @@ def test_hybrid_requires_two_ready_modes_and_names_handoffs() -> None:
348348
assert transition["preserves_agent_id"] is True, transition
349349
assert transition["spends_quota"] is False, transition
350350
assert "--agent-id codex-main-control" in transition["guard_command"], transition
351+
# The transition policy is the same rule as the mode preview: a target that
352+
# resolves its host must not pin the compatibility adapter path, while a
353+
# target that needs a visible identity still pins it.
354+
by_id = {item["transition"]: item for item in two_ready["transitions"]}
355+
for transition_id in (
356+
"visible_bootstrap_to_isolated_headless_turn",
357+
"im_gateway_to_isolated_headless_turn",
358+
):
359+
command = by_id[transition_id]["target_turn_plan_command"]
360+
assert "--host" not in command, (transition_id, command)
361+
for transition_id in (
362+
"isolated_headless_turn_to_visible_tui_escalation",
363+
"shell_service_to_visible_tui_escalation",
364+
):
365+
command = by_id[transition_id]["target_turn_plan_command"]
366+
assert "--host codex-cli" in command, (transition_id, command)
367+
assert "--execution-mode interactive-visible" in command, (transition_id, command)
351368

352369

353370
def test_identity_gate_and_no_spend_boundary() -> None:

0 commit comments

Comments
 (0)