docs/design/workspace-runtime-architecture.md
本文是 qwen serve 从 Session-centric 迁移到
Workspace-runtime-centric 的目标设计与堆叠交付契约。该设计由四个可独立合并的
PR 渐进落地;本文描述最终形态,不表示 foundation PR 已实现所有 capability。
当前落地进度(Foundation + MCP):已实现 Bridge 权威的五态 lifecycle snapshot、 workspace 级单调 epoch、完整物理 work lease、绝对启动 deadline、无参数
ensure/status、10 分钟可续期保活、drain/removal/shutdown admission,以及 SDK 的 primary/qualified runtime 方法,以及 MCP 的 revision、Catalog 和 runtime management。当前ensure会在同一观察预算内准备 MCP;Extensions、 Skills、Tools 的 generation/revision、Catalog 投影和 operation 状态机仍属于后续阶段。
为避免把后续阶段的契约误读为 foundation 已有行为,本文使用以下标记:
| 领域 | Foundation(已实现) | Target(后续阶段) |
|---|---|---|
| Runtime lifecycle | Bridge 权威五态、workspace 单调 epoch、物理 work lease、启动 deadline、drain/removal admission | capability 健康状态参与统一对外投影 |
ensure | 无参数;确保 ACP Channel 完成 initialize 并准备 MCP;成功后续期 10 分钟 | 准备 Extensions、Skills、Tools,并通过 capability status 表达收敛 |
status | lifecycle、runtimeLive、runtimeEpoch 与 MCP capability 快照 | 其他 capability、generation、revision、error 和 operation 投影 |
| SDK | primary/qualified runtime 与 MCP config/runtime 方法 | 其他 Catalog、operation 和统一 deadline 的完整 owner-aware API |
除明确标为当前实现的 MCP 契约外,第 9~14 节中其他 capability、Catalog、generation、 revision 和 operation 的详细状态机均是 Target 契约。
核心目标只有一个:
Workspace 是运行时、隔离和管理边界;Session 只是 Workspace Runtime 中用于对话与执行的消费者。
仍保留的旧入口会明确标为兼容 adapter,而不是另一套架构。整个 stack 只有通过 第 14 节的验收条件后,才算完成迁移。
早期 daemon 以 Session 为入口。创建或加载 Session 后才启动 ACP 子进程, 随后初始化 Config、Skills、Tools、MCP 和 Extensions。管理页面因此逐渐出现了 多套兜底流程:
这些流程把“管理工作区”和“运行一次对话”绑定在一起。它们也让 daemon、Bridge、 ACP 和前端同时拥有一部分生命周期或状态判断,难以回答以下问题:
Workspace-runtime-centric 架构通过一个工作区级运行时解决这些问题,而不是创建 隐藏 Session 或额外的“管理 Session”。
WorkspaceRuntime 聚合是唯一 runtime ownership 边界;Bridge 驱动物理 Channel、
epoch 和 lease,Coordinator 管理 capability 收敛、operation 和对外投影。二者都是
同一个 WorkspaceRuntime 的内部组件,不是并列 runtime owner。ensureRuntime() 请求完整 Workspace Runtime,
再轮询权威 operation/status 和读取 Catalog;前端不编排内部初始化步骤。以下条件是实现选择的边界,不是建议:
sessionId,内部不通过 sessionOrThrow()、
requestSessionStatus() 或任意 Session 查找 Config。WorkspaceRuntime,不属于第一个 Session,
也不由最后一个 Session 决定何时退出。ready 只属于当前 runtime epoch;旧 epoch 的数据最多是 stale。ensure、其他
runtime command 或 Session 创建来启动;Foundation 暂时保留 production startup
preheat 兼容策略。对外不提供按 capability 选择的启动接口。/runtime/status 暴露收敛状态。qwen serve daemon
├── 持久化控制面
│ ├── Global config owner
│ │ ├── User scope 配置与 Secret
│ │ └── Extension 安装存储与全局 operation
│ ├── Workspace scope 配置
│ └── Skill 安装存储
└── WorkspaceRegistry
├── WorkspaceRuntime(A)
│ ├── WorkspaceRuntimeCoordinator capability/operation 协调者
│ ├── Workspace config controller 工作区覆盖与其 operation
│ ├── WorkspaceService 本地文件与配置边界
│ └── Bridge ACP 通信驱动
│ └── Workspace ACP Runtime 0..1 个子进程
│ └── Session 0..N 个逻辑 Session
└── WorkspaceRuntime(B)
└── ... 与 A 完全隔离
物理上仍复用现有 qwen --acp 子进程和 ACP Channel。迁移改变的是所有权:
它们是 Workspace ACP Runtime 的实现细节,不是 Session 级进程。
daemon 持久化控制面回答“用户配置了什么”,并且不依赖 ACP 或 Session:
配置提交生成 desired state。它不直接宣称某个 Workspace Runtime 已应用该状态。
全局和工作区 config owner 必须分开:
/workspace/config/extensions 虽保留了 singular/primary 风格的路径名,逻辑上仍
指向全局 config owner,不代表 Extension Store 或 operation 属于 primary runtime;/workspaces/:workspace/config/extensions 必须拒绝安装、更新、卸载和 User scope
enable/disable,也不能查询或响应其他 controller 的 operation/interaction。每个 WorkspaceRuntime 持有一个 Coordinator。Foundation Coordinator 只负责
lifecycle snapshot 投影、ensure/status admission 和 drain/dispose;物理事实仍全部
来自 Bridge。
Target Coordinator 将扩展为以下状态的唯一可写所有者:
调用方不得直接根据 Session 数量或某个模块缓存推断 capability 状态。
Bridge 是 WorkspaceRuntime 内由 Coordinator 和兼容 adapter 调用的通信驱动,负责:
Bridge 不负责:
AcpSessionBridge 的 Channel、epoch、物理 lease 和 idle timer 是同一个
WorkspaceRuntime 的底层生命周期事实,不是第二个 Session runtime。Foundation
Coordinator 只读取 lifecycle snapshot;Target Coordinator 再从这些事实构造
capability 投影。两者都不复制一套相互竞争的 Channel 状态机。Bridge 只能在所有物理
lease 均为空时回收,不能仅依据 Session 数量结束 Channel。
ACP Runtime 回答“这个工作区在当前 epoch 实际可以使用什么”,包括:
daemon 不复制这些运行时逻辑,只负责协调和观察。
Session 只拥有对话和执行状态:历史、上下文、Turn、模型、Mode、审批和会话临时 状态。Session 是 Workspace ACP Runtime 的逻辑子对象,不是 Extensions、MCP、 Skills 或 Tools 管理能力的初始化入口。
User scope 是 daemon 进程级的持久化 desired state,不属于 primary runtime。
primary workspace 只是 singular /workspace/runtime/... 兼容路由所选中的普通
WorkspaceRuntime;它不能因此成为全局配置或全局 operation 的 owner。
规则如下:
/workspace/config/... 这一兼容命名,但其 owner 不能与 primary runtime 的
Coordinator/controller 合并。/workspaces/:workspace/config/... 只允许 Workspace scope;不得借该路由修改
User scope。deferred,在下次
ensure 或创建 Session 时应用。/workspace(s)/.../mcp 控制接口仅为
兼容保留原有持久化行为;新 /runtime 路由不提供 enable/disable。一个工作区的 effective desired state 由 User scope、Workspace scope 和已启用 Extension 的贡献合并而成;合并规则属于 Config/ACP,不在 Coordinator 中复制。
operation 的查询与 interaction 回复遵循创建者所有权:全局操作只从全局 controller
查询,工作区操作只从相应 qualified controller 查询。相同 operationId 即使出现在
另一路由的请求里也必须返回 not found,不能通过 daemon 级 pending map 绕过 owner。
cold -> starting -> active -> idle
| ^ |
| └--------┘ 新 lease
└-> cold + lastError 启动失败
active/idle -> stopping -> cold workspace removal / daemon shutdown / explicit restart
stopping -> starting/active 新 epoch(admission 开放时,旧 Channel 退出前)
idle -> stopping -> cold immediate or configured idle timeout
active/idle -> cold child crash
cold:没有 ACP Channel,也没有正在进行的启动;starting:Channel 正在创建或 handshake 尚未完成。它是物理 runtime 生命周期
状态,不等于某个 capability 的 starting;active:Channel 已 live,且至少有一个 session、workspace-control、discovery、
auth、spawn/restore 或其他物理 work lease;idle:Channel 已 live,且没有任何物理 work lease;runtime 继续保留进程和已加载
资源,后续工作复用同一 epoch;stopping:没有可复用的 live Channel,但旧 Channel 仍在异步退出。除 draining、
removal 或 daemon shutdown 已关闭 admission 外,并发新工作可以在旧 Channel
完全退出前启动新 epoch;aliveChannels 同时跟踪两者以保证最终清理。注册 WorkspaceRuntime 本身不启动 ACP child。Foundation 暂时保留 production
startup preheat,因此受信任的 primary 可在 daemon listen 后被兼容策略启动;不受
信任的 primary 和所有 secondary 不会被该策略启动。除此之外,Primary 与 secondary
都只由显式 runtime command(包括 ensure)或 Session create/load/resume 从
cold 启动。后续移除 startup preheat 后,两者完全一致。
若 Channel 已 live,Coordinator 中存在 capability reconcile 并不能单独把顶层状态
标为 starting;实际 RPC 持有 workspace-control/discovery/auth lease 时顶层为
active,lease 释放后为 idle。Capability 自己仍可保持 starting 或 error,
但不会改变顶层五态。只要存在新的可复用 live Channel,顶层就按该 Channel 的
active/idle 投影;旧 epoch 的 Channel 可同时处于退出过程。
Foundation Bridge 用 session 集合、spawn/restore 计数、workspace-control 计数、
MCP discovery 标记和 server-name 级 MCP auth 集合表示物理 work。已接入的
status、Catalog、Extension refresh、Skills refresh、MCP discovery/auth、普通 runtime
mutation 以及 Session create/load/resume/close 都在对应物理工作期间持 lease。
物理 startup 本身也受启动 lease 和 deadline 保护。当前 ensure 只覆盖
preheat/initialize,并在成功后登记 keepalive;它尚不串联 capability 阶段。
Foundation 在 OAuth 返回 pending 后保留 owning Channel 的 auth lease。明确观察到 同一 Channel 上的 server 已变为 non-pending 时释放;Catalog 中缺少 server 不是完成 证据。pending 状态的固定安全期限到达时,Bridge 先对 owning Channel 做最后一次状态确认; 仍无法证明完成时将 owning Channel 标记为待退役。无 Session 时立即终止;有 Session 时允许现有会话继续使用,并在最后一个 Session 排空后终止 Channel,以进程退出完成 safe drain。deadline 是故障恢复上限,不是普通 idle 回收。
Target Coordinator 将通过 Bridge 的外层 runtime-control lease 包住一次完整 capability runtime command。其中的 Catalog、Extension refresh、Skills refresh 和 普通 runtime mutation 仍可嵌套使用更具体的计数,但不能在阶段之间释放最后一个物理 lease。Coordinator 不建立第二套用于物理生命周期的可写“逻辑 lease”。这些计数和 Map 不对调用方开放。
Target Coordinator 还需要仅用于 workspace removal admission 的 daemon-local
management operation 计数。它覆盖尚未进入 Bridge 的配置持久化和后台提交,但不参与
顶层 active/idle 投影或 ACP idle 回收判断。
约束:
最后一个物理 work lease 释放后,Bridge 按 channelIdleTimeoutMs 立即或延迟回收 ACP
child。进程所有权仍属于 WorkspaceRuntime,回收条件由整个 workspace 的物理 work
lease 决定,而不是只看 Session 数量:
lastActivityAt 保持既有 Session 观测语义,只由 Session spawn/restore 和
prompt 活动更新;workspace runtime 请求通过 lease/keepalive 控制回收,不伪装成
Session activity;0 时,普通 runtime work lease 排空后立即回收;裸 preheat
自身结算不启动立即回收器,而是保留到首次使用;成功的显式 ensure 会把 workspace 级保活窗口从本次成功时刻续期至少 10 分钟;
并发调用取最长窗口,窗口内再次调用会再次续期。通用部署仍可通过显式正数
channelIdleTimeoutMs 配置更长的 idle 窗口。两者都不计为 active work,workspace
removal 和 daemon shutdown 可以提前结束 runtime。
Session 数量不是回收条件,只是 lease 集合的一部分。
Foundation 保留 production 默认预热受信任 primary 的既有策略,避免尚未迁移到
ensure 的 SDK/API 调用方承担额外首次冷启动延迟。该预热仍通过 primary
WorkspaceRuntime/Bridge 执行,不改变 runtime ownership;不受信任的 primary 不会
启动 ACP。测试或嵌入方可以通过 preheatBridge: false 显式关闭。
startup preheat 是迁移期策略,不是目标架构的启动入口。完成调用方迁移和首请求延迟 验证后,再由独立变更移除默认预热;届时 runtime 只由显式 workspace intent 或 Session 需求启动。Bridge 已进入 shutdown 后,legacy preheat 明确失败而不是静默成功,使 调用方不会把一个无法再启动的 runtime 误判为 ready。
当前组合实现的 workspace removal activity 已包含 Session 之外的 Bridge 物理 work、
在途 ensure 和已接纳的 workspace-scoped management operation。非 force 移除遇到
这些活动项返回 workspace_busy。进入 draining 后,Registry 阻止新的路由解析,
Coordinator 关闭新的 ensure admission;已经解析但尚未开始物理工作的请求以
workspace_draining 失败。移除回滚时一并恢复 admission,提交后的强制清理才终止
现有 work。
Target 还必须把后台 capability 收敛和未终结 operation 纳入 activity。draining 期间收到的 User/global MCP 或 Skills 配置失效要保留为待 reconcile 状态;回滚后立即 重放,且重放成功前 ensure 不得把旧 Catalog 标成 ready。
本节的 Bridge epoch 属于 Foundation;capability status 和 Catalog 规则属于 Target。
每次新的 ACP 子进程/Channel 成为当前 runtime 时,Bridge 分配单调递增的
runtimeEpoch,Coordinator 将它绑定到 capability 状态。所有 live 状态和缓存必须
携带产生它的 epoch。
type CapabilityState = 'not_started' | 'starting' | 'ready' | 'stale' | 'error';
interface WorkspaceCapabilityStatus {
state: CapabilityState;
runtimeEpoch?: number;
error?: { code: string; message: string };
}
规则:
ready 必须来自当前 epoch 完成的 ACP 响应。ready 立即变为 stale。completed 不得覆盖新 epoch 的 not_started 或空结果。workspaceId + capability + runtimeEpoch;跨 epoch 只能作为
明确标记的 stale 展示数据。runtimeLive 只表示当前 Channel 是否存在,不替代 capability 状态。ensure 或领域 runtime
command。source: 'config' 或本地 fallback 可以提供控制面信息,但不能把 runtime
capability 标记为 ready。Runtime Catalog 与 Coordinator status 是两个互相校验、不能互相替代的投影:
initialized;live 或 cached
快照携带产生它的 runtimeEpoch。MCP/Skills 还显式携带 source,其他 Catalog
的来源由 initialized/epoch 和 Coordinator status 判定;state、runtimeEpoch 和错误;仅 Extension
capability 额外携带 desiredGeneration、appliedGeneration 和 appliedEpoch;appliedEpoch 是 Coordinator 对 Extension generation 回执的投影,不是 Catalog
自己的 epoch,也不得由前端用“当前 runtime epoch”猜测;ready、Catalog 已 initialized 且
Catalog runtimeEpoch 与当前 runtime 相等时,才把 Catalog 当作 live;Extensions
还要求 desiredGeneration === appliedGeneration 且 appliedEpoch 等于当前 epoch。本节全部为 Target。
只有具有原子版本化 Store 的 Extension 使用对外可见的 desired/applied generation。 运行时只有在当前 epoch 明确回执加载了该 generation 后,Coordinator 才能推进 applied generation。
interface GeneratedCapabilityStatus extends WorkspaceCapabilityStatus {
desiredGeneration: number;
appliedGeneration?: number;
appliedEpoch?: number;
}
约束:
generation + runtimeEpoch + reconciliationRevision;回执必须携带它实际加载的
generation,不能在 refresh 完成后重新读取
store 最新 generation 并猜测已应用值。appliedGeneration 与 runtime snapshot 在同一次成功响应中更新。ready 要求 appliedGeneration === desiredGeneration、appliedEpoch 等于当前
epoch,并且存在该 epoch 的实际快照。ready 立即变为 starting(正在 reconcile)或
stale(尚未开始);成功后由 Coordinator 一次性更新 generation 和状态。MCP/Skills 不使用伪造的 desired/applied generation。它们由各 WorkspaceRuntime Coordinator 维护不对外持久化的单调 capability revision:
not_started/stale 并返回 deferred,live runtime 排队 reconcile;revision + runtimeEpoch,只有两者仍为当前值时才可以写
ready/error;较新的 mutation 会使旧尝试失效;ensure 是 SDK/UI 唯一的通用 Workspace Runtime 启动命令:
POST /workspaces/:workspace/runtime/ensure
{}
primary workspace 使用等价的 POST /workspace/runtime/ensure。两个入口都拒绝非空
body;调用方不选择 capability,也不传 timeout、keepalive 或初始化顺序。
当前 Coordinator:
capabilities.mcp。ensure 成功证明 ACP Channel 已完成 initialize,并启动或继续当前 MCP revision 的
准备。只有 capabilities.mcp.state === 'ready'、epoch 与 lifecycle 一致且 MCP status
来自 live runtime 时,调用方才可读取 MCP Catalog。若同一物理启动正在进行,并发
ensure 复用 Bridge 的启动 Promise;每个成功调用都从自己的成功时刻续期
keepalive。若启动卡住,Bridge 的绝对启动 deadline 会中止并清理该次启动,后续显式
ensure 可以发起新的尝试。
服务端观察预算为 60 秒。ACP Channel 尚未就绪时,预算耗尽以可重试的
runtime_still_starting 错误结束;Channel 已就绪但 MCP 尚未完成时,返回 capability
starting,后台继续有界收敛。GET /runtime/status 只观察 lifecycle 和 capability,
不启动或重试 runtime。
后续 Coordinator 将在同一次 workspace runtime command 中固定准备标准能力
extensions -> (mcp, skills, tools):
MCP 已按 revision + epoch 实现上述收敛;Extensions、Skills 与 Tools 尚未迁移。
Target 中 Coordinator 先 prepare Extensions,再并行处理其派生能力;同一 capability
的并发工作合并。HTTP 观察预算耗尽可以先返回 capability starting,后台收敛受另一
个固定 deadline 约束,客户端通过 /runtime/status 观察终态。此语义在 capability
Coordinator 落地前不得由 SDK/UI 假设;当前只适用于已接入的 MCP。
按 capability 的 prepare 只是 Coordinator 的内部实现,不暴露 HTTP 或 SDK 接口。 新增 capability 时只修改 Coordinator 的标准能力集合和初始化逻辑。
以下为 Target:
type McpOperationState =
| 'running'
| 'waiting_for_input'
| 'succeeded'
| 'failed';
operation 用于 Extension 安装/更新、MCP OAuth 等有副作用、需要交互或不能靠重复 ensure 表达的工作。一个 operation record 只有一个可写所有者:
waiting_for_input 不是终态,仍持有带最大期限的 lease。operation 进入终态后保留
有限时间供 SDK/UI 查询。
Extension controller 保留自己的 queued/running/waiting_for_input/succeeded/ succeeded_with_warnings/failed 状态和 preparing/committing/reconciling phase;MCP
runtime operation 使用上面的较小状态集。当前协议不暴露一个虚假的 timed_out
终态:若 deadline 后仍不能安全取消,operation 继续保持非终态;安全 drain 后以
failed 和结构化 timeout error 结束。
Foundation 已实现 ACP 物理启动的绝对 deadline。它覆盖 Channel factory 和 initialize;超时会通过 AbortSignal 请求取消、终止迟到创建的 child,并清除启动 Promise,使后续 ensure/Session 可以重试。ensure 的 60 秒 HTTP 观察预算不延长这个 物理 deadline,SDK 使用 62 秒客户端预算为服务端返回预留时间。
Target 要求每次 capability ensure 或 operation 在入口创建绝对 deadline。每个
阶段只使用剩余预算,不得让 preheat、discovery、refresh 或每次 UI poll 各自重新
获得一份完整 timeout。Target ensure 具有调用方观察 deadline 和一次性有界后台收敛
deadline;前者到达可先返回 starting,后者不随 poll 重置。MCP auth 从首次 Bridge
调用到状态 observer 共用同一个 deadlineAt。
HTTP/SDK 请求超时与 operation deadline 是不同概念:
starting,其他 Target capability 后续采用相同语义;命令型 operation
返回 operationId;若底层有取消契约,deadline 到达时先请求取消。只有底层任务已经停止、完成必要 清理,或已被安全地从 ACP 生命周期中分离后,才可以进入 timeout 终态并释放 lease。 当前 MCP OAuth 没有取消契约,因此 observer deadline 到达不能释放 auth lease 或 认证全局 lane;具体 safe-drain 语义见第 12 节。
以下为 Target:
配置接口先提交 durable state,再在同一个扁平 domain result 或 operation result 中
单独表达 runtime activation。当前 wire contract 不包一层虚构的 commit 对象:
interface DurableMutationResult {
// name/scope/config/changed 等领域字段;Extensions 可携带 generation
// applied — runtime activation completed for all affected WorkspaceRuntimes.
// deferred — no live runtime; durable commit persisted, activation on next ensure.
// reconciling — durable commit persisted, live runtime reconcile in progress (operationId provided).
// partial — durable commit persisted, activation succeeded for some WorkspaceRuntimes but failed for others.
activation: 'applied' | 'deferred' | 'reconciling' | 'partial';
operationId?: string;
warnings?: Array<{ workspaceCwd: string; error: string }>;
}
同步 MCP/Skills mutation 以 HTTP 成功和领域字段表示 durable result;Extensions
mutation 先返回 operationId,operation 的 committing phase 成功后,其 result 再
携带 activation/warnings。提交完成后,即使 activation 超时或失败,也必须返回
“配置已保存”;客户端超时不能把已经落盘的变更显示成保存失败。
本节为 Target,但约束来源于当前 ACP OAuth provider 缺少可靠取消契约这一既有 事实。
OAuth 是 workspace-scoped operation,但 callback listener/port 是 daemon process-global 资源。锁和路由必须匹配真实资源作用域。
当前 ACP OAuth provider 没有取消契约,并使用可能冲突的 process-global callback 资源。因此后续 operation 层采用保守但可证明安全的模型:
workspaceCwd + serverName + operationId + runtimeEpoch;同一 workspace/server
不能并发认证。running/waiting_for_input 的 auth 时,其他工作区或 server 的认证请求明确失败,
而不是争用 callback listener。deadlineAt;初始 authenticate RPC 和
后续 observer 使用同一个 deadline,不能各自获得一段新的十分钟。pending 时,以 operationId 记录物理 auth lease,并把实际
runtimeEpoch 返回 Coordinator。waiting_for_input 期间最后一个 Session 关闭不
得回收 Channel。authenticationState: pending,operation 保持 waiting_for_input,物理 auth
lease、per-target lane 和 process-global lane 都不得释放。finally 在移除 callback listener 和 pending provider 记录后,
发送带 operationId + serverName 的 completion notification。该通知是 Bridge
释放对应物理 auth lease 的直接排空信号;同 epoch Catalog 中仍存在且明确为
non-pending 的 server 可以作为兼容佐证。failed/mcp_authentication_timeout(或 runtime unavailable)。未来只有在 ACP 提供可靠 cancellation,或 callback broker 能按不可伪造 token 完整 隔离多个认证时,才可以放宽全局串行化;这不是当前架构成立的前提。
/workspace/config/... 全局/User 配置 owner;部分领域兼容 primary 命名
/workspace/runtime/... primary WorkspaceRuntime
/workspaces/:workspace/config/... 指定工作区的 Workspace scope 配置
/workspaces/:workspace/runtime/... 指定 WorkspaceRuntime 的状态与命令
/sessions/... Session 生命周期和执行
以上是 Target 路由分类。Foundation 新增的 runtime 路由只有 primary/qualified
ensure 与 status;现有 MCP、Skills、Extensions 等领域路由仍按 legacy 契约运行。
ensure 使用普通 daemon mutation admission;Foundation 通过 daemon capability workspace_runtime 宣告上述 ensure/status
契约。只有当前所有可路由 WorkspaceRuntime 的 Bridge 都提供 lifecycle snapshot 时
才发布该 capability;不支持的注入式或旧 Bridge 调用 runtime 路由时返回
501 workspace_runtime_not_supported,服务端不会根据 isChannelLive 合成 epoch
或状态。这是迁移兼容边界,不是第二套 lifecycle 实现。
Extensions 的边界尤其需要明确:
GET /.../config/extensions 读取 durable inventory;install/check/update/uninstall 和
User scope enable/disable 只走全局 config owner;qualified config 路由只写该
workspace override;GET /.../runtime/extensions 读取带 epoch 的实际 Catalog,GET 不启动 ACP;POST /.../runtime/ensure 是管理区域唯一的通用 Runtime 激活入口;页面不选择
Extension 或其他 capability;/.../config/extensions/refresh 不得直接调用 Bridge 或启动 runtime。旧
/workspace/extensions/refresh 若暂时保留,只是 legacy Session-centric adapter,
新 SDK/UI 不调用它;该 legacy-primary adapter 的 operation namespace 与全局 config
owner、qualified workspace owner 都必须隔离。MCP 与 Skills 遵循同一分层:
GET/PUT/DELETE /.../config/mcp/servers 和
POST /.../config/mcp/:server/{enable,disable} 只读写 durable desired state;User
scope 只允许 singular/global owner,qualified 路由只允许 Workspace scope;mcp.excluded 的 owner,尤其不能把 secondary workspace
的覆盖写进 primary workspace;GET /.../runtime/mcp、runtime reload/restart、approve/authenticate/clear-auth 和
runtime operation 属于对应 WorkspaceRuntime。runtime 路由不提供持久化
enable/disable;GET /.../config/skills 以及 config install/delete/enable 只使用
daemon-local inventory 和设置,不查询 live ACP Catalog。global scope 只允许
singular/global owner,qualified 路由只允许 Workspace scope;GET /.../runtime/skills 返回当前 epoch 的实际 Skills,ensure 负责启动和准备完整
Runtime,
包括 Extension 注入内容。只存在于 runtime Catalog、未出现在 config inventory 的
Extension Skill 是只读项;其来源 Extension 的激活通过 Extension config owner
管理,Skills 页面不能对它执行 enable/delete。Workspace config/runtime API 是 daemon REST 控制面,不是 ACP Session method。
WorkspaceDaemonClient 必须显式使用 REST transport,除非 ACP HTTP/WS route table
完整实现同名路由并有等价测试。不能依赖默认 transport 后再遇到 404。
Foundation SDK 已提供:
ensureWorkspaceRuntime() / workspaceRuntimeStatus();WorkspaceDaemonClient.ensureRuntime() / runtimeStatus();Target SDK 还应直接提供:
getOperation/waitForOperation(命令型长任务)、runtime
status polling(幂等 ensure/reconcile);SDK 方法必须按 owner 收窄,而不是依赖服务端 400/404 纠正错误调用:
DaemonClient 承载全局 Extension install/check/update/uninstall、User scope MCP 和
global Skill mutation;WorkspaceDaemonClient 只承载 qualified Workspace config 与对应 runtime API;Target 管理区域在进入目标 workspace 或切换 workspace 时调用一次
ensureWorkspaceRuntime();当前 epoch 已完整 ready 时 Coordinator 直接返回,不重复
初始化。Extensions、MCP、Skills 页面只读取各自的 config inventory、runtime status
和 runtime Catalog。
页面不存在 capability-selecting prepare,也不调用
refreshWorkspaceConfigExtensions() 作为新架构 runtime command。
UI 不直接调用 Bridge/ACP 兼容接口。
WebShell 选择 Session 后,三个管理页必须把该 Session 的规范化 workspaceCwd
显式传给 workspace hooks;页面切换工作区时重建本地页面状态。Provider 的 primary
workspace 只作为没有显式 workspace owner 的兼容默认值,不能覆盖活动 Session 的
workspace,也不能让 qualified action 回落到 primary runtime。
Web Shell 只有在 capability readiness 和 Catalog freshness 契约可用后才切换到 runtime API。迁移至少满足:
runtimeEpoch 和 capability revision;以下为 Target:
GET /runtime/status 是 capability 收敛的权威观察接口;
GET /runtime/operations 返回当前 WorkspaceRuntime 中仍为
running/waiting_for_input 的命令型任务,GET /runtime/operations/:operationId 返回指定任务的权威状态。active collection 和
by-id 状态都保留 OAuth 的 deadlineAt 与 authUrl,因此页面刷新或重新进入时可以
恢复观察,而不重复启动认证。Runtime capability 收敛不通过 Session EventBus 广播;
SDK/UI 只通过 operation/status polling 保证最终收敛,也不会为了观察状态创建隐藏
Session。
各 capability 的单一所有权和失效条件如下:
| Capability | Desired owner | 顺序 token | Runtime initializer | Status/cache owner | 主要失效条件 | 下游页面 |
|---|---|---|---|---|---|---|
| Extensions | daemon Extension Store | Store generation + reconcile revision | 当前 epoch 的 ACP Config refresh | Coordinator | Extension generation、revision、epoch | Extensions、MCP、Skills |
| MCP | User/Workspace settings、Secret、Extension contribution | Coordinator capability revision | 当前 epoch 的 ACP MCP discovery | Coordinator | MCP revision、Extension generation、epoch、auth | MCP、Agent Tool selector |
| Skills | Skill store、settings、Extension contribution | Coordinator capability revision | 当前 epoch 的 ACP Config/Skill discovery | Coordinator | Skills revision、Extension generation、epoch | Skills、Agent editor |
| Tools | settings、Extension contribution | epoch + Extension-derived revision | 当前 epoch 的 ACP ToolRegistry | Coordinator | Extension generation、epoch | Agent editor、Tool selector |
模块可以保留自己的原始结果缓存,但它们是 Coordinator 状态的输入,不是第二个 可写状态源。
三个页面共享同一个管理区域 Runtime 入口,但 config inventory 与 runtime Catalog 始终是两份明确的数据:
进入 workspace 的管理区域 -> ensureRuntime()(不传 capability,同 epoch ready 时为 no-op)
-> Coordinator 确保一个 ACP Runtime 并准备标准能力
-> 各页面读取 config desired state / runtime status / 自己的 Catalog
-> 轮询 operation/status 等待终态
-> 页面切换复用同一 runtime/epoch;手动刷新仍调用统一 ensure 或领域命令
任何页面都不得创建隐藏 Session、选择已有 Session、遍历 Session 进行刷新,或自己 组合 preheat/initialize/按 capability 启动/reload/poll 状态机。普通 config/status/ Catalog GET 始终保持只读,不以“页面加载”为理由启动 ACP。
页面需要同时展示:
验收条件:
deferred。preparing/committing/reconciling/terminal,不自行刷新 Session。ensureRuntime()。手动刷新调用同一无参数入口后重读 status/Catalog,不调用
config refresh,也不传 extensions capability。isActive/capabilities/details;Catalog 未初始化、epoch 不匹配或 applied epoch/
generation 未收敛时,保留可编辑的 config inventory 并显示 pending/stale。页面需要区分:
验收条件:
ensureRuntime() 在当前 epoch 完成 MCP discovery 后才把 MCP 标为 ready;请求预算耗尽后
后台继续,页面通过 operation/status polling 观察终态,而不是只 reload 一次。operationId、服务端 deadlineAt 和 authUrl;不得重启认证、延长
deadline 或把观察失败报告成 daemon 已取消任务。页面需要区分:
验收条件:
ensureRuntime(),Skills 页面只读
当前 epoch Catalog,不创建 Session。0 立即回收 ACP child;裸 preheat
保留到首次使用;配置正值或 active keepalive 时,在较长窗口内复用同一 runtime/epoch;ensure 成功后至少保活十分钟,使紧随其后的状态与 Catalog 读取能够观察到
已初始化的 runtime;再次 ensure 会续期该窗口;force workspace removal 会把零 Session 的 ensure、Catalog、reconcile、OAuth
等 runtime work 计为 busy;进入 draining 后的新管理命令明确失败;ensure 与 Session
创建使用相同的普通 daemon admission:无 token 的 loopback 开发模式可调用;
配置 token、--require-auth 或非 loopback 部署仍要求 bearer auth。/workspace/extensions/refresh 路由 deprecated;Foundation 的自动化验证覆盖:
preheatBridge: false 的 opt-out;此外已通过相关单元测试、lint、build 和 typecheck。仍建议在合并前补充或人工执行 真实 ACP child E2E,验证进程级时序而不只验证 mock 契约。
后续实现不能只验证单个路由成功,至少覆盖:
ensure 遵循普通 daemon auth admission;daemon-local
config GET 仍可读,global config owner 不受 primary trust 影响。0 时,普通 runtime work 排空后立即回收;
裸 preheat 自身保留到首次使用;显式正值或 active keepalive 取较长窗口。兼容 preheat 保留旧资源语义;显式 ensure 额外登记
可续期的 10 分钟 workspace 保活窗口。Capability RPC 与最终状态投影不跨 epoch,
物理回收只发生在外层 runtime-control lease 释放后。