Multica
multica-ai/multica · 源码级 基于 server/ + packages/ 源码

把编程 Agent 变成真实队友

像给同事派活一样把 issue 指派给 Agent——它自己接活、写代码、报阻塞、改状态,并累积可复用的 skill。Web/Desktop 双端共享同一套 headless 业务包。所有结论来自源码文本,含实际函数签名与类型定义。

# multica-ai/multica · Go + Next.js + Electron · 改良版 Apache-2.0

Go · Next · Electron
全栈技术
core · ui · views
3 个共享包
PostgreSQL pg17
pgvector · 单容器多 DB
pnpm + Turbo
Monorepo
向下探索
01
architecture · the layering

分层架构

一句话定位:Multica 把编程 Agent 当成真实队友。后端 Go(Chi + pgx/sqlc + gorilla/websocket),Web 端 Next.js(App Router),桌面端 Electron(electron-vite + react-router-dom),移动端 Expo/RN,数据库 PostgreSQL(pgvector/pg17)。

从 PostgreSQL 到双端 app,依赖方向严格单向:views → core + ui,而 core 与 ui 彼此独立、互不导入。所有共享包以原始 .ts/.tsx 源码导出(无预编译),由消费端 app 的打包器直接编译——这就是 “Internal Packages” 模式,换取零配置 HMR 与即时跳转定义。Web 与 Desktop 通过 @multica/core、@multica/ui、@multica/views 三个共享包复用全部业务逻辑与 UI。

本文基于对 server/、apps/web/、apps/desktop/、packages/core|ui|views|eslint-config、CLAUDE.md、各 package.json、turbo.json、pnpm-workspace.yaml 的源码级阅读,所有引用均来自实际读到的文件。

