SDK / ACP / Daemon 架构 Deep-Dive

May 17, 2026 · View on GitHub

对比 OpenCode、Qwen Code、Codex、Claude Code 在 SDK、ACP(Agent Client Protocol)、HTTP daemon 三个维度的设计差异。基于 2026-05-01 各项目本地源码分析。

配套文章:

一、为什么这个话题重要

CLI Agent 不只是给人在终端敲命令用的工具——当它要被嵌入 IDE 插件、被 Python 数据流水线调度、被 web UI 展示、被 CI 脚本批量驱动时,程序化访问接口的设计直接决定了集成成本。

四款主流 Code Agent 在这个问题上做出了根本不同的选择:

产品程序化访问主入口
Claude Code自家 NDJSON 双向控制 SDK(TS + Python)
Qwen Code三语言 SDK(TS + Python + Java)+ ACP 双向(agent + client)
Codex自家 JSON-RPC app-server-protocol(TS + Python SDK)
OpenCodeHTTP daemon + OpenAPI codegen 客户端 + ACP agent

差别不在表面包装,而在进程模型与状态共享语义——本文剖开来讲。

二、4 种通信架构

Subprocess 模型(Claude / Qwen / Codex)

┌──────────┐  spawn   ┌─────────────────────────────┐
│ SDK 客户端 │─────────▶│ CLI 子进程(per-query/per-Client)│
└──────────┘  stdio   │ ├─ stdin/stdout NDJSON      │
                      │ │  或 JSON-RPC              │
                      │ └─ core (in-process 库调用) │
                      └─────────────────────────────┘
每个 query() / Client 实例 = 1 个 CLI 子进程;query 内多轮工具调用复用同一进程

特征:粒度是 per-query 或 per-Client——同一个 query() 流式返回中的多轮工具调用复用同一子进程;但不同 query() 调用各自 spawn 新进程。状态隔离强、每次新会话付启动开销。

Daemon 模型(OpenCode 独家)

┌──────────┐  HTTP/WS ┌─────────────────────────────┐
│ SDK 客户端 │◀────────▶│ daemon 进程(长生命)         │
└──────────┘          │ ├─ Hono / Bun.serve         │
                      │ ├─ Instance / Session Map   │
                      │ └─ core (in-process 库调用) │
                      └─────────────────────────────┘
1 次 spawn daemon → N 个 client 永久 HTTP 复用

特征:daemon 启动一次后,所有客户端 HTTP/WebSocket 调用都在同一进程内 in-process 函数调用 core。状态共享强、启动开销摊销。

ACP 模型(Qwen / OpenCode 支持)

┌──────┐  spawn   ┌─────────────────────────────┐
│ IDE  │─────────▶│ Agent 进程 = CLI 进程         │
└──────┘  stdio   │ ├─ NDJSON (Zed ACP 协议)     │
                  │ └─ core (in-process 库调用) │
                  └─────────────────────────────┘
IDE 启动时 spawn 一次,长连接驱动 agent

特征:是 subprocess 模型的"长连接版"——IDE 拉起 agent 一次,通过 stdio NDJSON 持续通信直到 IDE 关闭。

三、SDK 矩阵详细对比

3.1 语言覆盖

维度Claude CodeQwen CodeCodexOpenCode
TypeScript SDK 包@anthropic-ai/claude-agent-sdk@qwen-code/sdk v0.1.7@openai/codex-sdk v0.0.0-dev@opencode-ai/sdk
Python SDK 包claude-agent-sdkqwen-code-sdk v0.1.0aopenai-codex-app-server-sdk v0.116.0a1 + openai-codex-cli-bin runtime❌ 无
Java SDK 包❌ 无com.alibaba:qwencode-sdk v0.0.3-α + com.alibaba:acp-sdk v0.0.1-α❌ 无❌ 无
生成方式手写 + 完整双向控制 schema手写 + Transport 抽象Pydantic(Python)/ 手写(TS)+ Rust 协议层 codegenOpenAPI 13525 行 codegen
底层传输subprocess + NDJSON stdin/stdoutsubprocess(ProcessTransport)+ NDJSONsubprocess JSON-RPCHTTP REST(自动从 OpenAPI 生成)

