[v0.1/CLI Review] remote daemon auto-spawn 契約の矛盾を解消する #141
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#141
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
v0.1.0 の公開契約レビューで、remote daemon の auto-spawn 方針について normative reference 同士の矛盾を確認した。
docs/reference/cli.mdの scripting contract は次のように断言している。一方、実装と他の設計文書は intent-based policy を採用している。
sessions spawnと window launch は local/remote のどちらでも daemon を auto-spawn する。felis-daemon relay --no-spawnを使う。docs/reference/ipc.md(Cross-host carrier / CLI clients)とdocs/explanation/architecture/control-surfaces.mdはこの挙動を明記している。crates/felis-cli/src/conn.rsのDial::OpsOrSpawnはReconnector::dial_launchを通り、SSH carrier では relay に spawn を許可する。crates/felis-daemon/src/relay.rsは--no-spawnがない場合connect_or_spawn_persistentを実行する。つまり現状では、
felis --host remote sessions spawnやfelis --host remoteは cold remote daemon を起動するが、CLI の公開 scripting contract は起動しないと約束している。なぜ release blocker か
これは単なる説明上の typo ではなく、スクリプトが remote host に永続プロセスを作るかどうかという副作用の契約である。最初の release 後にどちらかへ揃えると、既存 automation の lifecycle assumption を壊す。
現在の intent split(spawn/window は起動、read/drive は起動しない)は control-surface rationale と整合しているため、第一候補は実装を維持して
cli.mdを修正すること。ただし release 前に明示的に決定し、テストで固定する必要がある。提案
docs/reference/cli.mdの auto-spawn 項目を、carrier ではなく verb intent が決める契約へ修正する。sessions spawnと window launches は auto-spawn を許可。--no-spawnを付けることで実現する。Acceptance criteria
docs/reference/cli.md、docs/reference/ipc.md、docs/explanation/architecture/control-surfaces.mdが同じ per-verb policy を記述する。relay --no-spawnになることを確認する。sessions spawnと window launch はrelayに--no-spawnを付けないことをテストする。felis sessions spawn --help/ root help / scripting reference から、remote side effect を予測できる。CHANGELOG.mdとskills/felisを cascade する。Related
--no-spawnを選ぶ公開 policy matrix は対象外。Parent: #12 / #31
Sources
docs/reference/cli.md:306-309docs/reference/ipc.md:93-101,1946-1951docs/explanation/architecture/control-surfaces.md:291-303crates/felis-cli/src/conn.rs:43-86crates/felis-client-core/src/dial.rs:118-131crates/felis-client-core/src/connector.rs:318-331crates/felis-daemon/src/relay.rs:1-7,87-100Triage (2026-09-05)
Verdict: accept, P1. Verified:
docs/reference/cli.md:306-309promises no remote auto-spawn whileconn.rsDial::OpsOrSpawnandrelay.rswithout--no-spawndo spawn. Keep the implementation (verb intent decides, carrier does not) and fix the reference, as proposed. The policy matrix test (local/SSH × spawn/window/read/drive/observer/bridge/probe) is the acceptance gate; #42's completion rule belongs in the same matrix. Parent: #55.Triage plan (2026-09-05)
Source-grounded triage against
mainat07a77ae4, reviewed through six rounds of an independent reviewer (piluna then sol); every finding was verified against the source and folded in, and the design decisions below were settled with the maintainer on 2026-09-05. The order that supersedes the tracker's is posted on #12: this issue lands before #29 (schemas, goldens, and reference tables freeze there).Remote auto-spawn: fix the reference, pin the verb → dial matrix
Claim check
Confirmed.
docs/reference/cli.md:306-309says "Over--host, remotedaemons are never spawned automatically"; the code and the other two
normative docs say intent decides:
crates/felis-cli/src/conn.rs:43-86Dial::{Ops, Observer, OpsOrSpawn};OpsOrSpawn→Reconnector::dial_launch(felis-client-core/src/dial.rs:118-134)→
RemoteSpawn::Allowover SSH;Ops/Observer→conn.rs:114-116RemoteSpawn::Refuse→relay --no-spawn(connector.rs:328).sessions spawnOpsOrSpawn(cli_sessions.rs:641); everyother sessions verb
Ops(:529,:888);daemon statusOps(
cli_daemon.rs:55);notifications subscribeObserver(
cli_notifications.rs:118); window launchesRemoteSpawn::Allow(
dial.rs:269); dynamic completions are local-only (#42).docs/reference/ipc.md:93-101anddocs/explanation/architecture/control-surfaces.md:291-303alreadystate the split.
Verdict
accept as proposed. Keep the implementation; the reference sentence is
the bug.
Approach
docs/reference/cli.md"The scripting contract" → "Auto-spawning": onerule for both carriers:
sessions spawnand window launches spawn; everyread/drive verb,
notifications subscribe,daemon status,version,doctor,felis bridge, and completions do not. Retargets are twodials: the source dial (
window retarget,sessions retarget-all)never spawns, and the landing the receiving window then makes on the
target host goes through
dial_and_land(
felis-client-core/src/dial.rs:260-328,RemoteSpawn::Allow).RemoteSpawngoverns only the SSH relay:connect_carrier(
connector.rs:522-548) callslocal::connectdirectly for a localcarrier and never
connect_or_spawn_daemon. So the target-sidelanding spawns over SSH (attach and create alike) and does not spawn
on a cold local socket — that retarget fails. Document exactly that
split as its own two rows (SSH target: spawns; local target: cold
socket fails) rather than "one rule for both carriers", and leave the
local behavior as-is: making a local retarget spawn is new behavior,
not a doc fix, and belongs to a separate issue if wanted; over SSH the non-spawning forms
dial
felis-daemon relay --no-spawn. Add the--no-spawnsentence to"Alternate carriers" if that section names the relay command.
skills/felis/SKILL.md: the same sentence wherever it describes remoteuse.
CHANGELOG.md: not needed (documentation of existing behavior).Dialbundles both and the bridge picks itsOfferper operation(
cli_bridge.rs:2877-2882, always throughconn::dial= Refuse). Makeeach verb's spawn policy (
RemoteSpawn::Allow/Refuse) aconstonits handler and add one test enumerating every verb (bridge included,
as Refuse with operation-dependent offers) with its expected policy,
and, for the retarget landing, two tests: the SSH relay command carries
no
--no-spawn(RemoteSpawn::Allowthroughdial_and_land), and alocal cold socket yields a connect error with no daemon spawned;
the existing
relay_commandtests (connector.rs:1324-1342) alreadyprove
Allow→ no flag andRefuse→--no-spawn.Dependencies / risk / labels
None. S. Keep labels. Parent #55.