当前位置: 首页 > news >正文

Emdash 拆解:多 Agent 并行开发桌面端的实现思路,兼谈 ACP 与 A2A

Em dash是排版术语中的“破折号”(),长度等于一个字母 “m”(em)的宽度。破折号通常用来引出解释、插入独立的想法,或者连接两个相关的子句。在这个项目里,它隐喻了产品的核心形态——为主线开发流程“开辟一条支线”(独立的 Git worktree),让各个 AI Agent 在这些支线里独立、并行地执行任务,最后再把结果无缝“连接”回主分支(PR/Diff)。

这个项目是一个本地优先的桌面控制台,核心能力(源自 README “What You Can Do”):

  • 同时跑多个 coding agent,不用手动切多个终端
  • 每个 agent 关在独立 Git worktree + 独立分支里隔离
  • 把 Linear/GitHub/Jira/GitLab 等的issue/ticket 直接派给某个 agent
  • 在一处看 diff、建 PR、查 CI、合并
  • 本地或 SSH 远程机器上跑同一套流程

用户如何把任务交给 Agent

Emdash 把“创建工作区”和“启动 Agent”放在同一个Create Task窗口里,但两者不是一回事:用户可以只创建一个隔离的 Task,之后再启动 Agent;也可以同时配置Initial Conversation,创建后立即让 Agent 开工。

用户需要提供或确认这些信息:

  1. 项目:Agent 要修改哪个代码仓库。通常由用户当前所在的项目页面自动带入,不必重复选择。
  2. Task name:这条开发支线的名称。它是创建 Task 的必填项;Emdash 可以根据 issue、PR 或随机规则自动生成,用户也可以修改。
  3. Workspace Settings:代码在哪里改。默认是在项目中创建独立 worktree;用户也可以选择仓库根目录、已有 workspace、PR 分支或远程 sandbox。创建新 worktree 时,还可确认:
    • 从哪个分支开始;
    • 是直接 checkout 该分支,还是基于它创建新分支;
    • 新分支名称;
    • 是否把新分支 push 到远端。
  4. Based on(可选):关联一个 Issue 或 Pull Request。Issue 可以来自 Linear、GitHub、Jira 等集成;选中后,标题和描述可以用于生成 Task name,并作为上下文交给 Agent。
  5. Initial Conversation(可选)
    • Agent:选择 Claude Code、Codex、OpenCode 等已安装并被 Emdash 检测到的 provider;通常会预选默认 Agent。
    • Prompt:描述希望 Agent 做什么,例如“修复登录超时并补充回归测试”。可以留空,也可以插入 Prompt Library 模板、文件或 issue 上下文。
    • Model:如果该 Agent 暴露了模型列表,可以指定模型;不选则使用 CLI 默认模型。
    • Auto-approve permissions:是否自动批准 Agent 的权限请求。
    • Use chat UI:Agent 支持 ACP 时,可选择结构化聊天界面;关闭时走传统 PTY 终端。

真正的创建条件只有:项目存在、Task name 非空、Workspace 配置合法。Agent 和 Prompt 都不是必填项;没有选择 Agent 时,Emdash 只创建 Task 和 workspace,不创建初始 conversation。

点击Create后,系统自动完成:

  1. 将 Task、workspace 配置及可选的 initial conversation 写入 SQLite;
  2. 根据 Workspace Settings 创建或复用目录,并执行建分支、checkout、创建 worktree 等 Git 操作;
  3. 如果项目本身通过 SSH 连接,便在远程机器上准备 workspace;本地项目则在本机准备;
  4. 如果配置了 Initial Conversation,在准备好的 workspace 路径中启动所选 Agent:传统 CLI 走 PTY,支持 ACP 且开启 Chat UI 时走 ACP;
  5. 将用户 Prompt 与可选的 issue 上下文发送给 Agent,持续接收状态和输出。

所以这里的“委派”不是 Agent 自己领取任务,也不是一个 Agent 再拆给其他 Agent,而是用户明确指定在哪份代码上工作、由哪个 Agent 执行、要它做什么;Emdash 负责准备隔离环境、启动进程和汇总结果。

痛点与解决的问题

emdash 的多 agent 不是为了让 agent 们合力完成一件大事,而是为了"多路并行押注 + 隔离防污染"

真实痛点:在同一个仓库里同时跑多个 CLI agent 会互相覆盖文件、污染分支、终端输出混杂。所以"多 agent"要解决的是:

  • 并行提效:任务 A 修 bug、任务 B 做 feature,同时进行不干扰
  • 赛马择优:同一个问题让不同 agent/不同方案各跑一版,挑最好的 merge

核心实现复现思路

整体结构:

远程/本地 workspace-server 守护进程

Electron 主进程

Preload 独木桥

Renderer 浏览器进程

wire over socket/stdio

chat-ui / ui React

wire client

contextBridge

rpc.ts controller

main/core 40+ 域模块

SQLite drizzle

api/controller

acp host

pty

git/files

