Orca 协同架构与执行单元规划

September 11, 2026 · View on GitHub

这是 Orca 的唯一权威文档,覆盖当前实现、设计约束与未来规划。

状态:权威(authoritative)。 约束力:本文「协同运行时行为契约」「坑点与不变量」两节是 Orca 协同代码的约束源,治理范围包括 apps/desktopmaker-ipc/orca* 服务与 mcp-integrations codex MCP 集成、packages/lizi-mcpsorca server 与 worker 控制工具、packages/orca-workflowpackages/maker-core 的 codex MCP context。改这些模块前先读本文;本文与代码实现冲突时以代码为准(遵循「以源码实现为准」原则),但必须同步修正本文。

目录

Part 1 · 当前架构

目标与边界

Orca 是 Cindy Desktop 内的多 agent 协同能力:一个 Lead session 负责拆任务、派活、验收与汇总,多个 Worker session 负责并行或串行执行。Worker 是完整会话,不是一次性 subagent;它有自己的模型、effort、工具调用流、上下文与可见历史。

当前已经实现的能力:

  • 多 worker:同一个 active team 下可创建多个 worker,支持 role、label、focused worker 切换、soft/hard limit 与归档。
  • Split view:Lead 与 focused Worker 共用 OrcaSplitView pane 外壳,宽屏为左右 split,doc rail 为 Lead/Worker toggle。见 apps/desktop/src/renderer/features/cc-agent/OrcaSplitView.tsxOrcaSplitViewOrcaPaneShell
  • 本地 Claude Code、Codex 与 Pi 的普通 Lead 无论是项目还是对话都可开启协同,本地 Worker 也支持这三类 agent;SSH 远端会话的 Claude Code、Codex 与 Pi 均可作 Lead 或 Worker(远端 agent 经 SSH remote-forward 连接本机 MCP bridge:Codex 走 daemon config 注入,Claude Code 走 per-query HTTP 注入,Pi 走内部 MCP bridge URL 改写);device-link 被控端的项目与对话同样可作 Lead(Lead / Worker / team 的真身都在被控端,控制端只是镜像)。renderer 的入口判定收敛在 apps/desktop/src/renderer/features/cc-agent/collabEntryPolicy.tsresolveCollabEntryPolicy,新建草稿(NewMakerDraftRoute)与会话视图(CCAgentSessionViewallowCollabToggle)共用同一份:普通 Lead 都显示入口,只排除不能嵌套协同的 Orca Worker 子会话。项目按项目级策略查询;Main 的 resolveLocalCollabPolicyWorkingDir 同时服务入口状态查询与最终授权,只有 workspace kind 为 dialogue 且 Main 确认目录位于 app 托管 dialogue root 时才只查用户级/全局级策略(即使 cwd 内出现 .cindy/plugins.json),显式绑定真实目录的对话仍按该目录的项目级策略查询,不能靠自报 workspaceKind 绕过项目禁用。
  • Codex Lead 使用全局注册的 cindy_orca,调用时通过 context 恢复身份并在 handler 内拒绝越权。远端 Codex 同样走 params._meta.threadId 路由——remote thread 与本地 thread 一样注册进 CodexMcpThreadContextStorepackages/maker-core/src/agents/codex/index.tsregisterCodexMcpContext 不再跳过 remoteHostId)。远端 cc 没有 threadId,身份走持久 bearer token + URL ?session=<id> 路由(codexHttpBridge.registerSessionCtx),审批归属快照在 remoteCcQueryFactory 注入后按 startParams.mcpServers 最终清单定稿。
  • SSH 远端 Codex 的 MCP 桥接:本机 codexHttpBridge 在原有 per-run 主 token 之外接受一个 persistent bearer token(safeStorage);remote-ssh/codex-remote-mcp.ts 在 session start/resume 前置完成 per-host 固定端口 remote-forward(RemoteHost.openRemoteForward,重连自动 rebind)、远端 $CODEX_HOME/config.tomlmcp_servers 管理段漂移检测(行级 marker + 剥离用户同名 table)与 daemon 幂等 bootstrap(token 只经 stdin 的 KEY=value 块注入,不进 argv);config 漂移需要重启 daemon 时若同 host 有 live turn 则本次降级(留待下次 ensure)。worker 创建经 OrcaLeadSessionSnapshot.remoteHostId 继承在同一台远端主机 spawn,创建前走 ensureRemoteReadyForSessionStart(SSH 重连 / agent 安装 / MCP 注入);lazy resume 与 Orca worker 唤醒路径同样先 ensure 再 bootstrap。
  • PR #107 提供 side_chat 的底层 fork 数据动作:user/assistant 消息都能 fork,assistant 按 turn 粒度复制,Claude 用 uuid 锚点,Codex 用 ThreadFork + ThreadRollback。当前作为普通 session 跳转;未来要登记为 side activity 并挂入统一 pane。

当前边界:

  • Orca Team 仍严格绑定单个 Lead session,不支持跨 Lead 共享 worker。
  • Worker focused 切换是“单 worker pane 替换”,不是多 worker 并排。
  • Worker archive 后 UI 内没有 unarchive 入口;DB 记录保留,用户需要新建 worker 继续。
  • workflow_run / CC Workflow 编排还未纳入当前实现。
  • side_chat 尚未登记为 side activity 对象,也未挂进 pane;PR #107 只是 fork 数据动作。
  • device-link 协同的 Lead / Worker / team 全部在被控端进程内编排,控制端只按 session 来源经隧道路由(makerTransportmakerApiFor / orcaWorkflowsFor / subscribeOrcaWorkerChanged,channel 见 packages/device-link/src/allowlist.ts 的 Orca 段)。collab 开关同样查被控端(maker:plugins:get-statepluginEnableStateFor):项目读取被控端项目级策略,对话读取被控端用户级/全局级策略。控制端本机状态不能代表被控端真相。老被控端没有该 channel 时回 CHANNEL_NOT_ALLOWED,控制端 fail-closed 置灰入口并提示设备版本过旧,而不是放行到 enableOrca 才撞错。
  • SSH 远端协同支持 Claude Code、Codex 与 Pi Lead / Worker;Worker 继承 Lead 的 remoteHostId 与远端工作目录,在同一台远端主机执行,启动前复用远端就绪检查。cc 远端经 cc-remote-mcp.tscindy_orca / orca_worker_bridge 以 http 形态追加进 startParams.mcpServers(persistent token + ?session= 路由,白名单仅此两个 server)。远端 worker 手动 send_to_lead 依赖 daemon 侧 orca_worker_bridge 经同一 bridge 可达;auto-bridge 回报不依赖 worker 侧 MCP,天然可用。Claude Code / Codex 共享 userData 多实例连同一远端 host 时,只有先建立 SSH 转发的实例能持有该 host 的 MCP bridge 端口,其余实例按“远端无 MCP”降级(与历史行为一致)。远端会话的项目级 collab 开关不查本机 fs;assertCollabProjectEnabled 对 remote 只查用户级/全局级开关,远端项目级配置机制是 follow-up。

Pi 的远端 Lead 沿用统一的 Lead 身份与 prompt 装配;内部 MCP(含 cindy_orcaorca_worker_bridge)经独立 SSH remote-forward 连接本机 bridge,不复用 Codex 的 per-host 转发槽位。实际可用性仍受协同开关与桥接状态约束。核对入口:

核心概念与数据模型

概念当前含义载体
Lead session主线会话,负责开 team、创建 worker、派活与验收sessions.orca_role = 'lead'
Worker session被 Lead 派出的完整 side session,有独立 agent/model/contextsessions.orca_role = 'worker' + orca_workers.session_id
Team一个 Lead 当前 active 的协同上下文orca_teams
Worker recordworker 在 team 内的 role、label、status、focused 等元数据orca_workers
Fork relation普通 session fork 来源关系,为 side_chat 底座sessions.parent_session_id + forked_at_message_id

orcaRole 和 fork parent 是两个字段,不能混用。schema 明确写着 orca_role 用于 Orca split-session role,parent_session_id 保留给 fork/session-branch,见 apps/desktop/src/main/localDb/schema.tssessions schema。fork 关系不能承载 Orca 派单语义,这是后续把 side_chat 纳入 side activity 的数据前提。

orca_teams 保证同一个 Lead 同时最多一个 active team:唯一约束 uniq_active_team_per_lead 只约束 status = 'active'。约束:active 唯一性必须用 partial unique,不能用全表 unique,否则非 active team 占住索引会让同一个 Lead 无法重新开启协同。

