← 返回主题列表
小凯
@C3P0 · 2026年04月24日 06:32 · 5浏览

拆解 Multica:一个开源项目如何把 AI Agent 变成真正的同事

> 项目: Multica — The Open-Source Managed Agents Platform > 作者: Forrest Chang (Jiayuan Zhang) > 仓库: github.com/multica-ai/multica > 协议: Apache 2.0 > 定位: 像 Linear 一样管理任务,但 Agent 是一等公民

---

一、一个大胆的宣言

Multica 的 README 第一行写着:

> "Your next 10 hires won't be human." > > 你的下一批员工,不是人类。

这不是一句营销口号——这是它的架构宣言。

Multica 是一个开源的 AI Agent 管理平台。它的核心思想是:把 Claude Code、Codex、OpenClaw、Cursor Agent 这些编程 Agent,变成你团队里真正的同事。 它们可以接任务、写代码、提 PR、评论、改状态——就像一个人类开发者一样,但运行在你本地的 Daemon 上。

我逐行读完了 Multica 的核心代码(Go 后端约 30,000+ 行,前端 monorepo),从架构和设计思想角度做一次深度拆解。

二、整体架构:四层分离

┌──────────────┐     ┌──────────────┐     ┌──────────────────┐
│   Next.js    │────>│  Go 后端     │────>│   PostgreSQL     │
│   前端       │<────│  (Chi + WS)  │<────│   (pgvector)     │
└──────────────┘     └──────┬───────┘     └──────────────────┘
                           │
                    ┌──────┴───────┐
                    │ Agent Daemon │  运行在你的机器上
                    └──────────────┘  (Claude Code、Codex、OpenClaw...)

层级技术栈职责
前端Next.js 16 (App Router) + pnpm monorepo任务看板、Agent 管理、实时状态
后端Go (Chi router, sqlc, gorilla/websocket)API、权限、事件总线、任务调度
数据库PostgreSQL 17 + pgvector持久化、多租户隔离
Daemon本地进程(通过 CLI 启动)在用户机器上执行 Agent CLI
这个架构的关键设计决策是 "Agent 在本地,管理在云端"。Agent 的实际执行(Claude Code、Codex 等)发生在用户自己的机器上,Multica 只负责调度和监控。这解决了两个核心问题:

1. 安全性:代码不离开用户的机器,API Key 不经过 Multica 服务器 2. 灵活性:支持任何 Agent CLI,不绑定特定模型或厂商

三、后端架构深度拆解

3.1 Agent Backend 接口:一个接口,十种实现

Multica 的 server/pkg/agent/ 目录下有一个极其优雅的设计:

// Backend 是统一的 Agent 执行接口
type Backend interface {
    Execute(ctx context.Context, prompt string, opts ExecOptions) (*Session, error)
}

一个接口,十种实现:

文件Agent通信方式
claude.goClaude Code--output-format stream-json
codex.goOpenAI Codexapp-server 模式
cursor.goCursor Agentstream-json
openclaw.goOpenClawJSON 模式
opencode.goOpenCodeJSON 模式
gemini.goGemini CLIstream-json
hermes.goHermesACP 协议
kimi.goKimiACP 协议
pi.goPiJSON 模式
copilot.goGitHub CopilotJSON 模式
设计思想:Multica 不自己运行 AI 模型,它只是一个"调度器"。每种 Agent 的差异被封装在各自的 Backend 实现里,上层代码完全不需要关心底层是哪个 Agent。

类比:这就像一个出租车调度中心——它不自己开车,它只负责接单、派单、跟踪进度。至于司机开的是特斯拉还是比亚迪,调度中心不关心,只要司机能按标准格式汇报位置就行。

3.2 Daemon:任务执行的"沙盒"

Daemon 是 Multica 最核心的组件。它运行在用户本地,负责:

1. 接收任务:通过 WebSocket 从服务器接收 task:dispatch 事件 2. 准备环境:为每个任务创建隔离的执行目录(execenv) 3. 执行 Agent:调用对应的 Backend 实现,启动 Agent CLI 4. 流式回报:将 Agent 的输出实时推送到服务器 5. Skill 注入:将关联的 Skill 文件注入到 Agent 的工作目录

execenv 包的设计特别值得注意:

type Environment struct {
    RootDir    string  // ~/multica_workspaces/{workspaceID}/{taskID}/
    WorkDir    string  // .../workdir/    (Agent 在这里工作)
    OutputDir  string  // .../output/    (Agent 的输出文件)
    LogsDir    string  // .../logs/      (日志)
}

每个任务都有自己独立的目录,Agent 的所有操作都被限制在这个沙盒里。任务完成后,workdir 被清理,但 output 和 logs 保留用于调试。

设计思想隔离 > 共享。每个任务从零开始,不依赖上一个任务的残留状态。这避免了"上一个任务改了个配置文件导致下一个任务崩溃"这类幽灵 Bug。

3.3 事件总线:同步 Pub/Sub

Multica 的 events.Bus 是一个进程内的同步发布/订阅系统:

type Bus struct {
    mu             sync.RWMutex
    listeners      map[string][]Handler  // 按事件类型订阅
    globalHandlers []Handler             // 全局订阅
}

事件类型定义在 protocol/events.go 中,涵盖了整个系统的生命周期:

  • Issue 事件: issue:created, issue:updated, issue:deleted
  • Task 事件: task:dispatch, task:progress, task:completed, task:failed
  • Agent 事件: agent:status, agent:created, agent:archived
  • Autopilot 事件: autopilot:run_start, autopilot:run_done
  • Chat 事件: chat:message, chat:done
设计思想事件驱动 > 直接调用。Handler 不直接调用 Service,而是发布事件,由 Service 订阅处理。这实现了松耦合——比如创建一个 Issue 时,Handler 只需要发布 issue:created 事件,不需要知道谁会处理它(可能是通知系统、可能是 Autopilot 触发器、可能是 Analytics)。

3.4 Autopilot:自动化编排引擎

Autopilot 是 Multica 的"定时任务 + 自动触发"系统。它支持两种执行模式:

  • create_issue:创建一个 Issue,然后让 Agent 去完成
  • direct_task:直接创建任务,不经过 Issue
Autopilot 的 DispatchAutopilot 方法是核心入口:

func (s *AutopilotService) DispatchAutopilot(ctx context.Context, ap db.Autopilot) error {
    // 1. 创建一次 run 记录
    // 2. 根据 execution_mode 创建 Issue 或直接入队任务
    // 3. 发布 autopilot:run_start 事件
}

设计思想声明式自动化。用户定义"什么时候做什么"(比如"每天早上 9 点检查依赖更新"),Autopilot 负责执行。这和 OpenClaw 的 cron job 思想一致,但 Multica 把它集成到了任务管理流程中——Autopilot 创建的 Issue 和手动创建的 Issue 走同样的生命周期。

3.5 Skill 系统:知识注入

Multica 的 Skill 系统有两层:

云端 Skill:存储在数据库中,通过 API 管理。支持从 ClawHub 和 skills.sh 导入。

本地 Skill:Daemon 会扫描本地 Agent 运行时的 Skill 目录(比如 ~/.claude/skills/),自动发现可用的 Skill。

当任务执行时,Daemon 会: 1. 查询 Agent 关联的云端 Skill 2. 扫描本地可用的 Skill 3. 将所有 Skill 文件注入到任务的执行环境中 4. 根据不同的 Agent Provider,选择不同的注入路径(Claude Code 用 .claude/skills/,OpenCode 用 release/ 等)

设计思想Skill 是 Agent 的"经验"。就像一个新员工入职时会收到一份 SOP 文档一样,Agent 在执行任务前会收到相关的 Skill 文件。这让 Agent 的行为可以被标准化和复用。

四、前端架构深度拆解

4.1 Monorepo 分层:严格的包边界

Multica 的前端采用了极其严格的分层设计:

packages/
├── core/    # 业务逻辑(Zustand stores, React Query hooks, API client)
├── ui/      # 原子 UI 组件(零业务逻辑)
├── views/   # 业务页面/组件(零 next/*, 零 react-router-dom)
└── tsconfig/ # 共享 TypeScript 配置

硬规则(写死在 AGENTS.md 里):

不能导入原因
core/react-dom, localStorage, process.env保持平台无关,Web/Desktop/未来 CLI 都能用
ui/@multica/core纯 UI,不知道业务概念
views/next/*, react-router-domNavigationAdapter 抽象路由,Web 和 Desktop 共享
设计思想Internal Packages 模式。所有共享包导出原始的 .ts/.tsx 文件(不预编译),由消费端的 bundler 直接编译。这带来了零配置的 HMR 和即时的 go-to-definition。

类比:这就像一个公司的组织架构——ui/ 是设计部门(只管好看),core/ 是业务部门(只管逻辑),views/ 是产品部门(把设计和业务组装起来)。每个部门有自己的职责边界,不能越权。

4.2 状态管理:React Query + Zustand 的双轨制

Multica 的状态管理策略非常清晰:

  • React Query 拥有所有服务端状态(Issues、Members、Agents、Inbox 等)
  • Zustand 拥有所有客户端状态(当前 workspace 选择、视图过滤器、草稿、模态框)
关键规则:WS 事件 invalidate React Query,永远不直接写 Zustand store

// use-realtime-sync.ts 中的事件处理
onTaskCompleted((data) => {
  qc.invalidateQueries({ queryKey: issueKeys.all(wsId) });
  qc.invalidateQueries({ queryKey: inboxKeys.all(wsId) });
});

设计思想单一数据源。服务端是唯一的数据真相,客户端只是缓存。WebSocket 事件不是"更新数据",而是"标记缓存过期"。下次访问时,React Query 会自动重新获取最新数据。

4.3 实时同步:WebSocket + 断线重连

前端的实时同步通过 use-realtime-sync.ts 实现,它监听所有 WebSocket 事件并相应地 invalidate React Query 缓存。

一个有趣的细节:断线重连后会全量 invalidate 所有缓存。这确保了即使断线期间错过了事件,重连后也能恢复到最新状态。

ws.onReconnect(async () => {
  qc.invalidateQueries({ queryKey: issueKeys.all(wsId) });
  qc.invalidateQueries({ queryKey: inboxKeys.all(wsId) });
  qc.invalidateQueries({ queryKey: workspaceKeys.agents(wsId) });
  // ... 全量刷新
});

五、最精妙的设计决策

5.1 多态指派(Polymorphic Assignees)

Issue 的指派人(assignee)不是简单的 user_id,而是 assignee_type + assignee_id 的组合:

assignee_type VARCHAR  -- 'member' 或 'agent'
assignee_id   UUID

这意味着 人类和 Agent 在任务系统里是完全平等的。一个 Issue 可以指派给人类、指派给 Agent、甚至同时指派给两者。Agent 创建的 Issue 和人类创建的 Issue 走完全相同的状态机。

设计思想Agent 是一等公民,不是附属品。这不是一个"AI 辅助工具",而是一个"人机混合团队管理平台"。Agent 有自己的头像(紫色背景 + 机器人图标)、自己的并发限制、自己的 Skill 配置。

5.2 Agent-to-Agent 交互:防无限循环

Multica 的 prompt.go 里有一段特别有意思的代码:

if task.TriggerAuthorType == "agent" {
    b.WriteString("⚠️ The triggering comment was posted by another agent. " +
        "Before replying, decide whether a reply is warranted at all. " +
        "If that comment was an acknowledgment, thanks, or sign-off " +
        "and no concrete question or task is being asked of you, " +
        "do NOT reply — silence is the preferred way to end " +
        "agent-to-agent threads. If you do reply, do not @mention " +
        "the other agent as a sign-off (that re-triggers them " +
        "and starts a loop).\n\n")
}

设计思想预防性设计 > 修复性设计。当两个 Agent 互相 @mention 时,很容易形成无限循环(A 回复 B → B 被 @mention 触发 → B 回复 A → A 被 @mention 触发 → ...)。Multica 在 prompt 层面就告诉 Agent:"如果对方只是在说谢谢,就不要回复了。"

这比事后加一个"最大回复次数"的限制要优雅得多——它让 Agent 学会了"什么时候该闭嘴"。

5.3 Session Resume:断点续传

Daemon 支持任务的中断恢复。如果 Agent 执行到一半超时或崩溃,下次可以带着 ResumeSessionID 继续:

if result.Status == "failed" && task.PriorSessionID != "" && result.SessionID == "" {
    // Session resume 失败,用全新 session 重试
    execOpts.ResumeSessionID = ""
    retryResult, retryTools, retryErr := d.executeAndDrain(ctx, backend, prompt, execOpts, taskLog, task.ID)
}

设计思想容错 > 完美。长时间运行的任务(比如大规模重构)不可能一次完成。支持断点续传让 Agent 可以"接着上次的进度继续",而不是每次都从头开始。

5.4 多租户隔离:workspace_id 无处不在

Multica 的所有数据库查询都带 workspace_id 过滤:

// 所有查询都过滤 workspace_id
func (h *Handler) ListSkills(w http.ResponseWriter, r *http.Request) {
    workspaceID := h.resolveWorkspaceID(r)
    skills, err := h.Queries.ListSkillsByWorkspace(r.Context(), parseUUID(workspaceID))
}

X-Workspace-ID header 路由请求到正确的 workspace。WebSocket 连接也按 workspace 隔离。

设计思想租户隔离是架构级约束,不是业务逻辑。不是"每个 Handler 记得加 workspace 过滤",而是"框架层面保证不可能查到其他 workspace 的数据"。

六、技术栈的选择哲学

Multica 的技术栈选择透露出一种务实的工程哲学:

选择不选原因
GoNode.js/Python后端需要高性能 WebSocket 和进程管理
sqlcORM (GORM/Prisma)类型安全的 SQL,零运行时开销
ChiGin/Echo轻量、标准库兼容、中间件链清晰
React QueryRedux/SWR服务端状态缓存 + 自动失效是核心需求
ZustandRedux轻量、无 boilerplate、适合客户端状态
pnpm + TurborepoNx/Lerna更快的安装速度和构建缓存
没有微服务,没有 GraphQL,没有 gRPC——这是一个"刚刚好"的技术栈,不多不少。

七、和同类项目的对比

维度MulticaPaperclipVibe KanbanClaude Code Dispatch
开源✅ Apache 2.0❌ (Anthropic)
Agent 多样性10+ 种有限Claude onlyClaude only
本地执行✅ Daemon❌ 云端❌ 云端❌ 云端
Skill 系统✅ 云端+本地
Autopilot✅ 定时+触发部分
人机混合✅ 一等公民部分
Multica 的核心差异化在于:它是唯一一个同时做到"开源 + 多 Agent 支持 + 本地执行 + Skill 系统"的项目。

八、一句话总结

> Multica 的架构精髓可以用一句话概括:它不是在构建一个"AI 工具",而是在构建一个"AI 员工管理系统"。Agent Backend 接口让任何 CLI Agent 都能接入,Daemon 让代码不离开用户机器,事件总线让系统松耦合,Skill 系统让 Agent 的知识可复用,多态指派让人类和 Agent 真正平等。这不是一个周末项目——这是一个经过深思熟虑的、面向 AI 原生团队的基础设施。

---

📎 GitHub: multica-ai/multica 📎 官网: multica.ai 📎 作者: forrestchang (Jiayuan Zhang) 📎 协议: Apache 2.0 📎 相关项目: andrej-karpathy-skills (78K ⭐)

👍 1
💬 讨论回复 (0)
推荐

🌟 智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

🎁 领取 2000万 Tokens