如果要从零复刻 Emdash,其核心思路如下:

  1. 第一步:基于 Git Worktree 的物理隔离(任务底座)
    • 思路:绝不能让所有 Agent 都在同一个目录下运行。当用户创建一个 Task 时,系统应自动为该任务分配一个独立的物理路径。
    • 实现:在 Electron 主进程(Main)中操作 Git CLI,为项目创建一个隔离的 Worktree。同时在本地 SQLite 数据库中写入tasksworkspaces记录,确保任务状态(即使应用重启)能被持久化恢复。
  2. 第二步:双轨制的 Agent 运行时抽象(执行引擎)
    • 思路:需要兼容“传统纯命令行 Agent”和“现代结构化 Agent”。
    • 实现(均在主进程中运行,避免阻塞 UI):
      • PTY 模式:针对传统 CLI,通过node-pty在对应的 Worktree 路径下 spawn 子进程,注入环境变量(hooks)来劫持其行为,并处理终端字符流。
      • ACP 模式:针对支持 Agent Client Protocol 的现代 Agent,通过SessionManagerSessionCell维护强类型的状态机,解析意图并管理权限与日志。

第三步:远程代理(workspace-server)

要解决的场景:你的项目代码不在本地 Mac,而在一台远程服务器上(通过 SSH 连的)。

桌面 App 想在远程跑 Agent、读文件、执行 git,最"笨"的做法是每个操作都开一条 SSH 命令发过去。但 Agent 干活是高频的(读几百个文件、频繁跑 git status),每次都新建 SSH 连接、启动进程,延迟高、开销大,还没法维持长会话状态。

Emdash 的做法:干脆在远程机器上常驻一个小程序workspace-server,一个 Node 守护进程)。

  • 桌面端只跟这个常驻程序说话,由它在远程本地就近执行 git/文件/Agent 操作。
  • 通信走Unix Socket(本地进程间管道,SSH 转发过来),而不是每次开新 SSH 命令。
  • 它们之间用一套固定格式的协议对话(Wire Protocol,标了版本号 v2.0.0,方便两端升级时对得上)。

一句话:把"每次远程喊话"变成"在远程派个常驻代表,你只跟代表沟通",代表就近帮你操作文件系统、git 和 Agent 进程。

第四步:展示层闭环(前端 UI)

要解决的问题:前端界面(你看到的聊天窗、diff 面板)要不要直接碰数据库、文件、子进程?

Electron 里前端是浏览器环境(Renderer 进程)。如果让它直接读 SQLite、spawn 进程,一旦前端代码有漏洞(比如渲染了恶意内容),攻击者就直接拿到了你机器的文件和 shell 权限。这是 Electron 安全的大忌。

Emdash 的做法前端和后端彻底隔离,只留一个细窄的通道

  • 所有底层能力(DB、git、进程)都关在**主进程(Main)**里。
  • 前端(Renderer)想要数据,必须通过contextBridge(Preload 脚本暴露的一座"独木桥")发强类型 RPC 请求给主进程,主进程做完再把结果传回来。
  • 前端自己只干两件事:渲染 UI+展示从数据库拉来的数据(比如聚合各任务的 PR 状态、diff、消息,做成审查面板)。

一句话:前端只当"显示器",所有危险操作都托管给主进程,中间靠一座受控的独木桥(Typed RPC)传话

两步都是同一个设计哲学:上层永远不直接碰底层,中间用一个受控的代理/通道转发。这样既安全(第四步),又高效(第三步)。

ACP

全称Agent Client Protocol(代码中对应@agentclientprotocol/sdk)。

  1. ACP(Agent Client Protocol)的大致内容是什么?
    • 定位:ACP 是专门为了让宿主应用(如 Emdash 这种 GUI 控制台、IDE)和 AI Agent 之间进行结构化通信而设计的协议。
    • 大致内容:它定义了一套基于 JSON-RPC(或类似结构)的双向通信标准。主要包括:
      • Session Lifecycle(会话生命周期)initialize,newSession,loadSession,closeSession
      • State & Transcript(状态与日志):Agent 通过SessionUpdate(如思考中、发送消息、工具调用、计划更新)将内部状态流式推给客户端;客户端可以渲染出比纯文本终端更丰富的 UI(如带有折叠的思维链、清晰的工具调用卡片)。
      • Permission Control(权限控制):Agent 要执行危险命令(如写文件、执行 Shell)时,必须通过requestPermission向客户端申请,客户端可以弹出拦截框让用户“允许”或“拒绝”。
      • Host Capabilities(宿主能力调用):Agent 可以反向调用客户端暴露的接口(如readTextFile,writeTextFile,createTerminal等),将文件系统操作或终端环境外包给客户端。
  2. ACP 是抽象规则还是具体实现?
    • 类似 HTTP/IP 协议,本身是抽象规则(规范),定义了消息的 Schema 和交互时序。
    • 但它有具体的 SDK 实现:在 Emdash 中,引用了@agentclientprotocol/sdk这个包,它提供了 TypeScript 的强类型定义(如SessionNotification,RequestPermissionRequest)。
    • Emdash 内部有一个专门的acp-agents模块实现了这个协议的客户端(Host)端点,负责把原生的SessionUpdate转换(toAgentUpdate)成状态机(SessionMachine)可以消费的统一领域事件(NormalizedEvent)。