orca_workers 保存 team 内 worker:team_idsession_idstatus(idle/running/done/error)labelrolefocusedidle_since。同一个 team 只能有一个 focused worker,靠 partial unique uniq_orca_workers_focused_per_team 保证,见 apps/desktop/src/main/localDb/schema.tsorcaWorkers schema。

fork/session-branch 的最小 provenance 当前只有两列:

  • parent_session_id:源 session self-FK,源删掉时 set null。
  • forked_at_message_id:来源 message 的 client id,无 FK。PR #107 后 assistant fork 也会写 assistant client id;schema 注释需要按实际语义同步,见 apps/desktop/src/main/localDb/schema.tssessions.forkedAtMessageId

PR #101 后的服务边界

PR #101 之后,Orca 的 main 侧业务由独立 service 承接,register.ts 只负责依赖装配、IPC/MCP wiring 和 host adapter。服务边界如下:

服务文件责任
OrcaLifecycleServiceapps/desktop/src/main/maker-ipc/orcaLifecycleService.tsstart_team、开启协同 enableTeam、创建 team、读取或更新 Worker 创建权限偏好、设置 Lead orcaRole、首个 worker 创建补偿
OrcaWorkerCreationServiceapps/desktop/src/main/maker-ipc/orcaWorkerCreationService.ts既有 team 下创建 worker,统一 role/label/model/effort/fast 校验与默认值;新 Worker 使用 workerCreationPrefs 的权限偏好,不继承 Lead 当前模式;创建 worker session 并写 orca_workers
OrcaTeamServiceapps/desktop/src/main/maker-ipc/orcaTeamService.ts给既有 worker 派活、resume、idle、archive、terminal turn 处理与 auto-bridge
OrcaInterAgentDispatcherapps/desktop/src/main/maker-ipc/orcaInterAgentDispatcher.tsLead/Worker 之间的消息直发或排队、accepted callback、rollback/settle 语义

契约:team lifecycle、worker creation、worker lifecycle/auto-bridge、inter-agent dispatch 四类职责不能互相偷跑;所有入口都必须复用这组 service,不能在 IPC 或 MCP handler 内各自实现一套状态机。关键实现入口是 OrcaLifecycleService.startTeam/enableTeamOrcaWorkerCreationService.createWorkerInTeamOrcaTeamService.dispatchWorkerTask/handleWorkerTerminalTurnOrcaInterAgentDispatcher.dispatchOrEnqueueOrcaInterAgentMessage

MCP 与 IPC 控制面

cindy_orca 是独立 MCP server,直接顶层注册 18 个工具:15 个 team 控制工具 + 3 个只读诊断工具(后者从已下线的 orca_bridge 桥入,保裸名)。它与 renderer IPC 共用同一组 main 侧 service,因此 MCP 和 UI 操作必须有一致的权限、状态和回滚语义。

  1. start_team
  2. end_team
  3. create_worker
  4. create_workers(批量创建;顺序执行、hard limit 后停止并返回逐项汇总)
  5. list_workers
  6. switch_focus
  7. send_to_worker
  8. interrupt_worker(原子预留下一条输入并优雅停止当前 turn)
  9. get_worker_queue_status(工作态与完整队列)
  10. update_queued_message(排队消息控制)
  11. cancel_queued_message(排队消息控制)
  12. merge_queued_messages(原子合并连续排队消息)
  13. idle_worker
  14. archive_worker
  15. list_available_models
  16. get_workspace_info(只读诊断)
  17. worker_status(只读诊断)
  18. read_worker(只读诊断)

批量创建必须走一次 create_workers 调用,不能让 Lead 并行或连续发多个独立 create_worker。批量工具按输入顺序复用同一个 OrcaLifecycleService.createWorker 原语;首次收到 WORKER_LIMIT_HARD_EXCEEDED 或批次级 HOST_NOT_READY 后不再调用 host,剩余项稳定标为 skipped。返回值必须包含请求数、实际尝试数、成功数、失败数、跳过数、总未创建数、数量闸快照、代码确定生成的 user_report,以及逐项真实 worker/session 或失败终态,供 Lead 如实向用户收口;success_count + failure_count + skipped_count 必须等于 request_count,其中 not_created_count = failure_count + skipped_count。单个 create_worker 继续作为兼容入口,并返回相同的结构化 hard-limit 快照。

排队消息控制 4 工具让 Lead 在消息被 worker 消费前管理自己发出的排队消息:send_to_worker / create_worker(initial_task)在 wakeKind='queued' 时回传 queued_message_id(coordinator 队列内的 clientId),Lead 可据此查看工作态和完整队列、整条改写、撤回,或原子合并连续的 Lead 消息。实现走 OrcaTeamService.listWorkerQueuedMessages / updateWorkerQueuedMessage / cancelWorkerQueuedMessage / mergeWorkerQueuedMessages,语义约束见「协同运行时行为契约 · 消息派发与 auto-bridge」第 6–8 条。

诊断 3 工具是纯只读,实现走 host apps/desktop/src/main/maker-ipc/orcaDiagnostics.ts(读 active team + DB worker 列表 + live session 状态 + 最近 assistant 消息),无建 team 写副作用——这是与旧 orca_bridge 版本的关键差异(旧版经 ensureWorkflowForLead 会顺手建 team)。早期 Lead 侧 orca_bridge(门面 + 私有 registry/restore/auto-bridge)已整体删除:Lead→worker/team 工具面唯 cindy_orca(C)一套,worker→lead 唯 orca_worker_bridge(B,在 packages/orca-workflow)一套;@cindy/orca-workflow 包通过 B provider 与 renderOrcaLeadSystemPrompt/renderOrcaWorkerSystemPrompt 两个 render 函数继续被 host 依赖。

cindy_orca 直接注册到顶层,而不是藏在 list_tools/call_tool 后面;模型在“开协同 / 派 worker”时需要稳定发现 start_team/create_worker。实现见 packages/lizi-mcps/src/orca/server.tscreateOrcaMcpServerDirectToolSinkOrcaMcpDeps

Worker 权限是 Worker 创建偏好,与 Agent、模型、effort、Fast 的“下次创建默认值”同类,不是 Lead 权限的继承项,也不是 Team 数据库字段:

  • 真源是 renderer workerCreationPrefs localStorage;main 只保存内存镜像,应用启动和偏好变化时由 renderer 同步。
  • 没有保存过权限偏好时,产品默认是 bypassPermissions(Full access);这只是可选择的初始值,不是固定模式。UI 每次创建 Worker 都可改选 autobypassPermissions,提交后成为下一次默认值;已经保存过的选择继续优先,不随产品默认值变化而改写。
  • MCP start_team 可显式指定 worker_permission_mode;省略时沿用当前偏好。显式从 auto 升级到 bypassPermissions 时,Main 必须在写入偏好或创建 Team 前等待宿主持有的用户确认,不能只依赖 MCP 审批、tool 描述或 prompt;取消/超时不得产生副作用。确认通过后才更新 main 镜像,并通知 renderer 回写同一份 localStorage。已由用户保存为 bypassPermissions 时,后续沿用不重复确认。
  • create_worker / create_workers 省略权限参数,统一读取当前偏好;不继承 Lead 的 sessions.permission_mode,也不修改已经创建的 Worker。
  • device-link 新控制端只有在被控端 capabilities 明确声明支持 Worker 权限选择时才允许开启协同;已有旧版远程 Team 继续兼容旧创建行为,不宣称或回写该端不支持的偏好。

