摘要

Orca 是一个开源的下一代 IDE,其核心定位并非自研 AI 模型,而是充当"AI 编排器"——在一个统一的界面中管理多个第三方 AI 编码助手并行工作。本文基于 Orca 开源仓库(MIT 协议)的源码,深入剖析其 Agent 编排架构,涵盖统一启动管道、双模式运行表面(Terminal Agent 与 Structured Session)、集中式状态管理,以及分布式执行场景下的三值裁决模型。


1. 设计背景:为什么需要 Agent 编排

当前 AI 辅助编程领域存在大量 CLI 形态的 coding agent:Anthropic 的 Claude Code、OpenAI 的 Codex、Google 的 Gemini CLI / Antigravity、xAI 的 Grok CLI、社区的 OpenCode、Aider、Goose 等。它们各有擅长的场景和模型特性,但共同的使用方式是"开一个终端,跑一个 agent"。

Orca 的设计假设是:在同一个项目上同时运行多个 agent,各自在隔离的 git worktree 中工作,最终由开发者对比结果、合并最优方案,效率远高于串行使用单个 agent。

这个假设带来三个工程挑战:

  1. 启动统一性:35+ 种 agent 各自有不同的 CLI 接口、启动参数和状态上报方式,IDE 需要一个统一的启动管道
  2. 状态可观测性:多个 agent 并行运行时,桌面侧边栏、CLI 工具、手机 app、仪表盘等多个读者都需要实时知道每个 agent 的工作状态
  3. 执行隔离与容错:agent 可能运行在本地、SSH 远程主机或 WSL 中,断连后工作不应丢失,状态判断不应误报

下面逐一分析 Orca 的解决方案。


2. 支持的 Agent 与类型系统

Orca 在 src/shared/tui-agent.ts 中定义了一个联合类型 TuiAgent,枚举了所有支持的 AI 编码助手:

export type TuiAgent =
  | 'claude'           // Claude Code
  | 'codex'            // OpenAI Codex
  | 'opencode'         // OpenCode
  | 'gemini'           // Gemini CLI
  | 'grok'             // xAI Grok CLI
  | 'antigravity'      // Google Antigravity
  | 'pi'               // Pi
  | 'goose'            // Goose
  | 'amp'              // Amp
  | 'cursor'           // Cursor CLI
  | 'copilot'          // GitHub Copilot CLI
  | 'devin'            // Devin CLI
  | 'kiro'             // Kiro
  // ... 共计 35+ 种

该类型贯穿整个代码栈——启动决策、状态管理、遥测上报、UI 渲染均引用它。新增一个 agent 的成本被控制在两处修改:在 TuiAgent 联合类型中增加一个成员,在 src/shared/agent-kind.ts 的遥测映射表中补一行。后者通过 satisfies Record<TuiAgent, ConcreteAgentKind> 在编译期保证映射的完备性。

这种设计的优点在于:类型系统本身就是 agent 注册表,遗漏一个映射会导致编译失败,而非运行时的静默降级。


3. 统一启动管道

3.1 Intent 驱动的启动模型

Orca 的 agent 启动采用 intent 模式:调用方声明"意图",但不指定运行模式。核心数据结构定义在 src/shared/agent-launch-intent.ts

export type AgentLaunchIntent = {
  agent: TuiAgent                    // 哪种 agent
  target: AgentLaunchTarget          // 在哪个 worktree(已有 / 新建)
  prompt?: AgentLaunchPrompt         // 初始 prompt
  sessionOptions?: Record<string, unknown>  // 会话选项
  reuseTerminal?: { handle: string }        // 复用已有终端
}

AgentLaunchTarget 是一个可辨识联合类型:

export type AgentLaunchTarget =
  | { kind: 'existing'; worktree: string }
  | { kind: 'create-worktree'; create: Record<string, unknown> }

注意,intent 中 没有 mode 字段。调用方(桌面 UI、手机 app、CLI、编排系统)不决定 agent 以"终端"还是"结构化会话"的形式运行。这个决策权属于执行主机。

3.2 两阶段模式决策

启动的唯一入口是 src/main/agent-launch/agent-launch-executor.ts 中的 executeAgentLaunch() 函数。整个流程分为四个阶段:

Phase 1:预判(Pre-flight)

decideAgentLaunchMode() 读取用户偏好设置和已知的约束条件,产出一个初步的模式判断:

const preflight = decideAgentLaunchMode({
  placement: {
    agent: intent.agent,
    ...(intent.reuseTerminal ? { terminal: intent.reuseTerminal.handle } : {})
  },
  settings,
  vocabulary
})

可行性检查位于 src/shared/structured-native-chat-launch-route.ts,按优先级依次排除:

检查项不满足时的 blocker
执行主机不是 localremote-execution-host
复用已有终端reused-terminal
Agent 不支持 structured sessionagent-without-structured-session
浮动工作区floating-workspace
自定义启动命令tui-launch-command
WSL 运行时project-runtime
主机能力未确认runtime-capability-unknown
主机不支持 structuredruntime-capability

当前仅 Claude 和 Codex 通过了 isAgentSessionHandleProvider() 检查,其余 agent 一律降级为终端模式。

Phase 2:创建/定位工作区

若 intent 指定创建新 worktree,此时执行创建。关键设计决策:当预判为 structured 模式时,worktree 不携带 startupAgent

return workspaces.createWorktree({
  create: withoutReservedAgentCreateFields(intent.target.create),
  startupAgent: preflight.mode === 'structured' ? undefined : intent.agent
})

这解决了一个历史问题:早期实现中 worktree 采用"agent-first"创建方式(创建时就启动 agent 终端),导致 structured 分支永远不可达。当前设计将 worktree 创建与 agent 表面创建解耦。

Phase 3:主机确认

调用 resolveAgentLaunchModeOnHost() 向执行主机请求 agentSession.createSupport。只有执行主机能判断当前工作区是否可以托管 structured session(例如 WSL 环境、远程主机等场景只有主机自己知道)。

Phase 4:创建运行表面

根据最终决策创建 structured session 或 terminal agent。若 structured 创建抛出确定性拒绝isDefinitiveAgentSessionCreateRefusal),自动降级为 terminal:

try {
  created = await createSurface(execution, placed.worktreeId, settled)
} catch (error) {
  if (
    settled.mode !== 'structured' ||
    !(error instanceof AgentLaunchStructuredSessionRefusedError) ||
    !isDefinitiveAgentSessionCreateRefusal(error.code)
  ) {
    throw error
  }
  // 降级为 terminal
  settled = downgradeAgentLaunchModeForStructuredRefusal(settled, vocabulary)
  created = await execution.surfaces.createTerminalAgent(...)
}

整个管道的核心保证是:永远不会因为用户的偏好设置导致启动失败。 偏好是"请求"而非"要求",降级是常规路径而非异常路径,且每次降级都会在 receipt 中记录原因,确保降级不是静默的。


4. 双模式运行表面

4.1 Terminal Agent:通过终端交互

Terminal Agent 是最通用的模式,适用于所有 35+ 种 agent。其运行方式是:

  1. 通过 node-pty 在进程外 daemon 中 spawn 一个 PTY(伪终端)
  2. 在 PTY 中执行 agent 的 CLI 命令(如 claudecodexopencode
  3. Agent 在终端中打字输出,用户在终端中输入

状态获取依赖两种机制:

  • Agent Hook:Orca 为每种 agent 安装 hook 脚本,agent 运行时通过 HTTP POST 向 hook server 上报状态。不同 agent 的上报格式不同,hook server 内有针对每种 agent 的归一化逻辑(代码库中有大量 server-*-normalization.test.ts 测试文件)
  • OSC 9999 转义序列:Agent 的 hook 脚本在终端输出中嵌入 \x1b]9999;{json}\x07 格式的控制序列。Orca 的 PTY 数据管道中有一个有状态的 OSC 9999 解析器(src/shared/agent-status-osc.ts),能从终端字节流中实时提取结构化的状态 JSON

Terminal Agent 的优点是通用性——任何能在终端运行的 CLI agent 都可以纳入管理;缺点是 Orca 对 agent 内部状态的感知是间接的,需要依赖 hook 上报和终端输出解析。

4.2 Structured Session:通过 SDK 通信

Structured Session 是一种更高级的模式,当前仅 Claude(通过 @anthropic-ai/claude-agent-sdk)和 Codex 支持。其核心区别在于:

  • 没有 PTY,不经过终端
  • Orca 通过 SDK 直接与 agent 进程建立结构化通信通道
  • 每一轮对话(turn)、每个 tool 调用、每条消息都是明确的数据结构

Structured Session 有一个完整的会话记录系统,定义在 src/shared/agent-session-record.ts

