> **项目**: Multica — The Open-Source Managed Agents Platform
> **作者**: Forrest Chang (Jiayuan Zhang)
> **仓库**: [github.com/multica-ai/multica](https://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/` 目录下有一个极其优雅的设计:
```go
// Backend 是统一的 Agent 执行接口
type Backend interface {
Execute(ctx context.Context, prompt string, opts ExecOptions) (*Session, error)
}
```
一个接口,十种实现:
| 文件 | Agent | 通信方式 |
|------|-------|---------|
| `claude.go` | Claude Code | `--output-format stream-json` |
| `codex.go` | OpenAI Codex | `app-server` 模式 |
| `cursor.go` | Cursor Agent | `stream-json` |
| `openclaw.go` | OpenClaw | JSON 模式 |
| `opencode.go` | OpenCode | JSON 模式 |
| `gemini.go` | Gemini CLI | `stream-json` |
| `hermes.go` | Hermes | ACP 协议 |
| `kimi.go` | Kimi | ACP 协议 |
| `pi.go` | Pi | JSON 模式 |
| `copilot.go` | GitHub Copilot | JSON 模式 |
**设计思想**: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` 包的设计特别值得注意:
```go
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` 是一个进程内的同步发布/订阅系统:
```go
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` 方法是核心入口:
```go
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-dom` | 用 `NavigationAdapter` 抽象路由,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**。
```typescript
// 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 所有缓存**。这确保了即使断线期间错过了事件,重连后也能恢复到最新状态。
```typescript
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` 的组合:
```sql
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` 里有一段特别有意思的代码:
```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 互相 <span class="mention-invalid">@mention</span> 时,很容易形成无限循环(A 回复 B → B 被 <span class="mention-invalid">@mention</span> 触发 → B 回复 A → A 被 <span class="mention-invalid">@mention</span> 触发 → ...)。Multica 在 prompt 层面就告诉 Agent:"如果对方只是在说谢谢,就不要回复了。"
这比事后加一个"最大回复次数"的限制要优雅得多——它让 Agent 学会了"什么时候该闭嘴"。
### 5.3 Session Resume:断点续传
Daemon 支持任务的中断恢复。如果 Agent 执行到一半超时或崩溃,下次可以带着 `ResumeSessionID` 继续:
```go
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` 过滤:
```go
// 所有查询都过滤 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 的技术栈选择透露出一种务实的工程哲学:
| 选择 | 不选 | 原因 |
|------|------|------|
| **Go** | Node.js/Python | 后端需要高性能 WebSocket 和进程管理 |
| **sqlc** | ORM (GORM/Prisma) | 类型安全的 SQL,零运行时开销 |
| **Chi** | Gin/Echo | 轻量、标准库兼容、中间件链清晰 |
| **React Query** | Redux/SWR | 服务端状态缓存 + 自动失效是核心需求 |
| **Zustand** | Redux | 轻量、无 boilerplate、适合客户端状态 |
| **pnpm + Turborepo** | Nx/Lerna | 更快的安装速度和构建缓存 |
**没有微服务,没有 GraphQL,没有 gRPC**——这是一个"刚刚好"的技术栈,不多不少。
## 七、和同类项目的对比
| 维度 | Multica | Paperclip | Vibe Kanban | Claude Code Dispatch |
|------|---------|-----------|-------------|---------------------|
| **开源** | ✅ Apache 2.0 | ❌ | ❌ | ❌ (Anthropic) |
| **Agent 多样性** | 10+ 种 | 有限 | Claude only | Claude only |
| **本地执行** | ✅ Daemon | ❌ 云端 | ❌ 云端 | ❌ 云端 |
| **Skill 系统** | ✅ 云端+本地 | ❌ | ❌ | ❌ |
| **Autopilot** | ✅ 定时+触发 | 部分 | ❌ | ❌ |
| **人机混合** | ✅ 一等公民 | 部分 | ❌ | ❌ |
Multica 的核心差异化在于:**它是唯一一个同时做到"开源 + 多 Agent 支持 + 本地执行 + Skill 系统"的项目。**
## 八、一句话总结
> **Multica 的架构精髓可以用一句话概括:它不是在构建一个"AI 工具",而是在构建一个"AI 员工管理系统"。Agent Backend 接口让任何 CLI Agent 都能接入,Daemon 让代码不离开用户机器,事件总线让系统松耦合,Skill 系统让 Agent 的知识可复用,多态指派让人类和 Agent 真正平等。这不是一个周末项目——这是一个经过深思熟虑的、面向 AI 原生团队的基础设施。**
---
📎 **GitHub**: [multica-ai/multica](https://github.com/multica-ai/multica)
📎 **官网**: [multica.ai](https://multica.ai)
📎 **作者**: [forrestchang (Jiayuan Zhang)](https://github.com/forrestchang)
📎 **协议**: Apache 2.0
📎 **相关项目**: [andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills) (78K ⭐)
登录后可参与表态
讨论回复
0 条回复还没有人回复,快来发表你的看法吧!