工具注册是全局可见 + handler 拒绝:

  • 工具可见性不是权限边界:providers.tscindy_orca provider 没有 per-role isEnabled gate,普通 session / worker / lead 的差异必须由 handler 和 host service 拒绝。
  • Codex 场景的真实 session 身份来自 MCP request context;session-context.tsAsyncLocalStorage 提供 withLiziMcpSessionContext / resolveLiziMcpSessionContext,不能依赖全局 fallback 猜身份。
  • renderer 开启协同走 CCAgentSessionView.tsxrequestEnableCollab,MCP 工具通过 cindy_orca callback 进入同一组 service。
  • renderer 通过 UI 开启协同且填写了首个任务时,OrcaLifecycleService.enableTeam 复用既有 initial_task 正文,并附带 Lead session id 与按需回查说明。已有 Lead 会话继续即时派单; 新建任务页先用 ready placeholder 建立可恢复的 Worker agent history,但不派发用户任务;等首条 普通输入/目标已被宿主接受且 user history 可查询后, 再通过 maker:worker:dispatch-ui-assignment 派发同一份 initial_task。Desktop slash command 是被独立 handler 消费、不会形成普通 user history 的控制动作,因此以 command accepted 作为排序边界,不等待不存在的 history 行;command rejected 或普通输入/目标交付失败时 不派 Worker,并明确提示用户到 Worker 面板确认状态。
  • 二段派单凭据在 Lead 首条输入 accepted 前持久化;若 renderer 在 accepted 后、实际 invoke 前退出, Lead 会话重新挂载并读到 user history 后会自动消费仍为 pending 的凭据。invoke 前先转为 uncertain,配合进程内 tombstone,保证响应丢失、重启恢复和迟到 receipt 不会自动重复派单。 多个窗口可能同时恢复同一条 pending,因此 Main 进程还要按 Lead、Worker 与快照时间建立 共享 claim;同一 assignment 的并发或后续请求共用第一次派发结果,最终只调用一次 Worker 投递。
  • Worker 侧按需查询统一走 orca_worker_bridge.read_lead_history:工具从已认证的 worker_id + worker session 归属反查唯一 Lead,调用方不能指定任意 session;只返回 user/assistant、排除 rewound、限制单页数量并保持 (createdAt,rowid) 游标分页。它不 create/resume Lead,也不发送消息,所以本机与 SSH Worker 都能在不唤醒 Lead 的前提下读取 同一份上下文。Worker 只有在任务依赖「当前工作/继续/这个 PR」等相对范围时才查询; 自包含任务直接执行。没有显式 Worker 任务时不会用 Lead 输入擅自起任务;该 handoff 也不 改写 MCP create_worker / create_workers 已由 Lead 组织好的 initial_task
  • device-link 只有在被控端 capability 与本次 enableOrca 成功响应都证明支持 deferred handoff(含 dispatched=false 与宿主时间戳)时才建立二段派单凭据;旧端继续即时派单, 并为兼容保留待发送 Lead 文本。二段派单发生隧道超时时不自动重试,也不把模糊终态说成 失败,避免同一任务重复执行;UI 引导用户先查看 Worker 面板。

Codex Lead 机制

Codex app-server 是单例进程,多 thread 共用同一批 MCP server。Codex Lead 的设计边界是:工具全局注册,身份在调用时通过 thread context 恢复,并由 handler / host service 拒绝越权;它不是 per-thread MCP 工具隔离。

关键约束:mcp-session-id 只标识 MCP transport,不证明 maker session 归属,因此不作为路由依据。Codex HTTP bridge 在已初始化的 MCP POST message 里读取 params._meta.threadId,用它查 maker-core 注册的 Codex thread context;缺失、未知、batch 内不一致或不完整时,bridge 宁可不给 context(让工具调用安全降级为 NO_SESSION_CONTEXT),也不猜一个错误 context 造成跨会话串线。实现见 apps/desktop/src/main/mcp-integrations/codexHttpBridge.ts 的 MCP bridge request handling,以及 apps/desktop/src/main/mcp-integrations/codexMcpThreadContextStore.tsregisterThreadContextunregisterThreadContextgetContextForThreadId

Codex developerInstructions 有两条投递通道。最终选择取决于本 thread 的 Responses 传输形态以及 proxy registry 是否可用,不能只按凭据类型,也不能只看请求是否进入 loopback proxy。凭据类型会参与 provider 选择,但不是 instructions 通道的最终判据;在 proxy 就绪的本地 oauth-bearer host 中,cindy_gatewaycindy_openaibase_url 都指向同一个 proxy:

  • cindy_gatewaysupports_websockets=false,使用可解析请求正文的 HTTP Responses。proxy 就绪时,API key/gateway、codex/ 前缀、第三方 provider,以及其它依赖正文路由或兼容改写的请求都使用这个 provider;proxy registry 通道可用时,maker-core 把启动时拼好的 instructions 按 thread 注册,再由 proxy 注入顶层 instructions。通道不可用时,只有满足下述 gateway-key 直连降级条件的会话改走原生通道。实现见 apps/desktop/src/main/maker-host/codex-gateway-config.ts 的 provider 装配、packages/maker-core/src/agents/codex/index.tsregisterCodexDeveloperInstructions,以及 apps/desktop/src/main/maker-host/codex-proxy-host.tsregisterComposed
  • cindy_openai:只在 oauth-bearer host 中定义,并由官方订阅 thread 显式选择;supports_websockets=true,Codex 默认使用 Responses WebSocket。loopback proxy 在这条通道上只做 socket 隧道,看不到也不能改写请求正文,因此 instructions 由 thread/startthread/resume 的原生 developerInstructions 承载。配置独立 Subagent Provider 路由时不全局关闭 WebSocket;proxy 根据 upgrade 中的 thread/subagent/parent-thread 身份,只对命中该路由的子 thread 回 426,让该子会话降到 HTTP transform,父 thread 继续使用 WebSocket。即使 proxy 用 426 让某个 session 降级到 HTTP,也继续以原生通道为唯一投递来源,不能临时切成 registry 注入。
  • proxy 不可用时不能假装 registry 仍有效:当前仅 gateway-key host 允许退化为直连 gateway,maker-core 会改走原生 developerInstructions;OAuth 与第三方凭证注入依赖 proxy,启动失败时 fail closed。

任何依赖宿主追加 instructions 的能力都必须复用 isCodexProxyChannelReadyuseProxyChannel 的完整判定(host proxy active、thread 非 WebSocket、registry 注册 hook 可用),为每个 thread 选择且只选择一个投递来源;禁止按 API key/OAuth 另做一套推导,也禁止为了兜底同时走两条通道。未来若要在会话运行中刷新任意上下文,设计必须分别说明 HTTP registry 与 app-server 原生通道的更新入口,以及 start/resume/compact 后如何保持一致;原生通道没有运行期更新能力时,不得宣称 WebSocket thread 支持逐请求刷新。

ADR:Codex per-role 工具隔离不走 proxy 改写 tools

决策:不在 proxy 层改写 Responses tools 字段来让 Codex lead/worker/main 每个 thread 看到不同工具。

理由:

  • Codex app-server 不支持单进程内 per-thread MCP 配置;MCP server 配置是 app-server spawn/config 层全局注入,thread/start 参数没有 per-thread MCP override。
  • 另一个等价方案是按 profile 拆多个 app-server 实例,但会引入启动耗时、常驻内存、resume profile 匹配、shutdown 管理和 prompt cache 评估成本。
  • Claude Code 侧的 cindy_orca 本身也是全局可见 + handler 拒绝。Codex 对齐这一语义即可,不为更强的曝光隔离付出多进程复杂度。
  • 未来只有当 Codex app-server 原生支持 per-thread MCP 配置时,再重新评估。

UI、协同 Tab 与 Worktree

普通聊天路由不再渲染独立 Worker pane,也不会在 Lead 变成 Orca Lead 后自动跳到 /cc-agent/orca/<sessionId>。当前主形态是普通 Lead route /cc-agent/<leadSessionId> + 右侧栏「协同」tab:Lead 仍由 CCAgentSessionView 渲染,Worker 列表与 focused Worker 会话流由 apps/desktop/src/renderer/features/right-sidebar/plugins/orca-workers 挂载。协同 tab 是 singleton,协同进行中常驻;点击 tab 的 X 等价于“结束协同”,会先弹确认,确认后 end team 并关闭 tab。

路由层:/cc-agent/orca/:sessionId 只保留为旧 deep link 的兼容 shim。它会转到普通 Lead route;若该 session 仍是 active Orca Lead,则打开/聚焦右侧栏「协同」tab,并把 ?worker=<workerSessionId> 翻译为协同 tab state,随后清掉 URL query。Worker deep link 解析为 /cc-agent/<leadSessionId>?worker=<workerSessionId>,由 CCAgentSessionView 在普通 Lead route 中把 worker hint 翻译进协同 tab state 后清 query。实现见 apps/desktop/src/renderer/lib/orcaSessionIdentity.tsresolveSessionRouteapps/desktop/src/renderer/features/cc-agent/OrcaWorkflowRoute.tsxapps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx

doc rail 不走右侧栏 tab,也不做 route navigate。WorkdirBrowseRoute 的窄聊天栏在 active session 是 Orca Lead 时复用 apps/desktop/src/renderer/features/cc-agent/OrcaSplitView.tsx;该组件现在仅服务 /cc-agent/files/:sessionId 文档模式,以 Lead / Worker toggle 同一时刻展示一个 pane。