export type AgentSessionRecord = {
  schemaVersion: 2
  location: AgentSessionExecutionLocation  // 执行位置(主机、WSL distro、workspace)
  accountHome: AgentSessionAccountHome     // 账号锁定
  providerHandleChain: AgentSessionProviderHandleLink[]  // 提供者 handle 链
  lease: AgentSessionLease                 // 租约管理
  journalCheckpoint: AgentSessionJournalCheckpoint       // 日志断点
  // ...
}

Lease(租约)机制 是 Structured Session 最复杂的部分。它解决的核心问题是:在多客户端、断连恢复、进程重启等场景下,谁有权向 agent 的 provider 会话写入。Lease 包含:

  • runtimeFence:单调递增整数,只有 CAS(compare-and-swap)操作和已证实的驱逐才能推进
  • claimStatusreserved → live → conflicted → released 状态机
  • ownerProcess:PID + 进程启动时间 + spawnToken,防止 PID 复用误判
  • deathEvidence:进程退出的证据(exit-observed / pid-absent / identity-mismatch)
  • handoffStage:所有权交接的五阶段状态机(preparing → old-owner-stopped → new-owner-proving → recovering → manual-recovery

Provider Handle Chain 记录了会话的身份沿革。Claude 和 Codex 对"什么是会话身份"有不同的理解:

  • Claude:session id 是身份根,leaf uuid 是分支游标
  • Codex:thread id 是完整的标识符

Handle Chain 用链表结构记录 resume(恢复)和 fork(分叉)操作,并通过编译期约束确保"resume 不能伪装成 fork":

if (link.origin === 'resumed' && !sameRoot) {
  throw new Error('agent_session_provider_handle_forked')
}

5. 集中式 Agent 状态管理

5.1 设计原则

Orca 的 agent 状态管理遵循一条核心规则:

执行主机拥有 agent 状态,在一个 store 中,所有读者订阅它。

在此之前的架构审计(2026-09-09,记录于 docs/reference/agent-status-store.md)发现了严重的状态分裂问题:主进程内存在三份相同数据的副本,六个生产者和三个消费者各自维护优先级和新鲜度规则,导致同一个 agent 在桌面、手机和 CLI 上可能显示不同的状态。

5.2 Status Store 的实现

src/shared/agent-status-store.ts 实现了一个 authority/replica 双模式 store

export type AgentStatusStoreMode = 'authority' | 'replica'

export type AgentStatusStore = {
  getParent(subject: AgentStatusSubject): AgentStatusParentRecord | null
  getChildren(subject: AgentStatusSubject): AgentChildWorkRecord[]
  getSnapshot(): AgentStatusStoreSnapshot
  applyMutation(mutation: unknown): AgentStatusMutationEnvelope | null
  applySnapshot(snapshot: unknown): boolean
  applyTransportEnvelope(envelope: unknown): boolean
}
  • Authority 模式:运行在执行主机上。接受 mutation(变更),递增 revision 号,产出 AgentStatusMutationEnvelope 供 replica 消费
  • Replica 模式:运行在客户端(桌面 UI、手机 app)上。通过 applyTransportEnvelope 接收 snapshot 或连续的 mutation envelope 来镜像 authority 的状态

状态词汇固定为四值:

export const AGENT_STATUS_STATES = ['working', 'blocked', 'waiting', 'done'] as const

Structured Session 内部使用 working / attention / idle 词汇,在写入 store 时统一映射。

5.3 状态生产者

状态通过四条路径汇入同一个 store:

路径来源说明
Hook HTTP POSTAgent hook 脚本Agent CLI 通过 HTTP 上报状态到 hook server
OSC 9999PTY 字节流从终端输出中解析结构化状态 JSON
SSH/WSL Relay远程执行主机远程 agent 的状态通过 relay 通道传递
Structured Session FeedSDK 通信Structured session 的状态直接注入 store

所有路径最终收敛到 hook server 的 applyNormalizedStatus 方法。写入时确定优先级,读者只做展示——优先级在写入时决定一次,而非读取时每次重新裁定。

5.4 状态消费者

四个读者订阅同一份 snapshot:

  • 桌面侧边栏:通过 agentStatus:set IPC 事件实时更新
  • orca worktree ps CLI 命令:读取 hook server 的快照
  • 手机 Companion App:通过 relay 接收镜像的状态
  • Agent Dashboard:通过 web session 订阅

5.5 持久化与恢复

Hook server 将状态持久化到 last-status.json,设有 7 天的水合(hydration)窗口。恢复的行会被标记为 restoredUnconfirmed永远不会被视为活跃的真相——只有活跃的状态上报才能刷新它。

Structured Session 的行不参与持久化。journal 本身是 structured session 的持久化真相;一个 structured session 恢复后会重新发布状态到 store,而非从 last-status.json 中读取。序列化器会跳过携带 structuredHost 标记的行,水合时若在磁盘上发现此类行也会丢弃。


6. 分布式执行与三值裁决

6.1 SSH 执行边界

Agent 不一定运行在本地。Orca 支持通过 SSH 在远程主机上运行 agent,此时 PTY 是远程 relay daemon 的子进程,而非 SSH channel 的子进程,因此断连后 agent 工作继续运行。

docs/reference/ssh-execution-boundary.md 定义了一条核心规则:

执行主机拥有一切与执行相关的事物——工具、凭证、身份、环境、进程和产物。客户端拥有 UI、传输和控制平面状态,但对执行状态没有权威性。

两个不可妥协的推论:

  1. 禁止静默替代:对远程 repoPath 的操作绝不能回退到本地执行。缺失 SSH provider 不是本地回答的许可
  2. 禁止断言不可观测的事物:失联不等于退出

6.2 三值裁决模型

远程 agent 的存活状态采用三值裁决:

裁决含义证据要求
live主机确认进程在运行主机的正面观测
unverifiable无法确认——可能在跑,也可能已退出联系丢失、命令超时、查询失败
exited主机确认进程已退出主机提供的退出证据(PID 不存在、身份不匹配、观测到退出事件)

unverifiable 不等于 exited 这是整个裁决模型的核心约束。将 unverifiable 误报为 exited 的后果是:孤立仍在运行的工作,并可能在同一个 worktree 上冷启动一个重复的 agent。

代码中的参考实现位于 src/main/runtime/unstopped-pty-verification.ts,它将 liveunverifiableexited 作为三个独立的裁决值,把"无法询问"作为一种独立的答案。

判断流程遵循严格的证据链:

  1. 信号是来自执行主机还是客户端自己的簿记?——后者只能产出 unverifiable
  2. 该目标上的所有远程 PTY 是否同时静默?——同时静默指向传输丢失而非同时死亡
  3. 终止事件是否匹配当前的身份(PTY incarnation、provider generation)?——过期事件不算数
  4. 答案是否携带了证据?——pty.attach 对"relay 探测发现进程已退出"和"session map 中不存在该 id"返回相同的拒绝消息,只有前者携带 PTY_ATTACH_PROVEN_EXITED_MARKER

7. Worktree 隔离

Orca 的"并行 agent"能力建立在 git worktree 之上。每个 agent 在独立的 worktree 中工作:

  • 同一个仓库的多个 worktree 共享 .git 目录,但各自有独立的工作目录和索引
  • Agent 的文件修改、分支创建、提交操作都限定在自己的 worktree 内
  • 用户可以"将同一个 prompt 分发给五个 agent,各自在隔离的 worktree 中工作,对比结果后合并最优方案"

Worktree 的创建与 agent 启动是解耦的。AgentLaunchTarget 中的 create-worktree 分支通过注入的 AgentLaunchWorkspaceFactory 执行创建,创建时会从 intent 中剥离所有 agent 相关字段(withoutReservedAgentCreateFields),确保 worktree 的创建是中性的,不预设任何 agent。


8. 总结

Orca 的 Agent 编排架构可以归纳为以下设计原则:

原则体现
Intent 驱动,主机决策调用方声明意图而非模式;执行主机根据环境做最终决定
降级即正常路径Structured → Terminal 的降级不是异常处理,而是启动管道的常规分支
单一权威 store状态写入时决定优先级,读者只做展示,消除多副本漂移
三值裁决live / unverifiable / exited 分离证据的缺失与事件的缺失
类型系统即注册表TuiAgent 联合类型 + satisfies 约束确保新 agent 的添加是编译期安全的

这套架构使 Orca 能在不绑定任何特定 AI 模型的前提下,统一管理 35+ 种异构的编码助手,并在本地、SSH、WSL 等多种执行环境中保持一致的可观测性和容错能力。对于正在设计多 agent 协调系统的工程团队,其 intent 驱动的启动模型和 authority/replica 状态同步机制均有较高的参考价值。


本文基于 Orca 开源仓库(MIT 协议)v1.4.197 版本源码分析。文中所有代码引用均可在 https://github.com/stablyai/orca 中找到对应文件。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