返回 CodeWhale
COMMAND_CONTROL_PLANE.md
根目录 / docs / zh_hans / COMMAND_CONTROL_PLANE.md
1 # 共享命令/控制平面契约
2
3 > 英文原文:[COMMAND_CONTROL_PLANE.md](../COMMAND_CONTROL_PLANE.md)。
4 > 最后与英文同步日期(last synced with English revision):2026-09-29。
5
6 Issue #1888 和 #4022。
7
8 Codewhale 在三个表面上暴露同一套生命周期操作:输入到输入区(composer)的斜杠命令、
9 绑定的热栏(hotbar)槽位,以及一个 CLI 入口点。在这份契约之前,这三个表面可能——也确实——
10 发生了漂移:`/fleet status` 显示的是当前会话的子智能体(subagent),而
11 `codewhale fleet status` 读取的是持久账本;CLI 的 Lane 动词则完全没有对应的斜杠命令。
12
13 契约就是一张类型化的描述符表,加上每个域一个执行器,位于
14 [`crates/lane/src/control.rs`](../../crates/lane/src/control.rs) 和
15 [`crates/tui/src/fleet/control.rs`](../../crates/tui/src/fleet/control.rs)。
16 `codewhale-lane` 是薄 CLI 门面和 TUI 都已经依赖的最低层 crate,
17 所以契约放在这里,既只有一份,又不必分叉。
18
19 ## 词汇
20
21 以下定义不变且承重:**Fleet = 谁**,**Workflow = 顺序**,**Lane = 一个运行中的 Workflow**,
22 **Runtime = 在哪/如何**。Auto-Review 是一种权限档位(posture),绝不是评审角色。
23 没有“Operation”这个产品名词;内部的 `ControlOperation` 类型命名的是控制平面*动词*,
24 绝不出现在面向用户的文案里。
25
26 ## 描述符钉住了什么
27
28 每个 `(domain, verb)` 对都有且只有一个 `OperationDescriptor`,以 `<domain>.<verb>`
29 形式的稳定 id 为键:
30
31 | 字段 | 含义 |
32 | --- | --- |
33 | `id` | `lane.status`、`fleet.interrupt`……——在每个表面上、每份回执(receipt)里都是同一个字符串 |
34 | `authority` | `read` 或 `write`。这不是权限档位:它说明该动词是观察持久状态还是改变持久状态 |
35 | `persistence` | 效果落在哪个持久存储(`lane_registry`、`fleet_ledger`) |
36 | `target` | 它作用于哪个确切的身份(`none`、`lane_run`、`fleet_worker`、`fleet_run`) |
37 | `retry` | `idempotent` 或 `unsafe` |
38 | `surfaces` | 哪些表面提供它 |
39 | `backend` | `Implemented`、`NotImplemented { hint }` 或 `SurfaceLimited { available_on, hint }` |
40 | `slash_command` / `cli_invocation` | 确切的绑定;热栏 action id 始终是 `slash.<slash_command>` |
41
42 当前的动词表:
43
44 | 动词 | Lane | fleet |
45 | --- | --- | --- |
46 | `list` | 读,整个注册表(registry) | 读,整个账本 |
47 | `status` | 读,一个 Lane | 读,整个账本 |
48 | `interrupt` | 写,一个 Lane(幂等) | 写,一个 worker(幂等) |
49 | `restart` | **无后端**——Lane 是被重新创建,而不是重启 | 仅 CLI(驱动管理器循环) |
50 | `resume` | **无后端**——已停止的 Lane 其 Runtime 会话已经消失 | 写,一次运行(幂等) |
51
52 ## 没有表面会宣传自己做不到的事
53
54 `OperationDescriptor::availability(surface, ctx)` 返回 `Available`,
55 或返回带净化后提示的类型化 `UnavailableReason`:
56
57 - `backend_not_implemented`——没人实现过它。所有表面都拒绝。
58 - `surface_not_supported`——后端存在,但不在这里。提示会指出可用的那个表面
59 (`codewhale fleet restart <worker-id>`)。
60 - `no_lane_registry` / `no_fleet_ledger`——持久存储还不存在。
61
62 可用性探测是**只读**的。`LaneRegistry::open_default` 和 `FleetManager::open`
63 都会顺带创建自己的存储,所以状态类动词会先探测 `lane_registry_root()` /
64 `fleet_ledger_path()`。否则“这个工作区没有 fleet 账本”就会悄悄变成
65 “这是我刚创建的一个空 fleet 账本”。
66
67 ## 精确的运行身份
68
69 `parse_target` 是三个表面共用的唯一目标解析器:只接受一个 token、只接受精确 id
70 (没有前缀匹配或模糊匹配)、允许 ASCII 字母数字加 `-`、`_`、`.`,不允许路径分隔符,
71 并且当无目标动词被传入参数时硬性拒绝。
72
73 写入可以通过追加 `@<lifecycle-seq>` 来**加栅栏**:
74
75 ```
76 codewhale lane interrupt lane-a1b2c3d4@3
77 /lane interrupt lane-a1b2c3d4@3
78 ```
79
80 如果持久记录已经越过序号 3,该动词会以 `conflict` 失败并给出所观察到的序号,
81 而不是去停止此刻碰巧在那里的对象。
82
83 ## 回执
84
85 每次调用都返回一个 `ControlReceipt`,携带操作 id、表面、权限、持久化作用域、可用性、目标、
86 `LifecycleOutcome`(`inspected`、`transitioned`、`no_change`、`rejected`、`failed`)、
87 所观察到的生命周期序号、可重试性、可选的有界净化失败信息,以及可选的有界运行分页。
88 `ControlReceipt::render()` 是唯一的渲染器;CLI 打印它,斜杠命令把它作为消息返回。
89 Lane 动词上的 `--json` 输出的也是同一个结构体。
90
91 ## 类型化的未知
92
93 运行 DTO 绝不暗示“不存在”。`Known<T>` 要么是 `Known(value)`,要么是 `Unknown(reason)`,
94 其中 reason 为 `not_recorded`、`not_applicable` 或 `redacted`,并且渲染为 `<not_recorded>`,
95 而不是空白或看似合理的默认值。
96
97 具体来说:fleet 回执的 `FleetResolvedRoute` 只记录**生效**的思考档位(reasoning tier),
98 所以 `requested_reasoning` 是 `not_recorded`——它不会用生效值回填,
99 `reasoning_downgraded()` 返回 `None` 而不是猜测。Lane 注册表完全不记录路由或用量,
100 因此那些字段一律是 `not_recorded`。fleet 运行是按任务、而非按运行加栅栏,
101 所以 fleet 运行的 `lifecycle_seq` 是 `not_applicable`。
102
103 ## 边界与脱敏
104
105 - 运行列表是分页的:`DEFAULT_RUN_LIST_LIMIT`(50),硬上限 `MAX_RUN_LIST_LIMIT`(200),
106 并且分页会报告 `total` 和 `truncated`,这样一个边界永远不会被误认为空结果。
107 - 状态 worker 行和检视工件(artifact)行上限为 24,并带显式的省略提示。
108 - 回执详情上限为 `MAX_DETAIL_LINES`(40)行,每行 `MAX_DETAIL_LINE_CHARS`(240)个字符。
109 - 每个对操作者可见的字符串都要过 `sanitize_line`:以 `$HOME` 为根的路径折叠为 `~/…`,
110 形似凭据的 `key=value` 对和已知的 token 前缀(`sk-`、`ghp_`、`xoxb-`、`Bearer`……)
111 变成 `[redacted]`。
112
113 ## 模型可见的工具表面
114
115 未变。这项工作不新增任何工具、任何工具参数、任何提示词文本;面向模型的子智能体表面仍然只有
116 `agent`。不需要做工具 schema 的回归度量。
117
118 ## 测试
119
120 - `crates/lane/src/control.rs`——描述符表的完整性、两个域上五个动词的对称性、
121 跨表面的 authority/persistence/target 一致性、可用性规则、目标解析与生命周期栅栏、
122 回执往返、边界限制与脱敏;另有执行器测试,证明三个表面针对同一个持久 Lane
123 得到逐字节相同的结果,以及 interrupt 是幂等且带栅栏的。
124 - `crates/tui/src/fleet/control.rs`——带类型化未知的路由/用量 DTO 投影、有界分页与行、
125 在账本缺失时如实报告而不创建、仅 CLI 的 `fleet.restart`,以及 `fleet.status`
126 的跨表面身份一致。
127 - `crates/tui/src/commands/groups/core/lane.rs` 和 `…/fleet.rs`——斜杠动词映射到共享操作,
128 `/fleet status` 读取持久账本而不是会话子智能体,并且裸派发(热栏触发的形式)是只读的。
129 - `crates/cli/src/lib.rs`——CLI 在相同的 id 下恰好暴露所声明的 Lane 动词,
130 且 `lane stop` 是 `lane interrupt` 的兼容写法。
131
131 lines MARKDOWN