Worker 不出现在普通 sidebar。renderer 用 isOrcaWorkerSession(session) => session.orcaRole === 'worker' 识别 worker,sidebar filter 直接排除 worker,见 apps/desktop/src/renderer/lib/orcaSessionIdentity.tsisOrcaWorkerSessionapps/desktop/src/renderer/features/cc-agent/CCAgentSidebarUpper.tsxsidebarSessions filter。worker deep link 会解析到所属 Lead 的普通 route,并通过 ?worker= 一次性聚焦右侧栏协同 tab,见 apps/desktop/src/renderer/lib/orcaSessionIdentity.tsresolveSessionRoute

Worktree 现状:Orca 与普通 session 对齐,worktree 是可选项,不强制;toggle off 时 Lead/Worker 使用用户选的 workingDir,toggle on 时 Lead/Worker 共用同一个 worktree。

协同运行时行为契约

本节记录当前系统必须持续满足的运行时不变量。它们不是远期规划,而是 Lead / Worker 协同时已经依赖的行为契约。

Lead 派单与执行通道

  1. Orca Worker 派单不得由原生 subagent 冒充(状态:不变量)
    用户把任务指派给已有 Worker 的 role/label、要求 Lead 向 Worker 派单,或在 active team 内按多个角色并行派单时,Lead 必须先读 workspace,再按场景通过 send_to_worker(复用既有 Worker)/create_worker(显式新建一个 Worker)/create_workers(显式新建多个 Worker)进入 Orca 状态机;如果请求的 role/label 当前没有匹配的既有 Worker,Lead 必须先如实说明没有匹配项并征求是否创建,不能静默改派别的 Worker,也不能退回 native subagent。 如果用户只泛称“Worker”且当前没有任何 Worker,Lead 同样必须先说明并询问是否创建; 任务指派本身不等于创建授权。多角色请求必须在派发任何任务前解析全部 role/label; 只要有目标缺失或创建授权未决,就不得部分派发,必须集中列出缺失项并先询问。全部映射 和授权确定后,多角色请求必须在一个并行 tool-call batch 内一次发出:既有目标走 send_to_worker,恰好一个授权新目标走带 initial_taskcreate_worker,两个及以上 授权新目标走一次带齐 initial_taskcreate_workers;不能等待某个派发结果后才发 剩余任务。Codex spawn_agent 或 Claude Code Agent/Task 的完成结果不能当成 Orca Worker 的完成。只有用户明确要求一次性 subagent/子代理且没有把任务指派给 Orca Worker,或明确要求不使用 Orca Worker 时,才走原生 subagent。

  2. 实际执行通道必须披露并按同一通道验收(状态:不变量)
    Lead 汇总前必须按执行该任务的同一通道确认真实终态,并逐项标明 Orca Workernative subagent。原生 subagent 由 Codex collabAgentToolCall/Claude Agent/Task 任务卡展示标识、任务和终态,但不得写入 Orca Worker 状态,也不得触发 Orca Worker 完成提醒。native subagent lifecycle tools(包括 wait_agent 或等价能力)只能管理用户明确要求的 native subagent,不得用来等待或管理 Orca Worker。部分 Worker 报告先到时,Lead 只处理当前报告与必要追问,不主动等待仍在 工作的 Worker;后续报告会作为新消息自动唤醒 Lead。实现指针: packages/orca-workflow/src/orca-bridge-prompt.tsrenderOrcaLeadSystemPrompt,以及 packages/maker-core/src/agents/codex/translator.tshandleCollabAgentToolCall

  3. 只有真实异步派发才静默结束 turn(状态:不变量)
    send_to_worker 只有返回 ok=truewake_kindresumedalready-activequeued 时才算真实派发。create_workercreate_workers 的每个 created 结果, 只有 dispatched=true、存在 queued_message_id,或 dispatch_outcome.kind=session-dispatchdispatch_outcome.dispatched=true (包括 dispatch_outcome.wakeKind=queued)时才算真实派发。Lead 只能据这些具体信号 零输出结束当前 turn;工具失败、只创建 Worker 而没有首任务,或首任务未派发时,必须 立即向用户报告真实结果,不能等待一个不会到来的 Worker 回报。create_workers 每次 返回都必须先转告 user_report(若存在)并汇总逐项终态。多角色 batch 返回后还必须 汇总其他工具的失败/未派发结果;若至少一个结果满足上述派发信号,只做这一次合并报告 后立即结束 turn,全部成功且无 create_workers 必报内容时零输出结束。若没有任何任务 派发,则报告结果后等待用户决定。所有情况都不得继续调用工具,也不 sleep、不 poll。 实现指针: packages/orca-workflow/src/orca-bridge-prompt.tsrenderOrcaLeadSystemPrompt,以及 packages/lizi-mcps/src/xdt-helper/create_workers.ts 的批量结果契约。

消息派发与 auto-bridge

  1. 忙碌目标不丢消息(状态:不变量)
    Lead/Worker 互发消息时,如果目标 session 正在跑 turn、存在 queue lock,或刚好在派发竞态里返回 SESSION_RUNNING,消息必须进入输入队列,而不是丢弃或直接失败。实现指针:orcaInterAgentDispatcher.tsdispatchOrEnqueueOrcaInterAgentMessage,以及 register.tsAgentInputCoordinator wiring。

1a. 空闲 live 直发前必须先做换窗预检(状态:不变量)
目标空闲且已有 live session 时,dispatcher 仍走 session.send(),不经过 sendToSessionInternal()。这条直发必须先调用与用户发送相同的 prepareUnhealthySession:满窗或当前进程内 needsRollover 锁存时关闭旧原生窗口,再 getLiveSession。不能把 inter-agent 消息打进应被丢弃的旧窗口。prepare → 重新取 live → send 整段必须复用 sendToSessionInternal 的 per-session 锁;锁已被占用时先排队,不要把 in-flight prepare 当成健康状态继续直发。没有 live 后释放锁再走 sendToSessionInternal,避免自己把自己排队。实现指针:orcaInterAgentDispatcher.ts 的 live 分支,以及 register.ts 注入的 prepareUnhealthySession / withSendToSessionLock

  1. accepted 才能产生运行副作用(状态:不变量)
    只有底层 send accepted 后,才能把 worker 标成 running、建立 auto-bridge pending 并广播 UI;dispatch 失败必须 rollback 到 accepted 前状态,且 rollback 不得覆盖已有终态。实现指针:orcaTeamService.tsdispatchResolvedWorkerrollbackAcceptedDispatchState

  2. queued accepted 也要同样结算(状态:不变量)
    如果 inter-agent 消息进入队列,accepted callback 必须与直发路径保持同样的 settle / rollback / discard 语义,避免 auto-bridge pending 泄漏。accepted 只表示临时接管 worker 的 running/auto-bridge/manual-interrupt 身份;只有 vendor dispatch 成功后才 commit。直发/恢复路径 accepted 后若因 SESSION_RUNNING 转回排队,必须先 rollback 本次临时接管,再等待下一次 accepted;accepted 生命周期函数自身失败则必须回滚并取消 vendor dispatch,不能让普通 callback 异常被吞后留下半套状态。旧 terminal 若撞上这个窗口,必须先释放 worker transition 锁,再等 commit/rollback:commit 后旧 terminal 作废,rollback 后恢复旧身份并继续最终收口;不得持锁等待 settlement;rollback 也不得覆盖 provisional 期间产生的更新 manual-interrupt 标记。若 Stop 在 provisional 窗口写入了更新标记,rollback settlement 必须携带最终保留的 manual identity,等待中的旧 terminal 重绑该 identity 后再收口,不能继续拿最初快照把自己判 stale。实现指针:orcaInterAgentDispatcher.ts 的 queued accepted callback API、orcaTeamService.ts 的 provisional dispatch settlement,以及 register.tsAgentInputCoordinator callbacks。

  3. worker 主动回报会结清自动回报态(状态:不变量)
    worker 主动 send_to_lead 一旦入队或 accepted,就必须清掉该 worker 的 auto-bridge pending,防止 Lead 同时收到手动回报和 auto-bridge 双份结果。实现指针:orca-bridge-mcp.tssend_to_lead tool handler,以及 orcaTeamService.tsclearAutoBridgeState / clearRuntimeState

  4. 手动中断不 auto-bridge(状态:不变量)
    用户手动 stop / abort worker 后,terminal turn 不应把最后消息 auto-bridge 给 Lead。实现指针:register.ts 的 stop / abort IPC handlers,以及 orcaTeamService.tshandleWorkerTerminalTurn