multica · 分层依赖图(自底向上)
          ┌──────────────────────────────────────────────────┐
          │                  PostgreSQL                        │
          │   pgvector/pgvector:pg17 · 单容器多 DB(worktree)   │
          │   agent / agent_task_queue / skill / agent_skill / │
          │   issue / runtime / chat_session / autopilot …     │
          └───────────────▲──────────────────────────────────┘
                          │ pgx + sqlc(生成代码 server/pkg/db/generated)
          ┌───────────────┴──────────────────────────────────┐
          │                Go Backend  (server/)              │
          │  Chi router · gorilla/websocket Hub · events.Bus  │
          │  internal/handler/*  内部 service/*  pkg/protocol  │
          │  ┌──────────────┐ ┌───────────────┐ ┌──────────┐  │
          │  │ TaskService  │ │ AgentTmpl /   │ │ Skill /  │  │
          │  │ (派活/认领/   │ │ Autopilot /   │ │ builtin_ │  │
          │  │  生命周期)    │ │ Squad service │ │ skills   │  │
          │  └──────────────┘ └───────────────┘ └──────────┘  │
          └───▲───────────────▲────────────────────▲──────────┘
 HTTP+WS(/ws) │               │ daemon WS(/tasks/claim, /start…)
              │               │                    │ 内嵌 CLI(multica)
 ┌────────────┴──┐   ┌────────┴──────────┐   ┌─────┴──────────────┐
 │ apps/web      │   │ apps/desktop      │   │ 本地 daemon / 云 runtime│
 │ Next.js · SSR │   │ (Electron)        │   │ 真正跑 Claude Code/Codex│
 │ platform/nav  │   │ react-router-dom  │   │ 等 Agent 二进制         │
 │ (next/nav)    │   │ 多 tab memory 路由 │   └─────────────────────┘
 └──────┬────────┘   └─────────┬─────────┘
        │ 都 import        都 import │
        ▼                          ▼
 ┌───────────────────────────────────────────────────────────┐
 │              共享包 (packages/, workspace:*)                │
 │  @multica/views — 业务页面(零 next/*、零 react-router、零 store)│
 │       │ 依赖                                                 │
 │       ▼                                                     │
 │  @multica/core  ←───────────┐   @multica/ui (纯原子组件)    │
 │  Headless 业务逻辑           │   零 @multica/core 导入        │
 │  零 react-dom / 零 localStorage  Base UI + Tailwind v4       │
 │  · Zustand stores(客户端状态) │   styles/ 设计 token         │
 │  · TanStack Query(服务端状态)─┘   ← views 同时依赖 ui        │
 │  · platform/CoreProvider · api/ClientIdentity + zod         │
 └───────────────────────────────────────────────────────────┘
        │ 仅 import type(纯类型 + 纯函数,零运行时耦合)
        ▼
 ┌───────────────────────────┐
 │  apps/mobile (Expo / RN)  │  独立:自有 React 版本、UI、状态、构建链
 └───────────────────────────┘
views → core + ui

严格单向

core 与 ui 互不导入,views 同时依赖二者;框架 API 只允许出现在各 app 的 platform/ 层。

Internal Packages

源码直出

共享包不预编译,exports 直指 .ts/.tsx,消费端打包器编译——零配置 HMR + 即时跳定义。

4 端 1 后端

同构多端

Web / Desktop / Mobile / CLI 四端 + Go 后端;mobile 与 core 之间仅 import type 纯类型耦合。

02
modules · source-level teardown

核心模块一览

以下基于对 server/ 与 packages/* 的逐文件阅读,把分层展开到「关键文件 + 关键代码段 + 设计 + 失败模式」的粒度。

表 01 · 模块 → 文件/目录 → 作用
模块文件 / 目录作用
server/ 入口cmd/server · cmd/multica · cmd/migrate三个入口二进制:HTTP/WS 服务、multica CLI、迁移工具(module github.com/multica-ai/multica/server,Go 1.26.1)
server/ handlerinternal/handler/*.goChi HTTP handler,按资源拆分(issue.go/agent.go/chat.go/daemon.go/daemon_ws.go…),统一 UUID 解析三态约定
server/ taskinternal/service/task.goTaskService——派活、认领、生命周期推进、状态对账、指标的核心(2362 行)
server/ builtininternal/service/builtin_skills/*随产品发给 Agent 的内置 skill(multica-working-on-issues、multica-squads、multica-autopilots),是“源码可追溯的契约”
server/ skillinternal/skill/frontmatter.go · reserved.goSKILL.md frontmatter 解析(与前端 core/skills/frontmatter.ts 同构)、保留 slug
server/ tmplinternal/agenttmpl/templatesAgent 模板(静态目录,工作区无关,前端缓存 staleTime: Infinity)
server/ realtimeinternal/realtime · internal/eventsHub——WS 广播;Bus——进程内事件总线
server/ runtimeinternal/daemon · daemonws · cloudruntime本地 daemon 与云 runtime:真正执行 Agent 的 execenv/repocache
server/ dbpkg/db/queries/*.sql + pkg/db/generatedsqlc:手写 SQL → 生成类型安全 Go。改 SQL 后 make sqlc 重生成
server/ protocolpkg/protocolWS/任务事件载荷类型(EventTaskQueued/Dispatch/Running/Completed/Failed、TaskCompletedPayload)
apps/webapp/[workspaceSlug]/(dashboard)/… · platform/navigation.tsx工作区作用域路由 + 路由组;Web 端唯一允许用 next/navigation 的地方,适配成共享 NavigationAdapter
apps/desktopsrc/main · src/renderer/src/platform/navigation.tsxElectron 主进程/preload 桥;Desktop 端唯一用 react-router-dom 的地方,多 tab + memory router;构建把 multica CLI 打进 app
packages/coreapi/ · platform/ · <domain>/queries|mutations|storesHeadless 业务逻辑:零 react-dom/零 localStorage/零 process.env;TanStack Query + Zustand + WS 同步;react 仅为 peer
packages/uicomponents/ui/* · styles/tokens.css原子 UI:shadcn 基于 @base-ui/react(非 Radix);没有 @multica/core,强制“纯 UI、零业务逻辑”
packages/viewsissues/agents/inbox/squads/chat · navigation/*共享业务页面;定义 NavigationAdapter 抽象、<AppLink>;exports 与 deps 里没有任何 next/react-router-dom

关键设计 · UUID 解析三态约定

源于真实事故 #1661(DELETE 返回 204 却匹配 0 行,因原 util.ParseUUID 对非法输入静默返回零 UUID)。CLAUDE.md 的 Backend Handler UUID Parsing Convention 把它定为硬约定:

  • ① 资源路径参数(可能是 MUL-123 也可能是 UUID)必须经 loadIssueForUser/loadAgentForUser 等 loader 解析,之后所有写查询用解析出的 entity.ID;
  • ② 纯 UUID 的请求边界输入用 parseUUIDOrBadRequest,非法即写 400;
  • ③ 可信往返(sqlc 返回的 UUID)用 parseUUID(panic-on-invalid,由 middleware.Recoverer 兜成 500)。
03
headless · state separation

Headless core

Zustand(客户端状态)+ React Query(服务端状态)严格分离。CLAUDE.md 把这条定为“架构保持连贯的根本约束”:TanStack Query 拥有全部服务端状态,Zustand 拥有全部客户端状态,二者绝不混用。

服务端状态用 queryOptions 描述,键以 wsId 开头——工作区切换即换 key、自动出对数据,无需手动失效。

packages/core/agents/queries.ts — wsId 前缀的 queryKey
export const agentTaskSnapshotKeys = {
  all: (wsId: string) => ["workspaces", wsId, "agent-task-snapshot"] as const,
  list: (wsId: string) => [...agentTaskSnapshotKeys.all(wsId), "list"] as const,
};

export function agentTaskSnapshotOptions(wsId: string) {
  return queryOptions({
    queryKey: agentTaskSnapshotKeys.list(wsId),
    queryFn: () => api.getAgentTaskSnapshot(),
    staleTime: 30 * 1000,        // 仅作 fallback;主刷新信号是 WS 任务事件
    refetchOnWindowFocus: true,
  });
}

客户端状态——Zustand store 一律放在 packages/core(即便是视图相关的 filter/viewMode 也算纯状态),并用工作区感知的 persist;WS 事件只做失效、绝不直接写 store(保持 Query 缓存为唯一真相,避免竞态)。一个任务生命周期事件会把一组与 Agent presence 相关的 query 全部置 stale。

packages/core/realtime/use-realtime-sync.ts — task 前缀处理器
task: () => {
  const wsId = getCurrentWsId();
  if (!wsId) return;
  qc.invalidateQueries({ queryKey: agentTaskSnapshotKeys.list(wsId) });
  qc.invalidateQueries({ queryKey: agentActivityKeys.last30d(wsId) });
  qc.invalidateQueries({ queryKey: agentRunCountsKeys.last30d(wsId) });
  qc.invalidateQueries({ queryKey: agentTasksKeys.all(wsId) });
  qc.invalidateQueries({ queryKey: ["issues", "tasks"] });  // 前缀匹配所有 issue
}
// task:message 故意不走该前缀——长任务每条流式消息都来一次会引发失效风暴。

关键设计:Agent 的 presence(在线性 AgentAvailability × 负载 Workload)是纯前端派生(agents/types.ts、derive-presence.ts)——后端只提供 agent + runtime + 最近任务快照,前端从同一份 agentTaskSnapshot 缓存推导所有列表小圆点/悬浮卡,零额外网络。

本节失败模式
  • Zustand 无限重渲染:selector 返回新对象/数组(s => ({a,b}))每次都不同——必须选基本量或用 shallow 比较(CLAUDE.md Common Zustand footguns)。
  • 双真相源漂移:把服务端数据复制进 Zustand → 两个真相源必然漂移(硬规则禁止)。
  • “无故空列表”:agentRunningFilter 故意 NOT 持久化——running 状态秒级变化,持久化开关会让用户回来面对空列表(partialize 排除该字段)。
04
boundaries · how they're enforced

包边界强制

本仓库最反直觉的发现:边界不是靠 ESLint no-restricted-imports 或 dependency-cruiser 强制的(全仓搜索 no-restricted-imports/dependency-cruiser/no-restricted-paths 均无命中)。

强制手段是依赖声明 + TypeScript 解析失败 + phantom-dep lint 三者叠加:1) 包的 package.json 决定它能 import 什么。@multica/ui 的 deps 里根本没有 @multica/core;@multica/views 的 deps/peer 里根本没有 next 或 react-router-dom;@multica/core 把 react-dom 只放在 devDependencies(测试用),生产代码无法依赖它。2) phantom-dep 规则把“偷用未声明依赖”变成 CI 报错(eslint-plugin-import-x/no-extraneous-dependencies):

packages/eslint-config/base.js — phantom-dep 拦截(节选)
"import-x/no-extraneous-dependencies": ["error", {
  devDependencies: [ "**/*.test.{ts,tsx}", "**/test/**",
    "**/electron.vite.config.*", "**/src/main/**", "**/src/preload/**", /* … */ ],
  peerDependencies: true,
}];