3.2 各 SDK 的内部"形状"

Claude Code——双向控制最完整:

// 核心: entrypoints/sdk/controlSchemas.ts 定义 20+ 控制结构
const q = query({
  prompt,
  canUseTool: async (req) => userApprovesIt(req),  // 工具审批回调
})
q.setModel('haiku')        // 运行中切模型
q.seedReadState([...])     // 把 IDE 已打开文件灌入 fileReadCache
q.interrupt()              // 注入软中断

Qwen Code——架构对齐 Claude,控制语义在追赶:

// 源码: packages/sdk-typescript/src/index.ts
import { query, tool, createSdkMcpServer } from '@qwen-code/sdk'

// 已暴露类型: CLIControlRequest / CLIControlResponse / ControlCancelRequest
// Transport 接口预留 HttpTransport / WebSocketTransport(packages/sdk-typescript/src/transport/Transport.ts:5-7)

Codex——最厚的协议层,最薄的语言层:

# 源码: sdk/python/src/codex_app_server/client.py
from codex_app_server import Client
# 协议在 Rust 端 17414 行(v1: 245 行 + v2: 10885 行)
# Python/TS SDK 都是 protocol/v2.rs 的 codegen
client = Client.connect()  # subprocess 拉起 openai-codex-cli-bin

OpenCode——SDK 只是 OpenAPI HTTP 壳子:

// 源码: packages/sdk/openapi.json 13525 行
import { createOpencodeServer } from '@opencode-ai/sdk'
// 1. 自动 spawn `opencode serve` daemon
// 2. SDK 实例就是 OpenAPI 客户端,所有方法都是 HTTP 调用
const server = await createOpencodeServer()
const client = new OpencodeClient({ baseUrl: server.url })

3.3 Qwen Java SDK 的特殊地位

Qwen Code 的 sdk-java/ 拆为两个独立 Maven 包:

Maven 坐标用途
com.alibaba:acp-sdk v0.0.1-α独立的 Java ACP 协议实现库(不限于 Qwen Code)
com.alibaba:qwencode-sdk v0.0.3-αQwen Code 的 Java 客户端,依赖 acp-sdk

意义:4 款产品中唯一为 ACP 协议提供 Java 生态实现——这意味着任何 ACP 兼容的 agent(含 OpenCode)都可以被 Java 应用集成,而不只是为 Qwen 服务。

四、ACP(Agent Client Protocol)支持

🆕 2026-05-17 更新:本节作为 ACP 维度的全景对比,已被独立短稿 ACP 支持 Deep-Dive 焦点化展开,含:方法逐项对照(Qwen 10 vs OpenCode 13)/ 库版本差距(0.14.1 vs 0.21.0)/ Qwen 独家 httpAcpBridge 2802 LOC daemon HTTP↔ACP 桥接 / IDE 端 UX 评分矩阵 / Qwen 借鉴清单。本节保留 2026-05-01 时点数据作为历史快照。

ACP 是 Zed 团队推动的 IDE↔Agent 标准协议,依赖 npm 包 @agentclientprotocol/sdk。

4.1 各家立场

角色Claude CodeQwen CodeCodexOpenCode
作为 ACP Agent(被 IDE 拉起)❌✅ packages/cli/src/acp-integration/acpAgent.ts(838 行 · @agentclientprotocol/sdk@^0.14.1)❌✅ packages/opencode/src/acp/agent.ts
作为 ACP Client(驱动其他 Agent)❌✅ packages/vscode-ide-companion/src/services/acpConnection.ts❌❌
Java ACP 实现❌✅ acp-sdk 独立可复用库❌❌
Zed 编辑器扩展❌(用自家 IDE 扩展)✅ packages/zed-extension/❌❌
VSCode 集成方式私有协议vscode-ide-companion 走 ACP❌(仅 CLI)/sdks/vscode/ 单独包