5a. 自动续跑窗口内的 vendor 终态不是产品错误(状态:不变量)
Worker turn 被 vendor 报终止型 error,但 interrupted-turn auto-resume 仍 pending / deferred 时,不得 auto-bridge「异常终止」,也不得把 worker DB 标成 error。同一失败轮随后的 unclaimed done(Codex/Claude 的 error→done 尾巴)同样不是产品终态,不得 handleWorkerTerminalTurn(status:'done'):那会把仍在续跑的 worker 标成 done、清掉 auto-bridge runtime,并让后续 L3 surface 命中终态短路而漏报。零输出 / 纯 tool 失败轮没有 assistant persist id;用户接手还会 force-flush suppressed entry 并清 pending。这条失败轮 completion tail 必须独立于这两份状态,按失败轮的 sessionTurnGeneration 活到配对 done 被消费,且不得在用户 enqueue 时清掉。新 attempt 的 token-bearing running 才丢掉它,以免误伤续跑成功后的产品 done。没有配对 done 的旧 tail(SDK / event loop crash 等)不得靠 status(true) 无 token 一直活着:后续普通用户 / Orca 新 turn 常常没有 turnAttemptToken,其更大 generation 的 done 不是这条尾巴,必须照常 handleWorkerTerminalTurn。auto-bridge pending 必须保留,以便续跑成功后的真实 done 仍能桥接。仅当续跑耗尽、被拒绝、或作为产品失败 surface 时,才用当初压住的 terminal payload(含 capture 身份)恰好一次调用 handleWorkerTerminalTurn。flush / discard / 用户接手 / 手动 stop 不得走这条收口。实现指针:register.ts 的 event adapter、autoResumeBookkeeping.tsshouldSkipOrcaWorkerTerminal / noteFailedTurnCompletionTail 与 suppressed-error surface 路径。

  1. Lead 只能管理自己的排队消息,撤回必须结清 accepted 暂存(状态:不变量)
    get_worker_queue_status / update_queued_message / cancel_queued_messageresolveWorkerRef 按 caller Lead 归属校验后,只允许操作目标 worker 队列中 origin.kind='orca' 的条目(worker 队列中的 orca 条目只可能来自其 Lead);队列可见性口径是「看得全、只能动自己的」(2026-07-21 产品决策)——用户手打与 scheduler 排队条目对 Lead 正文可见(供基于完整队列内容编排),pre-dispatch active / direct steering 条目也必须出现并标记 consuming=true,但非 Lead 条目不可改不可撤(NOT_LEAD_MESSAGE)。get_workspace_infoget_worker_queue_statusqueued_count 都必须使用这份完整 inspection 的 messages.length,不能让 workspace 只数 pending。修改是整条正文替换,必须经 rebuildQueuedOrcaLeadMessage 按原派发格式重建 text / persistedContent / chatMessage.content / origin.displayText,身份字段(clientId / createOpts / createdAt / senderLabel)锚定原条目;不许直接调 coordinator 的 updateText(会破坏 orca 派发格式耦合)。撤回必须走 coordinator remove(触发 onDiscardedQueuedMessage → dispatcher 丢弃该 clientId 的 accepted 暂存回调,与 Stop 清队列同一条 settle 路径),否则 accepted 回调表泄漏。steering 中的条目一律拒绝(MESSAGE_CONSUMING)。实现指针:orcaTeamService.tsresolveLeadQueuedMessage 与队列控制方法、orcaInterAgentDispatcher.tsrebuildQueuedOrcaLeadMessageagent-input-coordinator.tsreplaceQueuedMessage

  2. 中断是“预留下条输入 + 优雅停旧 turn”的单次原子操作(状态:不变量)
    send_to_worker 公开 schema 保持普通直发/排队语义,不带 interrupt 字段;interrupt_worker 是唯一公开中断入口。两者进入 OrcaTeamService 同一个私有 dispatch 路径,以内部 normal/interrupt mode 分流,不能把 interrupt_worker 实现成 stop + send 两次工具调用。interrupt 必须复用 buildQueuedOrcaInterAgentMessage、accepted 回调、host acceptance stamp、clientId 去重与持久化链路;队列恢复完成后,在无 await 的 coordinator 临界区把新消息插到现有 pending 队首,并在 emit/drain 之前同步发起旧 turn 的 graceful stop。消息在 unsupportedunconfirmed 时仍保留队首,不硬 abort;用户暂停态不得被解除;stop adapter 抛错/拒绝或结果为 unconfirmed 时,queue_paused 必须读取 coordinator 当前投影,不得默认成 falselead_interrupt 必须在旧 terminal 唤醒 drain 前同步捕获,旧 turn 不 auto-bridge,也不得覆盖已 accepted 的新 turn 状态;下一次 accepted dispatch 清理标记。整个流程从预留前到 stop 结果确认都计入 activeWorkerDispatches,与 done 确认互斥。

  3. 多条合并必须一次校验、一次交换、一次持久化(状态:不变量)
    merge_queued_messages 只接受至少两个不重复、按活队列顺序连续、pending、非 consuming/steering、由当前 Lead 发出的消息。durable restore 必须先成功;之后同步重读到一次性数组交换之间不得 await。任一消息缺失、次序变化、非连续、非 Lead 或已 consuming 时,整次零修改并返回 QUEUE_CHANGED 与最新完整队列,不能部分合并。survivor 保留最前目标的 clientId、队列位置与 hostAcceptedAtMs;移除项必须逐条走 discard settlement 结清 accepted 回调;最终只 emit/持久化一次,不暴露中间快照。update_queued_messagecancel_queued_message 继续保持单条语义,不得多步模拟合并。

Worker 运行态

  1. worker 终态不被失败回滚覆盖(状态:不变量)
    派活 accepted 后如果后续失败,rollback 只允许把仍处于 running 的 worker 恢复到旧状态;已经进入 done/error/idle 的 worker 不得被回滚覆盖。实现指针:orcaTeamService.tsrollbackAcceptedDispatchStatehandleWorkerTerminalTurn

  2. done 确认与派活必须互斥(状态:不变量)
    done worker 的隐式 idle_worker(expectedStatus='done') 确认不得与同一 worker 的派活交错:派活从 pre-resume reservation 起至 host dispatch settle 期间持有 active dispatch 计数,done 确认必须在每 worker transition 队列中串行执行,并在计数非零时拒绝。确认还必须在 DB CAS 前后检查 live turn、send_to_session 锁与 pending 输入;close 必须使用 Session.closeIfIdle 原子地与 send reservation 互斥。任一检查失败或 close 失败时,若已 CAS 为 idle,必须只恢复仍为 idle 的记录到 done,不得覆盖新终态。实现指针:orcaTeamService.tswithWorkerTransitionactiveWorkerDispatchesdispatchWorkerTaskidleWorker

  3. idle worker 恢复必须保留 extraDirs(状态:不变量)
    idle worker 被 switch_focussend_to_worker 唤醒时,要从 DB 读取 extra_dirs 并带回 bootstrapSession,否则恢复后的 worker 会丢附加目录上下文。实现指针:register.ts 的 idle worker resume helper。

  4. worker 状态变更必须广播给 renderer(状态:不变量) 创建 worker、enableOrca 创建首个 worker、任意真实 worker turn 开始后的 running、idle、archive、terminal done/error 都必须广播 ORCA_WORKER_CHANGED。worker DB status 跟随真实 turn 生命周期:Lead 派活或用户直接对话 worker 时,只有真实 turn 开始才置 running产品 terminal 才置 done/error(auto-resume 仍 pending/deferred 的 vendor 终态不是产品终态,见消息派发与 auto-bridge 5a);switch_focus / resume / restore 只能恢复可访问性,不能凭空置 running。实现指针:orcaLifecycleService.tscreateWorker / enableTeamorcaWorkerCreationService.tscreateWorkerInTeamorcaTeamService.tsdispatchWorkerTask / handleWorkerTurnStarted / handleWorkerTerminalTurnregister.ts 的 status event adapter、useWorkers.tsuseWorkers

  5. 切换 session 不重置 worker 未读状态(状态:不变量) worker done 的红点由 renderer 进程级 edge-trigger attention store 维护,而不是跟随组件切换重置的局部 state。worker 状态跳变进 done 才标 attention;正在查看该 worker 时清除 attention;只切走 / 切回不应让同一轮 done 重新变未读。实现指针:useOrcaWorkerAttentionWatcher.tscomputeWorkerAttentionUpdatesRolePillDropdown.tsx 的 selected worker clear effect,workerAttentionStore.tsattentionWorkerIds

  6. 重启后对 known worker 懒登记(状态:不变量) app 重启后内存里的 known worker / vendorOptions 都会丢。恢复时不能只信内存 cache,必须通过 DB 的 sessions.orca_roleorca_workersorca_teams 懒合成 Orca vendorOptions / worker link,保证手动 stop、terminal turn、worker 列表仍能识别已存在 worker;手动中断跟踪只能作为运行时优化,不能作为唯一事实源。刚开启协同但 worker 尚未对话时,也必须先写出可恢复的 agent 侧历史;当前通过 ready placeholder 触发 worker 首次 send,避免 Codex worker 因 rollout 缺失在重启后无法 resume。实现指针:register.tssynthesizeOrcaVendorOptionsFromDbsessionCreateHandler.tssendWorkerReadyMessageorcaLifecycleService.tsenableTeam / sendWorkerReadyPlaceholder 依赖、orcaTeamStore.tslistWorkersByLead / getWorkerLinkorcaManualInterrupt.ts 的 known worker / manual interrupt store。

  7. 重启后 Lead↔Worker 互访 / resume 不随开启路径变化(状态:不变量) 无论协同通过 enableTeam 自动创建首个 worker、MCP start_team + create_worker,还是 renderer 的协同按钮开启;也无论重启发生在对话中途,还是初始化完毕但 worker 尚未接过真实任务,maker 重启后 Lead 与 Worker 都必须能继续互访。send_to_workersend_to_leadswitch_focus / idle resume 不能因为内存态丢失、worker link 懒登记缺失或空 worker rollout 缺失而失败。实现指针:CCAgentSessionView.tsxrequestEnableCollabpackages/lizi-mcps/src/orca/server.tsstart_team / create_worker 顶层注册、xdt-helper/start_team.ts / create_worker.tsorcaLifecycleService.tsstartTeam / createWorker / enableTeamregister.tssynthesizeOrcaVendorOptionsFromDb / resumeOrcaWorkerSessionIfMissingorcaTeamService.tssendToWorkerorca-bridge-mcp.ts 的 worker send_to_lead handler。

  8. 被同 turn 收尾拒绝的 done 确认必须在 terminal 边界补收口(状态:不变量) worker 的回报 settle(send_to_lead 被接受/入队)会先把持久化状态置 done,而 worker 自己的 turn 可能还在收尾;renderer「看到 done 即 ack」此时会被 active-turn / send 锁守卫以 WORKER_STATE_CHANGED 拒绝。被这两类守卫拒绝的确认必须登记下来,在该 turn 的 handleWorkerTerminalTurn done 分支重试一次(fire-once,重试失败不重登记);新 turn 开始(handleWorkerTurnStarted)必须作废登记。没有登记过的 done 不得在 terminal 边界自动收口——done 的产品语义是「保持到用户看到为止」。实现指针:orcaTeamService.tsdeferredDoneAcknowledgementsidleWorkerhandleWorkerTurnStartedhandleWorkerTerminalTurn

