feat(cli): add supervised Fleet workspace - #8869
Conversation
Fleet Stage 1B E2ESetup
Scenario
Expected
Result (2026-08-10)
|
Teammates and the supervisor spawn detached from the leader's terminal, so `stdio: 'ignore'` meant a subprocess that died during startup left no trace at all: the leader could only report "exited before becoming ready", with no exit code, no output and nothing to read afterwards. - Capture supervisor and per-teammate output to log files under the agent-view store, falling back to the previous behaviour when the log cannot be opened so diagnostics never block a spawn. - Carry the exit code and a bounded tail of the captured output into `lastError`, through the supervisor `state` event, into RemoteSession, and out to the composer's status label and the thrown start error. - Emit a terminal `state` event on spawn failure; previously nothing did, so the leader waited out the full readiness timeout for a failure the supervisor already knew about. - Add `QWEN_FLEET_DEBUG=1` lifecycle tracing across spawn, config load, auth and handshake. Leader-side breadcrumbs go to a log file rather than stderr, which Ink owns. - Always write a teammate's fatal error to its log, flag or not. - Show the Fleet grid with a single teammate; leader-beside-one-teammate is the smallest scenario worth debugging and it used to fall back to tabs. - Add an end-to-end dogfood harness driving the real subprocess path with a stubbed model backend, plus troubleshooting docs.
Dogfooding pass: Fleet now actually runs, and fails legiblyPushed as The core problemBoth the supervisor and every teammate spawned with No exit code, no output, nothing to read afterwards. That is why the previous attempt at a real run couldn't be diagnosed. What changed
Real run — leader + 1 real subprocess teammate
Separate OS processes, from Startup and handshake, from Leader side ( Final frame — leader beside a single teammate, both transcripts live: Every observable on the checklist: process start, handshake/auth, status transitions ( Multi-teammate
One artifact to check with a human eye: at 3 panes with a full approval dialog, the third pane's title looked truncated in my text capture ( The 50ms poll loop — measuredSteady-state CPU, all sessions idle, sampled from
Each teammate issues a Recommendation: keep it for the MVP, don't redesign it in this PR. It is not a correctness problem, the feature is experimental and off by default, and socket fan-out is already scoped for Stage 2. But ~13% of a core for three idle agents is real battery and fan cost, so this should not ship on-by-default with polling still in place. Suggest it becomes an explicit Stage 2 exit criterion rather than a nice-to-have. Approval routing — reachable, and it worksThe run disproves the assumption that this path is unreachable. A read-only teammate calling Answering it (↓ → Enter) routed the decision back through Two caveats on how it was reached: the teammate was in Auto mode and its classifier call failed against the stub backend, so it fell back to asking. With a working classifier Verdict: approval routing should stay a Stage 1B acceptance criterion. It is implemented, reachable and now demonstrated. The remaining gap — plan-required teammates — should be named as its own deferred item rather than being read as "approvals don't work". Also foundA stale supervisor survives its leader by design (10-min idle grace). If its store is deleted underneath it, the next launch dies with TestsNew coverage: Two pre-existing How to test this locallygit fetch origin codex/feat-fleet-1b-runtime && git checkout codex/feat-fleet-1b-runtime
npm install && npm run bundle
# A. Scripted end-to-end run, no API key needed
npx tsx integration-tests/terminal-capture/fleet-dogfood.ts 1 # leader + 1
npx tsx integration-tests/terminal-capture/fleet-dogfood.ts 3 # leader + 3
# prints six pass/fail gates and an evidence dir (logs, PID samples, TUI capture, idle CPU)
# B. By hand with your own credentials
echo '{"experimental":{"fleet":true}}' > .qwen/settings.json
QWEN_FLEET_DEBUG=1 qwen # terminal ≥120 cols
# then: /coordinate investigate <something> and use one or two teammates
# Watch it work
tail -f ~/.qwen/daemon/fleet-debug.log ~/.qwen/jobs/*/worker.log
pgrep -af internal-fleet-teammate # separate PIDs per teammate
# Prove the diagnostics: make a teammate die on startup
QWEN_FLEET_DEBUG=1 OPENAI_BASE_URL=http://127.0.0.1:1 qwen
# the leader now names the exit code and the log path instead of "did not become ready" |
Two dogfooding findings, deferred to Stage 2Both surfaced while dogfooding this PR. Neither is a Stage 1B regression — the first is a property of the polling design this stage deliberately shipped with, the second is pre-existing supervisor lifecycle behaviour. Neither blocks #8869. Both are now tracked in #8842 with acceptance criteria, so they do not live only in review chat. 1. Worker poll loop idle CPUEach teammate issues a
Recorded in #8842 as the baseline to regress against. Socket/event fan-out was already in Stage 2 scope; it is now an explicit exit criterion rather than a nice-to-have, on the grounds that Fleet must not become default-on while polling remains. 2. Stale supervisor wedges later launchesA supervisor outlives its leader by 10 minutes. Delete its store while it is alive and the next launch cannot authenticate to it (the token lived in the deleted Scope callFixing either here would pull socket fan-out or supervisor recovery into Stage 1B, both of which are Stage 2 by design. Keeping #8869 to Stage 1B. |
|
I found two blockers in the supervised-process lifecycle and authorization model:
The overall subprocess and UI flow looks coherent, but these ownership boundaries should be fixed before merge. |
|
Pausing this draft while we reset the direction around the merged #8804 baseline. The subprocess experiment is not covered by #8804, but the current stack also carries a broader Fleet platform and has unresolved lifecycle and authorization blockers, so I’m not advancing it toward merge. If dogfooding shows a concrete isolation gap, the next step should be a smaller vertical slice from |
|
Follow-up: the remaining Qwen ↔ Herdr state-detection gap is now reproduced in herdrdev/herdr#2756. That stays in Herdr’s detection layer; it does not justify reviving the Qwen-side Reporter or Fleet stack. |
|
Closing as superseded. Not because independent teammate processes were the wrong idea, but because this is not how they get built here. The supervisor, worker entrypoint, credential scoping and lifecycle handling this PR implements already ship in the repository for other callers — The topology does not survive either. This stack is keyed one-fleet-per-project with a single-leader lock, which makes a second Qwen session in the same repository a read-only roster viewer. That participant — a session someone already started, doing its own work — is the one the replacement design is about. Replacement: #9399, tracked on #8724. It reuses the fleet plan's semantic/terminal channel split, its turn correlation, and its verification that the existing file lock model needs no upgrade. See #8718 for a correction to that plan's closing rationale. The branch is not deleted, and nothing here is lost if the direction changes. 中文说明作为已被取代关闭。不是因为"独立 teammate 进程"这个想法错了,而是因为在这个仓库里它不该这样建。 本 PR 实现的 supervisor、worker 入口、凭据隔离与生命周期处理,仓库里已经为其他调用方提供了: 拓扑同样不成立。本栈按"一个项目一个 fleet"加单 leader 锁,这会让同一仓库中第二个 Qwen 会话变成只读的 roster 观察者。而那个参与者——别人已经启动、正在做自己工作的会话——恰恰是替代设计要服务的对象。 替代方案:#9399,跟踪于 #8724。它沿用了 fleet 计划的语义/终端双通道划分、turn 关联,以及"现有文件锁模型无需升级"的验证。对该计划结案理由的更正见 #8718。 分支不会删除;若方向变化,这里的工作不会丢失。 |
packages/cli/src/agent-view/ — supervisor-server, supervisor-client, supervisor-store, supervisor-process, supervisor-runner, terminal-bridge, protocol and current-cli-argv — has no consumer. All 87 exported symbols are unreferenced outside the directory, no module imports it by path or by name, no CLI flag or sentinel string reaches it, and no build config mentions it. It only imports node builtins and core, so it is a leaf. It is the bespoke supervisor and PTY half of the fleet architecture, whose last open PR (#8869) is closed. The peer-session design that replaces it does not build a terminal or supervisor layer natively, so nothing here gets a consumer later. Left in place because it is live and unrelated despite the similar name: packages/cli/src/ui/components/agent-view/ (AgentTabBar, AgentChatView, AgentComposer), imported by DefaultAppLayout. Refs #9399
What this PR does
This PR upgrades the Fleet preview from the Stage 1A in-process contract to a usable supervised-process MVP. With
experimental.fleet: true, teammates run as independent Qwen Code OS processes, exchange prompts and peer messages through an authenticated supervisor, and project bounded semantic transcripts back into the leader UI.Wide terminals show the leader and up to three teammates at the same time, while narrow terminals, dialogs, screen readers, and native scrollback keep the existing single-view tabs. The active tab remains the composer target.
The runtime also handles startup races, worker crashes, cooperative and forced shutdown, per-worker credential revocation, stale supervisor protocol replacement, subprocess identity, nested-team denial, and Fleet credential scrubbing from child environments.
This is a stacked PR based on #8859.
Why it's needed
Stage 1A exposes the Fleet workflow and contracts, but all teammates still share the leader process and only one detailed transcript is visible at a time. This change makes the preview independently executable and gives it the same-terminal multi-view behavior needed for useful
/coordinatedogfooding.Reviewer Test Plan
How to verify
experimental.fleet, keepexperimental.agentTeamdisabled, restart Qwen Code, and open a terminal at least 120 columns wide./coordinatewith two independent read-only investigation tasks. Confirm both teammates start with distinct OS PIDs and the leader plus both teammate transcripts are visible in one frame.Evidence (Before & After)
Before: Fleet teammates run in the leader process and detailed Agent View content is shown one tab at a time.
After: Fleet teammates use supervised OS processes and a wide terminal renders the leader plus multiple bounded semantic transcript panes simultaneously.
Focused supervisor, runtime, grid, identity, messaging, lifecycle, and environment-scrubbing tests pass. Core typecheck and the targeted Core build pass.
The preview has now been run end to end.
integration-tests/terminal-capture/fleet-dogfood.tsdrives a real PTY leader, a real supervisor process and real teammate subprocesses through the productiondefaultSpawnWorkerpath over the real socket, with only the model backend stubbed. Leader + 1 and leader + 3 both pass all six gates: separate OS PIDs, handshake, status transitions, live transcript projection, teammate to leader reporting, a cross-process approval answered from the leader, a leader to teammate follow-up through the mailbox, and no surviving teammate process after the leader exits. Full logs, PID samples, TUI capture and idle-CPU numbers are in the dogfooding comment on this PR.The earlier note that a run was blocked by an ACP bridge / workspace-link mismatch applied to a stacked worktree; a clean checkout with its own
npm installdoes not hit it.Tested on
Environment (optional)
macOS, Node.js 22.22.0, source-mode stacked worktree.
Risk & Scope
Linked Issues
Part of #8841.
中文说明
这个 PR 做了什么
这个 PR 将 Fleet Preview 从 Stage 1A 的进程内契约升级为可用的受监管子进程 MVP。开启
experimental.fleet: true后,teammate 会作为独立的 Qwen Code 操作系统进程运行,通过带认证的 supervisor 交换 prompt 和 peer message,并将有界的语义化 transcript 投影回 leader UI。宽终端会同时展示 leader 和最多三个 teammate;窄终端、对话框、读屏模式以及原生 scrollback 继续使用现有单视图 tabs。当前激活的 tab 仍然是 composer 的发送目标。
运行时同时处理启动竞态、worker 崩溃、协作式与强制关闭、每个 worker 的凭据撤销、旧 supervisor 协议替换、子进程身份、禁止嵌套团队,以及从子环境中清除 Fleet 凭据。
这是一个以 #8859 为 base 的 stacked PR。
为什么需要它
Stage 1A 已经暴露 Fleet workflow 和 contracts,但所有 teammate 仍共享 leader 进程,并且一次只能查看一个详细 transcript。这个改动让 preview 真正由独立进程执行,并提供
/coordinatedogfood 所需要的单终端多视图体验。Reviewer 测试计划
如何验证
experimental.fleet,保持experimental.agentTeam关闭,重启 Qwen Code,并使用至少 120 列的终端。/coordinate启动两个独立的只读调查任务。确认两个 teammate 使用不同的操作系统 PID,并且同一帧中能同时看到 leader 和两个 teammate transcript。证据(Before & After)
Before:Fleet teammate 在 leader 进程内运行,详细 Agent View 内容一次只显示一个 tab。
After:Fleet teammate 使用受监管的独立操作系统进程,宽终端会同时渲染 leader 和多个有界语义 transcript pane。
Supervisor、runtime、grid、identity、messaging、lifecycle 和环境凭据清理的定向测试均通过。Core typecheck 和定向 Core build 通过。最终独立代码 gate 为 0 Critical、0 Major。
该预览现已完成端到端真实运行。
integration-tests/terminal-capture/fleet-dogfood.ts通过真实 PTY 启动 leader,并经由生产路径defaultSpawnWorker拉起真实的 supervisor 与 teammate 子进程,走真实 socket 通信,仅 model 后端为桩实现。leader + 1 与 leader + 3 均通过全部六项校验:独立的操作系统 PID、handshake、状态迁移、transcript 实时投影、teammate 向 leader 汇报、在 leader 侧应答跨进程审批、经 mailbox 下发 leader 到 teammate 的后续消息,以及 leader 退出后无残留 teammate 进程。完整日志、PID 采样、TUI capture 与空闲 CPU 数据见本 PR 的 dogfooding 评论。此前关于 ACP bridge / workspace-link 不匹配导致无法启动的说明仅适用于 stacked worktree;在独立 checkout 中自行
npm install不会遇到该问题。测试平台
环境(可选)
macOS、Node.js 22.22.0、源码模式 stacked worktree。
风险与范围
关联 Issue
#8841 的一部分。