Orca 的 AI Agent 编排架构:如何管理 35 种 AI 编码助手并行工作
摘要
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。
这个假设带来三个工程挑战:
- 启动统一性:35+ 种 agent 各自有不同的 CLI 接口、启动参数和状态上报方式,IDE 需要一个统一的启动管道
- 状态可观测性:多个 agent 并行运行时,桌面侧边栏、CLI 工具、手机 app、仪表盘等多个读者都需要实时知道每个 agent 的工作状态
- 执行隔离与容错: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 |
|---|---|
| 执行主机不是 local | remote-execution-host |
| 复用已有终端 | reused-terminal |
| Agent 不支持 structured session | agent-without-structured-session |
| 浮动工作区 | floating-workspace |
| 自定义启动命令 | tui-launch-command |
| WSL 运行时 | project-runtime |
| 主机能力未确认 | runtime-capability-unknown |
| 主机不支持 structured | runtime-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。其运行方式是:
- 通过
node-pty在进程外 daemon 中 spawn 一个 PTY(伪终端) - 在 PTY 中执行 agent 的 CLI 命令(如
claude、codex、opencode) - 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)操作和已证实的驱逐才能推进claimStatus:reserved → 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 POST | Agent hook 脚本 | Agent CLI 通过 HTTP 上报状态到 hook server |
| OSC 9999 | PTY 字节流 | 从终端输出中解析结构化状态 JSON |
| SSH/WSL Relay | 远程执行主机 | 远程 agent 的状态通过 relay 通道传递 |
| Structured Session Feed | SDK 通信 | Structured session 的状态直接注入 store |
所有路径最终收敛到 hook server 的 applyNormalizedStatus 方法。写入时确定优先级,读者只做展示——优先级在写入时决定一次,而非读取时每次重新裁定。
5.4 状态消费者
四个读者订阅同一份 snapshot:
- 桌面侧边栏:通过
agentStatus:setIPC 事件实时更新 orca worktree psCLI 命令:读取 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、传输和控制平面状态,但对执行状态没有权威性。
两个不可妥协的推论:
- 禁止静默替代:对远程
repoPath的操作绝不能回退到本地执行。缺失 SSH provider 不是本地回答的许可 - 禁止断言不可观测的事物:失联不等于退出
6.2 三值裁决模型
远程 agent 的存活状态采用三值裁决:
| 裁决 | 含义 | 证据要求 |
|---|---|---|
live | 主机确认进程在运行 | 主机的正面观测 |
unverifiable | 无法确认——可能在跑,也可能已退出 | 联系丢失、命令超时、查询失败 |
exited | 主机确认进程已退出 | 主机提供的退出证据(PID 不存在、身份不匹配、观测到退出事件) |
unverifiable 不等于 exited。 这是整个裁决模型的核心约束。将 unverifiable 误报为 exited 的后果是:孤立仍在运行的工作,并可能在同一个 worktree 上冷启动一个重复的 agent。
代码中的参考实现位于 src/main/runtime/unstopped-pty-verification.ts,它将 live、unverifiable、exited 作为三个独立的裁决值,把"无法询问"作为一种独立的答案。
判断流程遵循严格的证据链:
- 信号是来自执行主机还是客户端自己的簿记?——后者只能产出
unverifiable - 该目标上的所有远程 PTY 是否同时静默?——同时静默指向传输丢失而非同时死亡
- 终止事件是否匹配当前的身份(PTY incarnation、provider generation)?——过期事件不算数
- 答案是否携带了证据?——
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 中找到对应文件。
更多推荐



所有评论(0)