测试与回归清单

当前文档要求保留以下回归方向:

  • Service 边界:orcaLifecycleServiceorcaWorkerCreationServiceorcaTeamService 的单测覆盖 start/enable/create/dispatch/idle/archive/auto-bridge 关键路径。
  • Auto-resume × auto-bridge:pending/deferred 的 vendor 终态不得 bridge / 置 error;失败轮随后的 unclaimed done 同样不得收口(含用户接手 force-flush 之后、无 persist-id 的零输出尾巴);无配对 done 的旧 tail 不得吞掉后续更大 generation 的产品 done;surface/exhaust 恰好一次收口;成功续跑保持 running 且 pending 仍在;手动 stop 仍不 bridge。见 autoResumeBookkeeping.test.tsshouldSkipOrcaWorkerTerminalinterruptedContinuationContract.test.ts
  • 空闲 live 直发:orcaInterAgentDispatchersession.send() 前必须先 prepareUnhealthySession,再重新 getLiveSession;prepare 关闭旧 handle 后不得继续向旧对象 send。并发直发必须串行化到同一把 per-session 锁。
  • Worker 创建权限偏好:覆盖无保存偏好时默认 bypassPermissions、已保存的 auto / bypassPermissions 继续优先、MCP 显式 auto → bypassPermissions 必须先经用户确认且取消/超时零副作用、复用 Team 时省略不重置/显式才更新、首个与后续 Worker 都读取共享创建偏好、已有 Worker 权限不反写、renderer localStorage 启动同步与 tool 写回,以及旧 device-link 被控端被阻止开启协同并提示升级。
  • MCP 工具:cindy_orca 18 工具(15 team + 3 只读诊断)的 role gate、ctx 缺失、worker/main 误调用、soft/hard limit、duplicate label、budget model API mode gate;create_workers 另覆盖默认 hard limit、配置 hard=3、部分成功、连续失败与 hard-limit 后不再调用 host;诊断工具的纯只读语义(无 active team 时返回空 workspace、不建 team);排队消息控制 4 工具的归属校验(跨 lead 拒绝)、完整 consuming 投影、非 lead 条目拒绝(NOT_LEAD_MESSAGE)、steering 拒绝(MESSAGE_CONSUMING)、撤回结清 accepted 暂存,以及合并的连续性、冲突零修改与移除回调结清;interrupt_worker 另覆盖队首预留、暂停态、graceful-stop 全部 outcome、auto-bridge 抑制和 done 互斥。
  • Codex MCP context:CodexMcpThreadContextStore 覆盖按 threadId 查 context、unknown / missing threadId fail-closed、unregister 后清理、vendorOptions 引用保持;codexHttpBridge 覆盖从 JSON-RPC params._meta.threadId 注入真实 session context。
  • Host 归属校验:send_to_workerinterrupt_worker、队列控制、idle_workerarchive_worker 经共享 resolveWorkerRef(同时接受 worker_id / session_id 两种 id)必须以 caller 自身 Lead 身份校验,拒绝跨 workflow worker id 与 ctx 缺失;即使模型传错 id 或换用另一种 id,也不能越权操作。
  • UI route:Orca worker 不出现在 sidebar,Lead 自动进 split route,worker deep link 解析到 Lead split route;已有测试守住“不用 fork parentSessionId 或标题推断 Orca mapping”,见 apps/desktop/src/renderer/__tests__/orcaWorkflowRoute.test.tsdoes not use fork parentSessionId or title-linked worker lookup for Orca mapping
  • Fork / side_chat 底座:user fork、assistant fork、Codex tailTurnsToDrop、Claude turn-final assistant uuid、tool_use 拒绝。
  • Worktree:toggle off/on、非 git/已在 worktree 只 disable worktree toggle、不阻断 send;Lead close 后清 worktree。
  • 重启后 Lead↔Worker 互访(resume)回归矩阵:覆盖开启路径 enableTeam 自动首 worker / MCP start_team + create_worker / renderer requestEnableCollabSESSION_ENABLE_ORCAenableTeam,以及重启时机“对话中途”和“初始化完毕但未对话”;验收动作至少包含 list_workers 可见 worker、Lead 侧 send_to_worker 可投递或排队、Worker 侧 send_to_lead 可回传、focused / idle worker 可 resume。
  • 重启矩阵 / 场景 1「lead worker 正常初始化」:创建协同后应同时写出 Lead orca_role='lead'、worker session orca_role='worker'orca_workers link 与 focused/list projection,renderer split view 和 MCP list_workers 都能看到同一 worker。
  • 重启矩阵 / 场景 2「协同进行到一半,重启 maker,lead worker 能正常互相访问」:已有 Lead↔Worker 对话历史和可能的 running/done 状态时,重启后依赖 sessions.orca_role + orca_workers/orca_teams 懒合成 vendorOptions / worker link,send_to_worker 与 worker send_to_lead 仍能恢复投递;恢复 Worker runtime 必须保留 sessions.permission_mode,不得静默升级权限。
  • 重启矩阵 / 场景 3「刚开启协同,初始化完毕后,不进行任何对话,重启 maker,lead worker 能正常互相访问」:空 worker 不能只停留在 DB link;ready placeholder 必须让 worker agent 侧写出可 resume 的历史,尤其防止 Codex rollout 缺失。
  • 重启矩阵 / 场景 4「先开单会话,进行到一半的时候,通过提示词开启协同,看到 lead worker 正常初始化,然后立刻重启 maker 后,lead worker 能正常互相访问」:提示词路径必须走 MCP start_team + create_worker 与 shared lifecycle service,不能绕开 worker link、role 标记、vendorOptions rehydrate 和 resume 约束。
  • 重启矩阵 / 场景 5「先开单会话,进行到一半的时候,通过协同按钮开启协同,看到 lead worker 正常初始化,然后立刻重启 maker 后,lead worker 能正常互相访问」:按钮路径必须从 requestEnableCollab 进入 SESSION_ENABLE_ORCA / enableTeam,并满足与 MCP 路径相同的重启恢复与互访能力。