ACP 和 A2A(Agent-to-Agent)

流程层面 A2A 确实可以复用 ACP 的骨架,分歧不在"流程",而在几个隐含假设上。

ACP 的核心时序其实是一个通用的会话协议initialize(能力协商)→newSessionprompt(发指令)→SessionUpdate(流式回状态)→requestPermission(回调审批)。

把这个套到 A2A 上完全成立:一个 orchestrator agent 只要扮演 ACP 里的Client 角色,把 sub-agent 当成被调用的 Agent,prompt就是子任务,SessionUpdate就是子任务进度。所以传输层、会话生命周期、流式回传这套东西是可复用的

但是语义层需要扩展——去掉"人在回路"的非对称假设,补上对等角色、agent 能力发现、远程寻址。

所以业界把它们做成两个协议,不是因为流程不同,而是因为**"另一端是不是人"这个假设不同**,导致权限、能力词汇、发现机制三处必须重做。

复用会断掉的 4 个点

分歧不在流程,而在 ACP 内置了"一端是人机 UI,另一端是干活的 Agent"这个非对称假设

  1. 角色对称性:ACP 里 Client 天然带"人类能力"(弹权限框、渲染 UI),Agent 带"计算能力"。A2A 两端都是 Agent,是对等的——ACP 的角色划分在这里失效。
  2. 权限模型:ACP 的requestPermission本质假设背后有个人点"允许/拒绝"。A2A 里 sub-agent 申请权限时,对面是另一个 Agent,没有人——这个回调要么退化成自动批准,要么得往上层再转发一跳,语义变了。
  3. 能力语义:ACP 协商的是面向人的能力(readTextFile、terminal、permission UI)。A2A 需要的是面向 agent 的能力——任务委派、能力发现(“你会干什么”)、Agent Card、技能广告、产物(artifact)交付。这批词汇 ACP 里没有。
  4. 寻址与发现:ACP 是本地的(一个 host 通过 stdio spawn 一个 CLI 子进程,点对点)。Google 的 A2A 强调跨网络、跨厂商发现(HTTP + Agent Card),要解决"我怎么找到并信任一个远程 agent",这是 ACP 完全没覆盖的层。
http://www.jsqmd.com/news/1351334/

相关文章:

  • 2026湖州拆除复原毛坯找哪家?优选施工队对比指南 - geo交流
  • Java Selenium自动化破解滑动验证码:从图像识别到轨迹模拟实战
  • SQL注入进阶:无列名注入原理与实战绕过information_schema过滤
  • 深入了解天津建设监理协会网站:助力天津工程建设规范化发展的专业门户与行业风向标
  • 昆明本地防水补漏哪家靠谱?屋顶/卫生间/外墙/地下室/阳台渗水师傅筛查(2026年8月新) - 金信达
  • 别再瞎优化Python代码!一套可落地的性能剖析+内存+并发提速方案
  • 一文讲懂osek网络管理
  • 没有工作经验怎么写简历,夸克AI简历秋招定制教程
  • Python开发者必知:10大安全漏洞原理与实战修复指南
  • C++ Lambda表达式默认参数限制解析与四种替代方案实践
  • 鸿蒙 DevEco CLI:AI驱动的命令行工具
  • 开关电源电感选型实战指南:从核心参数到拓扑应用
  • 抖音无水印批量下载器:5分钟轻松搭建个人内容素材库
  • 南海区汽车维修避坑指南:靠谱汽修怎么选 - 国麟测评
  • C语言总结 --- 构造数据类型
  • MyBatis动态SQL实战:告别SQL拼接,高效构建复杂查询
  • 7. 打开App再截图:启停3步闭环
  • 17需求落地:监控系统的部署与运维
  • Linux 八股不靠硬背:跟着逆境救一台“失联”的服务器
  • 嵌入式BSP提交前自查:代码质量、设备树与团队协作全流程指南
  • C++ Asio网络编程实战:SSL/TLS加密通信从原理到实现
  • ASMR触发器技术解析:从声音工程到心理声学的沉浸式体验设计
  • 全屋整装与传统装修区别对比|秦皇岛佳人装饰 - 装企精灵GEO
  • 北海本地防水补漏如何挑选?屋顶/卫生间/外墙/地下室/阳台漏水检修实测(2026年8月新) - 北京优选
  • C语言条件语句详解:if与if-else实战指南
  • 智能轨道电源系统选型全攻略:从家装到工业场景的技术逻辑与方案对比
  • 锂电池SOH估计与NRBO-Transformer模型实践
  • RT-LAB平台PWM模块应用与电机控制实践
  • 打破智能体信息孤岛:适配开源 Agent 框架的 AI 搜索工具推荐与实践
  • UE5性能优化利器:ProfileCPU精准定位CPU瓶颈实战指南