于是 @multica/ui 里若有人 import {…} from "@multica/core",因为 core 不在 ui 的 package.json,lint 直接 no-extraneous-dependencies 失败;views 里 import "next/navigation" 同理失败。CLAUDE.md 的 Dependency Declaration Rule 明确:禁止依赖 pnpm hoist 解析未声明的 import(会导致 peer-dep 变体下的生产构建失败)。3) CLAUDE.md Package Boundary Rules 把这些写成硬约束:core 零 react-dom/零 localStorage(用 StorageAdapter)/零 process.env;ui 零 @multica/core;views 零 next/*/零 react-router-dom/零 store;框架 API 只允许出现在各 app 的 platform/ 层。

i18n · 正向拦截

唯一主动防火墙

唯一被 ESLint 主动“正向拦截”的边界是 i18n——packages/views/eslint.config.mjs 开 i18next/no-literal-string,JSX 里写裸字符串直接编译报错。

潜在弱点

约定 + 评审

因为没有显式 import 防火墙,若有人把 next 误加进 views 的 package.json,边界即被打开——这是“约定 + 评审”而非“工具硬隔离”。

被动失败链:包依赖图(package.json deps)→ TS 找不到模块 → phantom-dep lint 报错——三层叠加把“偷用未声明依赖”在 CI 拦下,而非靠单一显式规则。
05
agents · assign · claim · skill

Agent 管理

任务分配、进度跟踪、技能积累——核心在 server/internal/service/task.go(TaskService,2362 行)。任务分配——polymorphic assignee:Issue 的 assignee 可以是 member 或 agent(assignee_type + assignee_id)。

把 issue 指派给 Agent(或在评论里 @ 它)即触发入队:

server/internal/service/task.go — enqueueIssueTask
func (s *TaskService) enqueueIssueTask(ctx, issue, triggerCommentID, forceFreshSession) (db.AgentTaskQueue, error) {
    if !issue.AssigneeID.Valid { return ..., fmt.Errorf("issue has no assignee") }
    agent, _ := s.Queries.GetAgent(ctx, issue.AssigneeID)
    if agent.ArchivedAt.Valid { return ..., fmt.Errorf("agent is archived") }
    if !agent.RuntimeID.Valid { return ..., fmt.Errorf("agent has no runtime") }

    task, _ := s.Queries.CreateAgentTask(ctx, db.CreateAgentTaskParams{
        AgentID: issue.AssigneeID, RuntimeID: agent.RuntimeID, IssueID: issue.ID,
        Priority: priorityToInt(issue.Priority),
        TriggerCommentID: triggerCommentID,
        TriggerSummary:   s.buildCommentTriggerSummary(ctx, triggerCommentID), // 200 rune 摘要
        ForceFreshSession: ...,
    })
    // 先广播 queued,再通知 daemon——保证客户端按 queued→dispatch 顺序观测
    s.broadcastTaskEvent(ctx, protocol.EventTaskQueued, task)
    s.NotifyTaskEnqueued(ctx, task)
}

入队后不存上下文快照——Agent 在 runtime 里通过 multica CLI 现取所需数据。除 issue 任务外还有 EnqueueChatTask、EnqueueQuickCreateTask(自然语言→multica issue create)、EnqueueTaskForSquadLeader(Squad 路由层,leader 代理再分派)等变体。认领——daemon pull + 容量约束:runtime(本地 daemon/云)调 /tasks/claim,服务端原子认领并尊重 max_concurrent_tasks:

server/internal/service/task.go — ClaimTask
func (s *TaskService) ClaimTask(ctx, agentID) (*db.AgentTaskQueue, error) {
    agent, _ := s.Queries.GetAgent(ctx, agentID)
    running, _ := s.Queries.CountRunningTasks(ctx, agentID)
    if running >= int64(agent.MaxConcurrentTasks) { return nil, nil }  // 无容量
    task, err := s.Queries.ClaimAgentTask(ctx, agentID)                  // 原子取下一条 queued
    if errors.Is(err, pgx.ErrNoRows) { return nil, nil }
    s.ReconcileAgentStatus(ctx, agentID)
    s.broadcastTaskDispatch(ctx, task)
}

ClaimTaskForRuntime 还有两个性能/正确性机制:EmptyClaim 缓存(Redis)让稳态空队列跳过 Postgres 扫描,并在每次 enqueue 时同步失效;ReclaimStaleDispatchedTaskForRuntime 用 90s 恢复窗口回收“claim 响应丢失”的 dispatched 任务,避免双派发。进度跟踪——状态机 + WS 事件:任务生命周期 queued → dispatched → running → completed/failed/cancelled,另有 waiting_local_directory(等本地路径锁)。每次转移都 broadcastTaskEvent,前端据此失效 presence 缓存。

issue 状态不由服务端在任务完成时改——由 Agent 自己通过 CLI 管理(StartTask/CompleteTask 注释明确 “Issue status is NOT changed here”)。CompleteTask 还保证“每个完成的 issue 任务至少留一条 Agent 评论”,并对 chat 任务在同一事务里更新 resume 指针避免竞态。

技能积累——skill 表 + agent_skill junction + 内置 skill:Skill 是工作区级实体(skill 表含 content = SKILL.md 正文 + skill_file 子文件),Agent 与 skill 多对多:

server/pkg/db/queries/skill.sql — agent_skill 多对多
-- name: AddAgentSkill :exec
INSERT INTO agent_skill (agent_id, skill_id) VALUES ($1, $2) ON CONFLICT DO NOTHING;

-- name: ListAgentSkills :many
SELECT s.* FROM skill s JOIN agent_skill ask ON ask.skill_id = s.id
WHERE ask.agent_id = $1 ORDER BY s.name ASC;

Agent 模板物化时按 name find-or-create skill(GetSkillByWorkspaceAndName,复用已存在的 skill_id,避开 UNIQUE(workspace_id,name))。前后端用同构的 frontmatter 解析(server/internal/skill/frontmatter.go 与 packages/core/skills/frontmatter.ts 共用同一正则)。此外 builtin_skills/* 是随产品发给 Agent 的源码可追溯契约——CLAUDE.md 要求改 CLI/API/行为时必须同 PR 更新对应 SKILL.md 与 references/*-source-map.md,否则会“悄悄教 Agent 过时行为”。

列表接口省略 content 列:ListSkillSummariesByWorkspace/ListAgentSkillSummaries 故意不带 content 列——曾因每行带 SKILL.md 正文导致高延迟地区 CLI 15s 超时(GH #2174)。
06
web + desktop · one codebase

双端共享架构

两端都把根组件包进 <CoreProvider>(初始化 api/auth/chat store + QueryClient + WS),并各自提供 NavigationAdapter——一个共享抽象接口。

packages/views/navigation/types.ts — 共享抽象
export interface NavigationAdapter {
  push(path: string): void; replace(path: string): void; back(): void;
  pathname: string; searchParams: URLSearchParams;
  getShareableUrl: (path: string) => string;
  openInNewTab?: (path, title?, opts?) => void;   // 仅 desktop
  prefetch?: (path: string) => void;               // 仅 web → router.prefetch
}

Web 实现极薄(直接转发 next/navigation);Desktop 实现承载全部平台差异——多 tab、memory router、跨工作区切换、overlay:

apps/desktop/src/renderer/src/platform/navigation.tsx — push 节选
push: (path) => {
  if (path === "/login") { useAuthStore().getState().logout(); return; }
  if (tryRouteToOverlay(path, active?.router)) return;        // /workspaces/new 等 → WindowOverlay
  if (tryRouteToOtherWorkspace(path)) return;                 // 跨工作区 → switchWorkspace(slug,path)
  if (tryRouteToPinnedNewTab(path)) return;                   // 钉住 tab 的 push → 开新 tab
  active?.router.navigate(path);
}

这样 @multica/views 里的页面只调 useNavigation().push()/<AppLink>,对“自己跑在 Web 还是 Desktop”完全无感。CLAUDE.md 的 No-Duplication Rule 把决策树写死:依赖 Next/Electron → 留各自 app;依赖 react-router-dom/next/navigation → 留 app 的 platform/;其余一律进 core(headless)或 views(UI)。需要两端不同表现时用 props/slot(extra、topSlot)注入差异,而非复制逻辑。

CoreProvider

模块级单例

auth/chat store 做成模块级单例(首渲染 useMemo([]) 一次性 init),依赖 Vite HMR 保留模块级状态以跨热重载存活;并通过 ClientIdentity(platform/version/os)给所有 HTTP/WS 打标。

version skew

响应必过 zod

older desktop 必然连更新的 server,所有响应都要 parseWithFallback + zod、不得裸 as、enum 漂移降级不崩溃(源于 #2143/#2147/#2192)。

07
monorepo · turbo + pnpm

Monorepo 组织

Turbo + pnpm workspace。pnpm-workspace.yaml 声明 workspace(apps/*、packages/*)并用 catalog 钉版本——所有共享依赖写 "pkg": "catalog:",保证全仓单一版本。

pnpm-workspace.yaml — catalog 钉版本(节选)
packages:
  - "apps/*"
  - "packages/*"
catalog:
  react: "19.2.3"
  zustand: "^5.0.0"
  "@tanstack/react-query": "^5.96.2"
  zod: "^4.1.5"          # 防御 API 漂移
  tailwindcss: "^4"
  vitest: "^4.1.0"

turbo.json 定义任务图——build/typecheck/test/lint 均 dependsOn: ["^typecheck"] 或 ["^build"](先建上游包),dev 标 persistent + cache:false:

turbo.json — 任务依赖图
{
  "tasks": {
    "build":     { "dependsOn": ["^build"], "outputs": [".next/**","dist/**","out/**"] },
    "dev":       { "cache": false, "persistent": true },
    "typecheck": { "dependsOn": ["^typecheck"] },
    "test":      { "dependsOn": ["^typecheck"] },
    "lint":      { "dependsOn": ["^typecheck"] }
  }
}

根 package.json 脚本全部经 turbo + --filter:dev:web=turbo dev --filter=@multica/web,build/typecheck/test/lint 都 --filter=!@multica/mobile(移动端独立、不进根流水线)。后端不在 turbo 图里——Go 侧走 Makefile(make server/make sqlc/make migrate-up/make test),make dev 一键拉起 DB+迁移+前后端,并自动识别 git worktree(每个 checkout 独立 DB 名 + 端口,共用一个 PostgreSQL 容器)。

Internal Packages 的代价:共享包不预编译,exports 直接指向 .ts/.tsx(如 core 的 "./issues/queries": "./issues/queries.ts"),由消费端打包器编译,换取零配置 HMR + 即时跳定义。代价是消费端必须能编译这些源(Next/electron-vite 都配了 @source 扫描共享包让 Tailwind 看见类名)。
08
failure modes · edge cases

关键技术与失败模式

下表汇总从源码与 CLAUDE.md 提炼的边界场景、触发条件与处理方式——多数源于真实事故编号(GH issue)。

表 02 · 边界场景 / 触发 / 处理
场景 / 边界触发条件处理 / 后果
DELETE 返回 204 却 0 行(#1661)原 util.ParseUUID 非法输入静默返回零 UUID改为 loader 解析 + parseUUIDOrBadRequest 三态约定
presence 误读为“离线/失败”历史任务结果污染当前状态presence 纯前端派生,archived 优先于 runtime 健康;只答“此刻”
任务双派发claim 响应丢失,dispatched 无人推进ReclaimStaleDispatchedTaskForRuntime 90s 恢复窗口(> daemon 30+30s 超时)
刚入队任务被空队列缓存挡住EmptyClaim 缓存命中enqueue 同步 bump version;preSelectVersion 比对关闭竞态
并发完成同一任务多 Agent / 重复回调UPDATE … WHERE status='running' 无行 → 幂等成功
切换工作区看到旧数据query key 未带 wsId所有工作区作用域 query 必须以 wsId 为 key 前缀
Zustand 无限重渲染selector 每次返回新对象/数组选基本量或用 shallow 比较
持久化 filter 致“无故空列表”running 状态秒级变化partialize 故意排除 agentRunningFilter,保持 ephemeral
老 desktop 连新 server 白屏(#2143/#2147/#2192)响应 shape 漂移parseWithFallback+zod、禁裸 as、enum 漂移降级、switch 必带 default
包边界被打开误把 next/core 加进错误包的 package.json仅靠依赖图 + phantom-dep lint + 评审被动拦截(无显式防火墙)——潜在弱点
CLI 高延迟地区 15s 超时(#2174)skill 列表每行带 SKILL.md 正文列表查询用 *Summaries(省略 content)
保留 slug 与 TS 副本漂移手改 reserved_slugs.json 未重生成CI 重跑 generate:reserved-slugs 并 fail-on-drift
内置 skill 教 Agent 过时行为改了 CLI/API/行为但没改 SKILL.mdCLAUDE.md 强制同 PR 更新 SKILL.md + *-source-map.md
09
significance · vs the field

生态意义:vs Manus / Cursor / Windsurf

Multica 在“把 Agent 当队友”的赛道上有几处区别于现有产品的取舍。它的工程重心不在“更强的单个 Agent”,而在让一群 Agent 像团队成员一样被管理:可指派、可观测、可累积技能、人机同板协作。

🧩 vendor-neutral

无关模型的任务系统

不绑定单一模型——可对接 Claude Code、Codex、Copilot CLI、OpenCode、Gemini、Cursor Agent、Kimi、Kiro CLI。对比 Cursor Agent / Windsurf(深度耦合各自编辑器与模型、以单人 IDE 回合为中心),Multica 把执行下放给 daemon/云 runtime,自身只做项目管理层——更像“给 Agent 用的 Linear”。

📥 pull queue

拉取式带容量队列

agent_task_queue 的 queued→dispatched→running→completed/failed 生命周期、max_concurrent_tasks、stale 回收、空队列缓存,是一套面向多 Agent 长期运行的调度基础设施。对比 Manus 这类“一个自治 Agent 跑一长链”,Multica 强调多 Agent 并行、可观测进度、人随时介入。

🎓 compound skills

技能可累积可追溯

skill 是工作区级实体(SKILL.md + 子文件),通过 agent_skill 多对多挂到 Agent,模板物化时 find-or-create 复用;内置 skill 是“源码可追溯契约”并由 CI 约束与代码同步。把 README 所说的 “compound skills over time” 落到 schema 与流程上,而非停留在 prompt。

🖥️ isomorphic

真正的 Web + Desktop 同构

靠 NavigationAdapter + CoreProvider + 三层包边界(core 零 react-dom、ui 零 core、views 零框架)实现 Web 与 Electron 共享几乎全部业务代码,仅各自 platform/ 层接框架 API。这套“headless 业务 + 平台适配器”是开源、自托管平台同时维护多端而不爆成本的关键。

Squad 路由层为更大团队提供“把活派给一个由 Agent 领队的小组、领队再分派”的稳定抽象——这是把人类组织结构映射到 Agent 团队的早期尝试,超出了多数 Agent 工具“单 Agent/单会话”的边界。总体而言,Multica 用严格的状态分离与包边界保证这套“多 Agent 当队友”系统在 Web/Desktop 双端长期可维护。

⚠ 待确认清单
  • Stars 数未知(README 用 shields 动态徽章,仓库未含静态数值)。
  • 是否存在 ESLint 之外的 CI 脚本显式校验包依赖图(防止 next/core 被误加进错误包)——未在 .github/workflows 中逐项核实。
  • apps/mobile 内部架构(UI/状态/构建链)未在本次阅读范围内展开,仅确认其与 core 之间为 import type 纯类型耦合。
  • LICENSE 为“改良版 Apache-2.0 + 附加商用/品牌条款”,附加条款的完整法律含义未逐条解读。
Multica · 把 Agent 当队友
multica-ai/multica · Go + Next.js + Electron · 改良版 Apache-2.0
基于 server/ 与 packages/ 源码文本撰写 · 含实际函数名与类型定义
github.com/multica-ai/multica ↗