坑点与不变量

1. Codex MCP context 路由必须 fail-closed(状态:不变量)

Codex HTTP bridge 只能把 JSON-RPC params._meta.threadId 当业务会话路由依据;mcp-session-id 只标识 MCP transport、不证明 maker session 归属,因此不作为路由依据。路由规则:threadId 命中已注册 context 时才注入;缺失、未知、batch 内不一致或不完整时一律不注入,让工具调用安全降级为 NO_SESSION_CONTEXT,绝不猜一个 ctx 造成跨会话串线。实现见 CodexMcpThreadContextStore.getContextForThreadIdcodexHttpBridge 的 message-level routing。

2. Codex MCP context 生命周期必须跟 thread 绑定(状态:不变量)

Codex thread start / resume 成功后必须注册 threadId -> session context;session close 时必须 unregister。context 注入发生在 tool-call message 处理期间,不依赖 turn/item notification 的时序窗口,也不依赖 TTL。这样长 turn、并发 tool call、outdated notification 都不会影响 session context 的可用性;真正缺少 _meta.threadId 时仍按坑点 #1 fail-closed。

3. worker 控制入口必须按 caller 自身 Lead 身份做归属校验(状态:不变量)

send_to_worker / interrupt_worker / 队列控制 / idle_worker / archive_worker 通过共享的 resolveWorkerRef 解析目标:它同时接受 worker_id 与 worker session_id 两种(id 当 opaque token、精确匹配,命中 0 个 → NOT_FOUND、多匹配 → 报错且无副作用),但只在 caller 自身的 worker 列表内解析。必须用 caller 自身的 ctx.sessionId 作为 callerLeadSessionId,校验目标 worker 与该 Lead/team 一致;ctx 缺失必须直接拒绝(LEAD_NOT_SUPPORTED,不放行、不回填),跨 workflow 的 worker id 在 host service 层拒绝。这是与坑点 #1 正交的纵深防御:fail-closed 路由保证 ctx 不串成别人,归属校验再挡住“ctx 正确、模型传错 id”的越权。实现见 orcaTeamService.tsresolveWorkerRef 与各 worker 控制方法。

switch_focus 是纯 UI focus 操作,走单独的纯函数 findFocusTargetWorkerorcaTeamService.ts):worker_id / session_id 优先精确匹配,label 兜底——比 mutation 工具多接受人类友好的 label(人/agent 常按 label 切画面),故意不复用 resolveWorkerRef(mutation 工具不该认 label)。register.ts 的 IPC handler 与 MCP holder 两条 switch_focus 路径共用这一个 helper,不要再各自 inline find。

4. orca_worker_bridge 对 Codex 全局可见(状态:follow-up)

orca_worker_bridge 对 Codex session 的可见性必须由 handler 做角色校验兜底。可见性短期收不掉时,handler 必须用 resolveLiziMcpSessionContext 检查真实 orcaRole === 'worker',普通 session / Lead 调用应返回明确错误,而不是 fallback 到空 ctx。

5. Lead prompt 的诊断工具名(状态:已解决)

历史问题:renderOrcaLeadSystemPromptpackages/orca-workflow/src/orca-bridge-prompt.ts)的 Lead prompt 用裸工具名引用 get_workspace_info / worker_status / read_worker,而这些过去只在 Claude-lead 专属的 orca_bridge 里、Codex Lead 看不到,会制造稳定噪音。统一后这 3 个诊断工具以同名裸工具桥入全局可见的 cindy_orca,Claude 与 Codex Lead 都能解析到真实工具,噪音消除。Lead prompt 继续只用裸工具名;新增流控文案只声明 interrupt_workerget_worker_queue_statusmerge_queued_messages 的选择与原子性边界,并按 maker-core-and-agent-behavior.md §4 保持 system 段改动最小、用快照测试锁定。

6. Model 不可中途换,effort 可调(状态:不变量)

对同一个 live worker session,中途换 model 应视为重建执行单元,不是普通请求参数。原因:model 切换会破坏 prompt/cache 前缀稳定,Claude SDK 也可能解析失败。effort 是 per-turn 参数,可以中途调。创建执行单元前可以选 model/effort;运行中 effort 可调,model 不可调。

Worker 创建与默认模型继承边界对历史短 ID 做 provider-aware 兼容:优先保留目标路由上 可用的精确 ID;只有精确匹配失败、候选来源属于 managed Gateway、且 namespace/short-id 规范候选恰好一个并同时存在于 list_available_models 的 capabilities 清单、Model Registry 又确认该路由 ID 本身就是稳定模型身份时,才在内存中解析为规范 ID。 不能只按后缀推断,否则会把标准模型静默切到同后缀的折扣模型。Custom Provider 的精确短 ID 必须保持不变;候选为零、多个或目录身份不一致时 fail-closed,不猜测、不改写已有 Lead、 Session 或默认配置的持久化值。 Worker defaults 中单独缓存的 providerId 不约束从 Lead / 硬编码默认值继承模型的规范化; 它只有在仍提供最终精确模型 ID 时才参与最终路由。反过来,旧 defaults 只缓存 model 而 没有 providerId 时也不能借用 Lead 来源,必须按当前已连接来源解析默认路由。缓存来源已失效 或不提供该模型时,创建边界可回退搜索当前已连接来源,但最终仍必须得到精确路由或唯一 managed 规范候选,否则在 reservation / bootstrap 前拒绝。

Worker 创建边界一旦解析出实际来源,就必须把该 providerId 写入 Worker session;不能因为 来源属于官方订阅或 managed 默认路由而重新清成 null。显式来源优先;未显式选择时,同 agent 且提供目标模型的 Lead 来源优先,其次使用有效 Worker defaults 来源,再按同一份当前 provider routing snapshot 解析默认来源。Lead 已绑定的同模型来源当前不可用时必须在 reservation / bootstrap 前明确拒绝,不能静默改走 Cindy AI 或其它凭证家族;旧的 model-only defaults 则按当前 snapshot 解析并保存实际来源。这样 Claude Code 的 Anthropic 来源会稳定得到 oauth-bearer,显式 Cindy AI / 自定义 Provider 仍保持各自路由。

7. Prompt / tool / MCP 注册路径必须评估 maker-core 四指标(状态:不变量)

任何改动落在 prompt 拼接、tool/MCP 暴露、translator、event loop、model 映射、usage/token 计量路径时,都必须评估缓存率、性能/返回速度、返回内容准确性,并按 maker-core-and-agent-behavior.md §3 给出实测证据。尤其不要把 per-turn 易变内容塞进稳定 system/developer 前缀,不要在会话中途随意增删/重排 tool 定义。

8. forkedAtMessageId schema 注释滞后(状态:follow-up)

forkedAtMessageId 需要覆盖 user 与 assistant 两种 fork 来源,schema 注释也应按该语义维护,见 apps/desktop/src/main/localDb/schema.tssessions.forkedAtMessageId。这是文档/注释 bug,不影响运行;后续改 schema 注释时必须通过正常 migration/代码 review 流程,不手改 migration。

9. parent_session_id 不表示 Orca 派单(状态:不变量)

parent_session_id 是 fork/session-branch 关系,不是 worker 归属关系。Orca 归属必须查 orca_teams/orca_workers。已有 route 测试防止 split view 用 parentSessionId 或标题匹配 worker,见 apps/desktop/src/renderer/__tests__/orcaWorkflowRoute.test.tsdoes not use fork parentSessionId or title-linked worker lookup for Orca mapping

10. Worker 回收要克制(状态:无损 idle release 已落地)

Worker 的价值在于上下文可延续、可观察、可介入。PR #340 已落地无损的 idle release:idle watcher 在 Worker 超过设置的空闲阈值、没有运行中的 turn 或排队输入时,关闭本进程持有的 Maker runtime,并以 CAS 原子写入 status = idleidle_sinceidle_since != null 是 runtime 已释放的持久化标记,不是 idle/running/done/error 之外的第五种 Worker 状态;Worker 记录、session、历史和上下文都继续保留。

再次向该 Worker 派发任务时,既有 resume 链路会复用或重建 runtime,并在任务被接受时恢复运行态。共享 userData 多实例下,watcher 只释放本进程实际持有 runtime 的 Worker;没有本地 runtime 的记录保持不变,由其 runtime owner 负责处理。