4.2 三个观察

  1. Qwen Code 是 4 者中 ACP 投入最重的——同时做 ACP agent + ACP client + Zed 扩展 + Java ACP 库。这是基于"开放协议生态优于自研"的判断。

  2. Claude Code 与 Codex 都选了"自家协议"——Anthropic 用 NDJSON 双向控制,OpenAI 用 JSON-RPC app-server-protocol(17K 行 schema)。头部厂商不愿被外部协议 schema 约束 control plane 设计。

  3. Codex 用 MCP 替代 ACP——Codex 自身可作为 MCP server 暴露能力(codex-mcp / mcp-server crates)。MCP 是"工具调用协议"(窄),ACP 是"会话协议"(全)。Codex 选 MCP 反映它把自己定位为"被其他 LLM agent 调用的工具",而非"被 IDE 嵌入的会话"。

4.3 控制语义对比

能力Claude CodeQwen CodeCodexOpenCode
工具审批回调✅ NDJSON control_request✅(ACP permission_request)✅(JSON-RPC request_user_input)✅(ACP permission_request)
运行中切模型✅ set_model🟡 部分(SetSessionModelRequest)✅🟡
注入预读缓存✅ seed_read_state❌❌❌
软中断✅ interrupt✅ ACP cancel✅ JSON-RPC cancel✅
多 session 管理✅✅(LoadSessionRequest/ForkSessionRequest)✅(thread_state)✅
Fork session✅✅ ACP✅✅ ACP

Claude Code 独家的 seed_read_state:SDK 宿主可以把"用户在 IDE 已打开的文件"直接灌入引擎的 fileReadCache,避免重复读取。这是最贴 IDE 工作流的设计——Qwen Code 在 PR#3717 加了 FileReadCache 基础设施,理论上可以补齐这个 API。

五、OpenCode HTTP daemon 模式深度剖析

5.1 启动方式

opencode serve  # 启动 headless HTTP server

源码 packages/opencode/src/cli/cmd/serve.ts:

const opts = await resolveNetworkOptions(args)
const server = await Server.listen(opts)
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
await new Promise(() => {})  // 永久阻塞作为 daemon

仅 ~20 行代码。serve 子命令是 opencode 同一二进制的一种运行模式——daemon 不是独立 binary。

5.2 技术栈

组件实现
HTTP 框架Hono(packages/opencode/src/server/server.ts)+ hono-openapi 自动生成 spec
运行时Bun 优先(adapter.bun.ts:Bun.serve),Node 回退(adapter.node.ts)
默认端口4096(占用则随机分配)
服务发现mDNS Bonjour(mdns.ts)—— 局域网内自动广播 opencode-{port}._http._tcp.local
鉴权OPENCODE_SERVER_PASSWORD env;不设则警告 unsecured
WebSocketBun.serve 内建 + createBunWebSocket —— 长连接事件流推送
OpenAPIgenerateSpecs(app) 运行时生成 spec,SDK 直接 codegen

5.3 路由层级