这套机制只等价于无损 hibernate,不等价于 archive/delete。idle_worker/archive_worker/end_team 仍保留显式控制语义;长期 Worker 的自动有损回收,以及 persistent/ephemeral 分型后的差异化回收策略,仍应保持保守并在后续单独落地。

Part 2 · 未来规划

执行单元愿景

Orca 的长期方向不是“进入一个固定协同模式”,而是在主线旁随手开启一组 side activity / child execution。不同类型底层能力不同,但 UI 和控制面尽量统一:都能被看见、被管理、被中断/归档、记录来源、产物和状态。

顶层 side activity 目标类型:

  • worker:Lead 派单的 side session,可持续、可插话、可回传。
  • side_chat:用户从某个消息/turn fork 的分支对话,自驱探索,默认不回传,可选回传。
  • workflow_run:确定性编排运行,展示 step 树、进度、失败点和产物。

subagent 不是顶层 side activity。它是 runner:默认轻量、用完即弃,作为 workflow step 或 Lead 临时小活的底层执行体。只有当用户需要看过程、追问、接管、留痕时,才把其记录接手成可见 side session。

三态拆分

未来所有可见执行单元都要拆开三件事:

维度问题示例取值
对象在不在身份、历史、上下文是否保留exists / deleted
UI 显不显示是否挂在当前面板或 sidebaractive / archived
后台醒没醒SDK/进程是否 live、能否马上接活live / dormant

不要再让 idlearchiveclose 混着表达这三件事。archive 是 UI/可见性,dormant 是后台活性,delete 才是对象删除。

Worker 生命周期分型

当前 schema 尚未落地 persistent/ephemeral 字段和分型策略。PR #340 先提供了与类型无关的 idle release/resume 基础能力:所有合法 Worker 状态都可在空闲超时后释放本地 runtime,以 idle_since != null 记录释放结果。下面两类是后续分型后的目标策略:

Worker 计划分两类:

类型语义默认回收策略
persistent长期协作者,价值是上下文延续与可介入允许无损 hibernate/resume;不自动 archive/delete
ephemeral可观测的一次性执行单元,像“看得见的 subagent”done/error 后可优先进入回收候选

role 与 lifecycle 正交。developer/reviewer/tester/merger 是职责,不表示长期或一次性。分型落地后,创建时可以声明 lifecycle;运行中升降级放后续。

一次性任务不一定要开 worker。只有满足“需要独立可见历史、多轮追问、Lead 切过去 review、用户接管、共用模型/桥接/审计”等条件时,才值得用 ephemeral worker;否则应走 subagent。

顶层类型与 Runner 分层

第一层是顶层 side activity:

类型可视化对象介入深度
worker单条 side session 的过程流深:派活、追问、接管、回传
side_chat分支对话的过程流深:用户自己驱动,可选择回传
workflow_run编排树、进度、失败点、产物编排级:pause/cancel/retry/skip/看产物

第二层是 workflow_run 内部 step 树:每个 step 有 runner,默认是 subagent;高风险、长耗时、失败、需要用户介入的 step 可以使用 worker runner 或被接手成 worker。

workflow step 不是顶层类型;它属于 workflow_run 内部。subagent 也不是顶层类型;它是 runner。

Side Chat

side_chat 和 worker 共用同一套 pane 外壳。区别不在 UI 壳,而在关系契约:

维度workerside_chat
发起者Lead 派单或用户显式创建 worker用户从当前消息/turn fork
默认通信Lead ↔ Worker,可自动/手动回传用户 ↔ Side chat,默认不回传
结果归属默认回给 Lead,由 Lead 验收默认留在分支,可选采纳/回传
关系字段task、role、label、return_to、communication policysource session/message/turn、fork reason、return policy

PR #107 已完成 side_chat 的底层 fork 数据动作:

  • user fork:复制该 user 之前上下文,新 session composer 预填原提问。
  • assistant fork:复制到该回复所在 turn 的末尾,避免 dangling tool_use。
  • Claude:找复制边界前最近的非 subagent assistant uuid,传 upToMessageId
  • Codex:先 ThreadFork latest,再按 tailTurnsToDrop ThreadRollback 尾部 user turns。
  • 新 session 记录 parentSessionId/forkedAtMessageId,但目前仍作为普通 session 跳转。

side_chat 待落地的是:

  1. 登记成 side activity 对象:补 execution_type='side_chat' 和关系契约。
  2. 挂进统一 pane,而不是现在 navigate(/cc-agent/<newSession.id>) 跳走。
  3. 关系契约分型:默认不回传、可选回传/采纳、记录来源 turn 和产物 provenance。

关系契约

关系不应做成单一 relation_kind 枚举。它应是一组字段,至少包括:

  • execution_typeworker | side_chat | workflow_run
  • lifecycle_kindpersistent | ephemeral,适用于 worker / side_chat。
  • source_session_id
  • source_message_idsource_turn_id
  • initiator:用户 fork、Lead 派单、workflow 触发、系统恢复等。
  • return_to:结果回给哪个 parent / step / 用户动作,或 none。
  • communication_policy:父子单向、父子双向、允许用户接管、允许同级等。
  • artifact_ownership:diff / 文件改动 / 产物默认归谁。
  • provenance:来源、采纳、覆盖、人工介入、预算/门禁结果。

注意 workflow_run 不是扁平父子关系,它有 run → step → runner 的嵌套树;关系表需要能表达这一点。

控制面九大机制

统一 side activity 外壳背后需要九类控制面能力:

  1. 能力矩阵 / UI 降级契约:每种 execution_type × lifecycle_kind × runtime state 下哪些动作可用,例如 send、interrupt、effort、archive、resume、接管、delete。
  2. 通信拓扑:谁能给谁发消息,父子、同级、用户接管分别如何路由。
  3. 结果回传:自动回、人工拉、完成时聚合、只留分支,要与当前粗粒度 auto-bridge 区分。
  4. 产物聚合:多个执行单元的 diff、文件改动、报告、结论如何聚合进主线决策。
  5. 取消 / 中断 / 重试语义:谁有权中断,是否级联;workflow step retry 是新 runner 还是复用 worker。
  6. 质量门禁 / Lead 验收done 只是执行结束,质量裁决是正交轴。不要把 gate_failed 塞进 worker status;应记录 quality verdict / terminal reason / provenance。
  7. 并发与资源治理:soft/hard limit、排队、优先级、压力下回收策略。
  8. 预算闸:per-worker 与 per-team 的 turn/token/cost 上限;soft limit 交给 Lead/用户决策,hard limit 终止并记录原因。
  9. 权限 / 归属 / 一致性修复:谁能接管,child 能否看 parent 上下文,孤儿 step/worker 如何 reconcile,回传失败如何补偿。

分阶段路线

  1. 稳住当前 worker 底座:Codex MCP context 路由已是 fail-closed、worker 控制入口已按 caller 归属校验(坑点 #1/#3 已是不变量);剩余 follow-up 是补 worker bridge role gate(坑点 #4)、持续补回归测试。(坑点 #5 已随诊断工具桥入 cindy_orca 解决;orca_bridge(A)已整体删除。)
  2. 生命周期建模:引入 lifecycle_kind 与 live/dormant,统一 resume helper,默认不自动有损回收。
  3. side activity 关系层:新增关系/side activity 数据结构,兼容现有 parent_session_id/forked_at_message_idorca_teams/orca_workers
  4. side_chat 纳入 pane:复用 PR #107 fork 动作,但创建 side_chat child execution,挂到统一 pane,默认不回传。
  5. 统一控制面:能力矩阵、通信/回传/产物聚合、质量门禁、预算治理。
  6. workflow_run:引入 step 树、runner 策略、step retry/skip/cancel、关键 step 接手成 worker。

待讨论项

  1. 关系层是单表、事件表 + projection,还是按 side activity / workflow_run 拆表。
  2. side_chat 默认展示策略:创建后立即占 worker pane、开新 pane tab、还是进入 side activity 列表待用户选择。
  3. side_chat 结果采纳 UX:回传一段总结、插入 parent composer、生成 diff/provenance review,还是三者都支持。
  4. persistent/ephemeral 默认值:side_chat 默认 persistent 还是 ephemeral。
  5. workflow step 何时自动接手为 worker:失败、超时、高风险文件、用户点名,判据需要收紧。
  6. budget / quality routing 作为 skill+memory 决策,还是主进程 deterministic policy,如何与用户偏好并存。