/global          → 全局元信息(providers / installation / version)
/control         → 控制面(多 workspace 调度)
/                → workspace router → instance routes(核心 API)
  ├─ /session/*       (创建/列表/fork/resume/rewind/...)
  ├─ /file/*          (读写)
  ├─ /pty/*           (终端,WebSocket 升级)
  ├─ /tui/*           (TUI 客户端连接点)
  ├─ /provider/*      (LLM provider 管理)
  ├─ /mcp/*           (MCP server 管理)
  ├─ /permission/*    (审批流)
  ├─ /event/*         (事件订阅 SSE/WS)
  └─ ...
/ui              → 内嵌 web UI
+ /experimental/workspace
+ Flag.OPENCODE_EXPERIMENTAL_HTTPAPI 切到独立 HttpApi 实现

中间件链:ErrorMiddleware → AuthMiddleware → LoggerMiddleware → CompressionMiddleware → CorsMiddleware → FenceMiddleware

5.4 SDK 与 daemon 的连接方式

@opencode-ai/sdk 的 createOpencodeServer()(packages/sdk/js/src/server.ts):

const args = [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`]
const proc = launch(`opencode`, args, { env: {...} })

链路:

SDK 客户端
  │
  │  cross-spawn `opencode serve` 一次(启动 daemon)
  ▼
opencode serve 进程(daemon)
  │
  │  HTTP/WebSocket(之后所有调用走此通道)
  ▼
SDK OpenAPI codegen 客户端调用 daemon REST API

5.5 daemon 内部不再 spawn CLI

源码证据(packages/opencode/src/server/routes/instance/index.ts):

import { Instance } from "@/project/instance"
import { Agent } from "@/agent/agent"
import { Skill } from "@/skill"
import { Global } from "@opencode-ai/core/global"
// ... 全部是 import,不是 spawn

.post("/instance/dispose", ..., async (c) => {
  await Instance.dispose()  // ← 直接函数调用 core
  return c.json(true)
})

整个 server/routes/ 目录搜不到任何 spawn CLI 子进程的代码——core 是通过 import 加载到 daemon 进程内的库。

六、多 session 进程模型

6.1 OpenCode:所有 session 共进程

源码 packages/opencode/src/project/instance.ts:

const cache = new Map<string, Promise<InstanceContext>>()  // ← 内存级 instance 缓存

export const Instance = {
  async provide<R>(input: { directory: string; ... }): Promise<R> {
    const directory = AppFileSystem.resolve(input.directory)
    let existing = cache.get(directory)  // 同 directory 复用,不 spawn
    ...
  }
}

N 个 session × M 个 workspace = 永远是 1 个 daemon 进程。

6.2 进程模型层级

opencode serve daemon 进程(承载 N 个 session 的主进程)
│
├─ Hono HTTP server
├─ 全局状态:providers / config / GlobalBus / Database (SQLite)
│
├─ Instance Map(按 project directory 缓存)
│   ├─ Instance A(项目 /work/repo-a)
│   │   ├─ Session a1(LLM 上下文 + transcript)  ── in-memory
│   │   ├─ Session a2 ── in-memory
│   │   ├─ LSP server(spawn 一次)              ← 子进程,跨 session 共享
│   │   ├─ MCP server(spawn 一次)              ← 子进程,跨 session 共享
│   │   └─ Shell PTY(按需 spawn)               ← 子进程,工具调用粒度
│   │
│   └─ Instance B(项目 /work/repo-b)
│       └─ ...
│
└─ 上述所有 session 共享:Provider / Auth / Bus / Database / Hono runtime

6.3 颗粒度:何时 spawn 子进程

颗粒度是否 spawn 子进程说明
新加 session❌ 永不纯数据结构 + Effect Context
新加 workspace(instance)❌ 永不加到 cache Map 里
LLM 调用❌ 永不HTTP fetch from daemon
LSP 服务器✅ 每 instance 一次同 workspace 内 session 共享
MCP 服务器✅ 每 MCP 配置一次跨 session 共享
Shell / Bash 工具调用✅ 每次工具调用PTY 子进程,工具结束就回收
opencode tui 客户端✅ 是 daemon 的 client不是 daemon 内部进程

6.4 工作目录隔离:AsyncLocalStorage 上下文传播

多 session 共享 daemon 时一个核心问题:两个 session 在不同 workspace 工作,daemon 的 process.cwd() 应该是哪个?

OpenCode 的答案:daemon 的 process.cwd() 永远不变(启动时定的),用 Node.js AsyncLocalStorage 给每个请求绑定一个"虚拟 cwd"Instance.directory。

客户端如何声明 cwd

每个 HTTP 请求 3 种方式选其一(源码 server/routes/instance/middleware.ts:11-12):

const raw = c.req.query("directory")              // 1. URL query: ?directory=/path
         || c.req.header("x-opencode-directory")  // 2. HTTP header
         || process.cwd()                         // 3. 兜底用 daemon 启动 cwd

每个 API 调用都可以独立选不同的 directory,无需重启 daemon 或切换 session。

Middleware → Instance.provide → AsyncLocalStorage

// InstanceMiddleware(每请求)
const directory = AppFileSystem.resolve(...raw)
return Instance.provide({
  directory,
  async fn() { return next() }   // ← next() 内整条 async 链都"看见"这个 directory
})

// util/local-context.ts —— LocalContext 实现
import { AsyncLocalStorage } from "async_hooks"
export function create<T>(name: string) {
  const storage = new AsyncLocalStorage<T>()
  return {
    use() { return storage.getStore() },
    provide<R>(value: T, fn: () => R) {
      return storage.run(value, fn)   // ← Node 标准 API,跨 await 自动传播
    },
  }
}

AsyncLocalStorage.run(ctx, fn) 是 Node.js 18+ 的官方"按异步上下文传播变量"API——同进程内并发的多个请求各自携带各自的 InstanceContext,跨 await 不会污染。

所有代码读 Instance.directory,不读 process.cwd()

用途实际写法源码位置
File watcher 监听目录Instance.directoryfile/watcher.ts:79,125
Bash 工具默认 cwdInstance.directorytool/bash.ts:592-593
Bash 工具 workdir 参数覆盖resolvePath(params.workdir, Instance.directory, shell)tool/bash.ts:592
权限边界检查Instance.containsPath(path)(基于 Instance.directory + worktree)project/instance.ts:90-99

Instance.directory 的 getter 实质是 context.use().directory —— 读的就是 AsyncLocalStorage 中当前请求的 value。

daemon 自己从不 chdir

grep -rE "process\\.chdir" packages/opencode/src 全仓只 3 处命中,全在非 daemon 的 CLI 模式(run.ts / tui/attach.ts / tui/thread.ts)。opencode serve daemon 路径下 0 处 chdir——这是设计上必须保证的:daemon 改 cwd 会让所有并发请求互相污染。

子进程 spawn 时显式传 cwd

LSP server / Shell 命令 spawn 时,daemon 显式把目录作为参数传给子进程,不让它继承自 daemon 的 process.cwd():

// tool/bash.ts
const cwd = params.workdir
  ? yield* resolvePath(params.workdir, Instance.directory, shell)
  : Instance.directory
spawn(cmd, args, { cwd })   // ← OS 级 spawn 显式传 cwd

4 个典型场景

场景行为
两个 session 在不同 workspacecache 各 1 entry,AsyncLocalStorage 各持自己的 Instance.directory
两个 session 在同一 workspacecache.get(directory) 命中,复用同一个 InstanceContext + LSP/MCP——更高效
同 session 在请求间切目录每个请求独立走 Instance.provide,支持"探索性"跨 workspace
Bash 工具内带 workdir 参数在 Instance.directory 基础上 resolve 相对路径,spawn 时显式传 OS 级 cwd

与 SDK subprocess 模型对比

维度OpenCode daemonQwen/Claude/Codex SDK
客户端怎么指定 cwdHTTP ?directory= / headerquery({ cwd }) 参数
进程的 process.cwd()永不改变(共享 daemon)子进程 spawn 时设定一次
多 cwd 并发隔离AsyncLocalStorage 应用层隔离OS 进程隔离
cwd 切换成本~0(Map 查表)重新 spawn 子进程

6.5 设计取舍

维度OpenCode daemonQwen/Claude/Codex SDK subprocess
Session 是否独立进程❌ N 个 session 共享 daemon✅ 每个 query/Client 独立进程
跨 session 状态共享✅ 简单(直接 Map)❌ 需要文件 IPC
一个 session 崩了影响其他✅ 会(同进程)❌ 不会(隔离进程)
启动开销摊销✅ 一次(daemon 拉起后)❌ 每个 query 都付
MCP server 复用✅ daemon 寿命内(多 query 共享)❌ Client 内多轮共享,跨 Client 重启
用户感受冷启动仅第一次每个新 Client/query
OOM/内存隔离弱(应用层 Effect Context)强(OS 进程隔离)
资源效率高(共享 model registry / db)低(每个新进程重新加载)
工作目录隔离AsyncLocalStorage 应用层OS 进程级

OpenCode 用 Effect-TS 的 LocalContext / Context.Service(基于 Node.js AsyncLocalStorage)强制依赖注入,避免 module-level 全局可变状态——这是它敢做共进程多 session 的工程基础。Instance.dispose() 提供显式资源回收的逃生口。所有关键状态持久化到 SQLite(session.sql.ts:SessionTable / SessionEntryTable),进程崩溃后重启可恢复。

七、Qwen Code 引入 daemon 的工作量评估

7.1 可直接复用的现成资产

资产位置价值
Express HTTP server 模板packages/vscode-ide-companion/src/ide-server.ts已用 express + cors + auth token 起 HTTP 服务(给 IDE 用),代码可参考
Channels 多路由基础设施packages/channels/base/SessionRouter / PairingStore / SenderGate / GroupGate / BlockStreamer / AcpBridge —— 原本服务于 Telegram/微信/钉钉 channels 的多用户多 session 设施
ACP agent(838 行)packages/cli/src/acp-integration/acpAgent.tsNDJSON 协议、session 生命周期、permission 流、authentication、cancel 全齐 —— 协议层 ~100% 可复用,只需把 stdio 替成 HTTP/WS
SessionServicepackages/core/src/...JSONL session 持久化、resume、fork、rewind、rebuild from transcript(PR#3739 刚加)
WebUI 包packages/webui/Vite + React + Tailwind,已有 web 客户端
SDK Transport 抽象packages/sdk-typescript/src/transport/Transport.ts:5-7代码注释明确写了 HttpTransport: Remote CLI via HTTP (future) + WebSocketTransport——是规划内事项
Background shell poolPR#3642 已合并多任务调度抽象,daemon 模式可直接扩展为多 client 任务队列

7.2 主要缺口与新增成本

缺口工作量说明
qwen serve CLI 命令0.5 天加个 cmd 入口(参考 OpenCode serve.ts 仅 ~20 行)
HTTP server 选型 + 路由2-3 天推荐 Hono(与 OpenCode 一致 + Bun 友好),或继续 express
ACP agent → 多 client 改造3-5 天当前 ACP 是单 stdio session 模型,改成 per-request session ID 路由
HTTP API schema(zod / OpenAPI)2-3 天复用 ACP 的 zod schema
SSE 或 WebSocket 流式事件2-3 天core 已有 event emitters,只需桥接
鉴权(bearer token + env)1 天完全照搬 OpenCode 思路
HttpTransport(SDK 端)2-3 天已留好接口,按 ProcessTransport 镜像写
daemon 生命周期 + pid file1-2 天graceful shutdown / SIGTERM / health check
MVP 合计~2-3 周(1 人)
多 workspace 路由5-7 天当前是 single-workspace,需要加 workspace ID 中间件
mDNS 服务发现1 天直接 bonjour-service(OpenCode 同款)
OpenAPI codegen3-5 天跟 OpenCode 一样 generateSpecs(app)
WebUI 接入 daemon5-7 天改 webui 数据源从 ipc 改 HTTP
文档 + 测试 + 例子5-7 天
对标 OpenCode 合计~1.5-2 个月(1-2 人)

7.3 真正的难点(架构决策)

决策点选项 A选项 B影响
Session 共享语义多 client 严格隔离Session 可跨 client 共享(手机→桌面续行)影响 SessionService 锁/可见性
状态进程模型1 daemon 承载所有 sessiondaemon 路由到子进程后者对 OOM 隔离友好但启动慢
MCP server 生命周期per-session 启动daemon 内 pool 跨 session 复用后者高效但有状态泄漏风险
FileReadCache 共享每 session 独立整个 daemon 共享(dev,ino) key 设计天然支持共享,但语义需要确认
Permission flow信任 daemon 内决策每 client 单独审批 UIPR#3723 刚把三模式合一,daemon 是第 4 种
多 client 并发请求串行并行后者是 daemon 真正价值但需共享资源加锁

7.4 推荐渐进路径

Stage 1(~1 周):实验性 --http-bridge flag
  ├─ 在现有 ACP agent 外加 HTTP→stdio 桥接(最小改动)
  ├─ 1 个 daemon 进程 = 1 个 ACP session(多 client 排队)
  └─ 让用户先用起来探索需求

Stage 2(~2-3 周):原生 qwen serve 多 session
  ├─ 重写 ACP agent 为多 session router
  ├─ 加 SDK HttpTransport 实现
  ├─ Web UI 接入
  └─ MVP 可用

Stage 3(~1-2 月):对标 OpenCode 完整设计
  ├─ Workspace routing
  ├─ mDNS + OpenAPI
  ├─ WebSocket 流式
  ├─ 集群部署文档
  └─ 企业鉴权

7.5 旁证:近期 PR 是否在为 daemon 做准备?

最近一周连续合并的几个 PR 在消除 session 共享/恢复/权限的障碍:

  • PR#3739(2026-05-01 +4087/-165)Add background agent resume and continuation —— BackgroundAgentResumeService + transcript-first fork resume
  • PR#3717(2026-04-30)feat(core): add FileReadCache and short-circuit unchanged Reads —— session-scoped 缓存基础设施
  • PR#3723(2026-04-30)feat(core): add shared permission flow for tool execution unification —— Interactive / Non-Interactive / ACP 三模式权限决策合一
  • PR#3642(2026-04-28)feat(core): managed background shell pool with /tasks command —— 跨 session 任务调度

这些恰好是 daemon 模式必须解决的前置问题。仅依据近期 PR 主题推测,Qwen 团队心里大概率有 daemon 路线图。

八、典型场景的最佳选择

场景最佳选原因
Python 数据科学 / ML 流水线集成Claude / Qwen / Codex三者都有 Python SDK;Codex 的 pydantic 模型对类型严格的工程师更友好
Java 后端服务集成Qwen Code 唯一选项唯一提供 Java SDK + Java ACP 实现
Zed 编辑器内嵌 AgentQwen / OpenCode两者都做 ACP agent;Qwen 还自带 zed-extension
VSCode 插件双向集成(自定义审批 UI)Claude / Qwen / OpenCodeClaude NDJSON / Qwen ACP / OpenCode ACP 都可承担
把 Agent 当 MCP 工具被另一个 LLM 调用Codex唯一原生暴露 codex-mcp server 接口
Browser/Web 调度多 Agent 实例OpenCodeREST API 设计天然适合 HTTP 调度
强双向控制(中途换模型 / 注入缓存)Claude Codeset_model / seed_read_state 是独家
HTTP/JSON-RPC 直连无需 SDKCodex / OpenCodeCodex app-server JSON-RPC / OpenCode REST
长跑 daemon 节省启动开销OpenCode 唯一原生支持其他三家每个新 query/Client 要 spawn 一次
CI 脚本一次性批量任务任一subprocess 模型反而更简单(一次性进程更干净)

九、四种架构哲学的总结

产品设计哲学体现
Claude Code深度 + 私有协议20+ NDJSON 控制原语让宿主像调试器一样精细操控引擎,不入 Zed/外部 ACP 生态
Qwen Code广度 + 兼容三语言 SDK + 双向 ACP + Java ACP 库,集成口最多,深控制语义在追赶
Codex协议 first,SDK 是壳17K 行 Rust JSON-RPC 协议 + MCP 双暴露,自定位"被调用的工具/服务"
OpenCodeREST/daemon firstOpenAPI codegen + 长跑 daemon 进程,多 client 共享 core 状态

十、关键洞察清单

  1. 没有任何 SDK/ACP 实现是直接 link core 库到客户端进程——core 永远在另一个进程里,差别只在那个进程是 daemon 还是 short-lived subprocess。

  2. OpenCode daemon 内部不再 spawn CLI——daemon 就是 CLI 二进制以 serve 模式运行,core 通过 import 加载到 daemon 进程内。

  3. Qwen Code 是 4 者中 ACP 投入最重的——同时做 ACP agent + ACP client + Zed 扩展 + Java ACP 库。

  4. Claude Code 与 Codex 都选了"自家协议"——头部厂商不愿被外部协议 schema 约束 control plane 设计。

  5. Codex 用 MCP 替代 ACP 的角色——把自己定位为"被调用的工具",而非"被嵌入的会话"。

  6. OpenCode 多 session 共享 1 个 daemon 进程,Map<directory, InstanceContext> 缓存 + Effect-TS Context 做应用层隔离 + SQLite 做持久化。子进程仅限 LSP / MCP / PTY 等"外部进程依赖"。

  7. 不同 session 工作目录隔离靠 AsyncLocalStorage,daemon 进程的 process.cwd() 永不改变——客户端 HTTP 请求带 ?directory= 参数,middleware 把目录绑入 async-context,所有代码读 Instance.directory 而非 process.cwd(),子进程 spawn 时显式传 cwd。这是 Node.js 写多租户长跑服务的标准范式。

  8. Qwen Code 加 daemon 的工作量约 2-3 周(MVP)/ 1.5-2 月(对标 OpenCode)——因为 ACP agent + Channels + WebUI + SDK Transport 抽象都已就绪。

  9. 真正的难点是几个架构决策——session 共享语义、状态进程模型、MCP server 生命周期、并发语义——而不是代码量。

十一、源码证据索引

主题文件路径
OpenCode serve 命令packages/opencode/src/cli/cmd/serve.ts
OpenCode HTTP server 主入口packages/opencode/src/server/server.ts
OpenCode Bun adapterpackages/opencode/src/server/adapter.bun.ts
OpenCode mDNSpackages/opencode/src/server/mdns.ts
OpenCode Instance Map(多 session 共进程证据)packages/opencode/src/project/instance.ts
OpenCode InstanceMiddleware(cwd 路由)packages/opencode/src/server/routes/instance/middleware.ts:11-12
OpenCode AsyncLocalStorage 封装packages/opencode/src/util/local-context.ts
OpenCode Bash 工具 cwd 处理packages/opencode/src/tool/bash.ts:592-593
OpenCode Instance routespackages/opencode/src/server/routes/instance/index.ts
OpenCode SDK server 启动器packages/sdk/js/src/server.ts
OpenCode ACP agentpackages/opencode/src/acp/agent.ts
OpenCode session SQLite schemapackages/opencode/src/session/session.sql.ts
Qwen Code ACP agent(838 行)packages/cli/src/acp-integration/acpAgent.ts
Qwen Code SDK Transport 接口(注释预告 HTTP/WS)packages/sdk-typescript/src/transport/Transport.ts:5-7
Qwen Code SDK ProcessTransportpackages/sdk-typescript/src/transport/ProcessTransport.ts
Qwen Code Channels 路由设施packages/channels/base/src/SessionRouter.ts 等
Qwen Code Java acp-sdk pompackages/sdk-java/client/pom.xml
Qwen Code IDE companion HTTP server(参考模板)packages/vscode-ide-companion/src/ide-server.ts
Codex Python SDK clientsdk/python/src/codex_app_server/client.py
Codex Rust 协议层codex-rs/app-server-protocol/src/protocol/v2.rs(10885 行)
Codex MCP server cratecodex-rs/codex-mcp / codex-rs/mcp-server

免责声明:以上数据基于 2026-05-01 各项目本地源码分析,可能已过时。版本号仅反映当时仓库状态。各产品演进迅速,建议使用前以仓库最新状态为准。