Khám Phá Multica: Hướng Dẫn Toàn Diện Xây Dựng Nền Tảng Quản Lý Agent AI Mạnh Mẽ

Một hướng dẫn xây dựng chi tiết, có thể thực hiện được, đúc kết từ phân tích sâu mã nguồn của dự án [`multica-ai/multica`](https://github.com/multica-ai/multica) (thu hút hơn 22.000 lượt sao, dung lượng khoảng 42 MB, là một monorepo song ngữ Go và TypeScript).

Nếu bạn chỉ có thể đọc một vài phần trước khi bắt tay vào code, hãy tập trung vào **Mục 3: Ý Tưởng Cốt Lõi** và **Mục 5: Giao Diện Backend Agent**. Mọi khái niệm khác đều được xây dựng dựa trên hai ý tưởng quan trọng này.

📋 Mục Lục Chi Tiết

  1. 🧐 Multica Là Gì – Và Không Là Gì?
  2. ⚡ Mô Hình Tư Duy 30 Giây
  3. 💡 Ý Tưởng Cốt Lõi – Không Tự Xây Dựng Vòng Lặp Agent, Hãy Bọc Lại Nó
  4. 🏗️ Tổng Quan Kiến Trúc
  5. 🔌 Giao Diện Backend Agent (trừu tượng then chốt)
  6. 🔄 Daemon cục bộ – Lấy Dữ Liệu, Đánh Thức, Xử Lý Song Song
  7. 📁 Workdir Riêng Cho Từng Tác Vụ + Chèn Cấu Hình Tự Nhiên
  8. 🧠 Kỹ Năng (Skills) – Lớp Khả Năng Kết Hợp
  9. ▶️ Các Phiên Có Thể Tiếp Tục và Tái Sử Dụng Workdir
  10. 🖥️ Server – Mô Hình Dữ Liệu, Thời Gian Thực, Đa Thuê
  11. ⏰ Autopilot – Tự Động Hóa Theo Lịch và Kích Hoạt
  12. 🖼️ Frontend – Ranh Giới Trạng Thái Nghiêm Ngặt
  13. 📦 Đóng Gói, Phát Hành, Tự Host
  14. 🏆 Các Thực Tiễn Kỹ Thuật Đáng Học Hỏi
  15. 🗺️ Kế Hoạch Xây Dựng Từng Bước (12 Giai Đoạn)
  16. ⚠️ Những Cạm Bẫy Thường Gặp và Các “Lan Can” Khó Khăn
  17. 📋 Cheat Sheet

🧐 1. Multica Là Gì – Và Không Là Gì?

Khẩu hiệu: “Nền tảng agent quản lý mã nguồn mở. Biến các agent lập trình thành đồng đội thực sự – giao nhiệm vụ, theo dõi tiến độ, phát triển kỹ năng.”

Định vị: Một giao diện quản lý dự án kiểu Linear (vấn đề, dự án, bình luận, hộp thư đến, cập nhật thời gian thực) nơi các agent lập trình AI là những thành phần hạng nhất cùng với con người:

  • Một agent có hồ sơ, hiển thị trên bảng, có thể được đề cập bằng `@`.
  • Bạn giao một vấn đề cho một agent giống như giao cho một đồng nghiệp.
  • Một daemon cục bộ trên máy tính xách tay của người dùng sẽ nhận công việc, chạy CLI của agent đã chọn (Claude Code, Codex, Cursor, Gemini, Copilot, OpenCode, …), truyền phát tiến độ và báo cáo lại.
  • Kỹ năng (Skills) (các gói markdown) được chèn vào mọi tác vụ để khả năng được tích lũy.
  • Autopilot là các tự động hóa được kích hoạt theo lịch trình (cron/webhook) để khởi chạy các agent mà không cần giao nhiệm vụ thủ công.

Multica LÀ:

  • Một mặt phẳng điều khiển / lớp điều phối.
  • Một giao diện người dùng quản lý đồng đội (bản sao Linear có agent).
  • Một daemon chạy các CLI của agent và truyền phát sự kiện.
  • Một hệ thống kỹ năng và autopilot.

Multica KHÔNG PHẢI LÀ:

  • Một vòng lặp agent (không gọi LLM, không có trình phân tích công cụ, không RAG).
  • Một thư viện – nó là một nền tảng có thể triển khai được.
  • Bị ràng buộc với một nhà cung cấp mô hình cụ thể – hỗ trợ 11 CLI agent khác nhau.

Người anh em gần nhất về mặt tinh thần là **Linear × LangGraph** – nhưng phần LangGraph được ủy quyền cho bất kỳ CLI agent bên thứ ba nào được cài đặt trên máy của người dùng. **Quyết định này là quan trọng nhất trong toàn bộ codebase.** Hãy hiểu rõ điều này trước khi đi sâu hơn.

⚡ 2. Mô Hình Tư Duy 30 Giây


┌──────────────────┐
│  Browser / Desk  │
│   (Next.js / EL) │
└────────┬─────────┘
         │ HTTPS + WS
┌─────────────▼──────────────┐
│   Server (Go: Chi + WS)    │  ← nguồn dữ liệu đáng tin cậy
│   Postgres + (opt) Redis   │
└────────┬──────────┬────────┘
         │ WS push  │ HTTPS poll
         │ wakeup   │ (mỗi 3s)
┌────────▼──────────▼────────┐
│  Daemon trên máy người dùng│  ← chạy các agent
│  (cùng một binary Go, cobra)│
└────────┬───────────────────┘
         │ exec.Command
┌──────────┬───────▼──────┬───────────┬──────────┐
▼          ▼              ▼           ▼          ▼
claude     codex         cursor       gemini     opencode  ...

Ba thành phần thời gian chạy, tất cả đều từ cùng một monorepo:

Thành phần Được xây dựng từ Chạy ở đâu
Server binary server/cmd/server Cơ sở hạ tầng của bạn (Docker / VPS / k8s)
multica CLI + daemon server/cmd/multica Máy tính xách tay của người dùng (Homebrew / install.sh)
Ứng dụng web apps/web (Next.js) + apps/desktop (Electron) Trình duyệt / Mac / Win / Linux

💡 3. Ý Tưởng Cốt Lõi – Không Tự Xây Dựng Vòng Lặp Agent, Hãy Bọc Lại Nó

Quyết định duy nhất cho phép một nhóm nhỏ triển khai một nền tảng với nhiều tính năng như vậy là:

Ngừng cố gắng trở thành một runtime agent. Hãy trở thành mặt phẳng điều khiển điều phối đến các CLI agent hiện có.

Cụ thể:

  1. Định nghĩa một giao diện Go duy nhấtBackend — với phương thức Execute dạng streaming.
  2. Viết một triển khai cho mỗi CLI (claude, codex, cursor, gemini, …). Mỗi triển khai chỉ là một exec.Command cùng với một trình phân tích cú pháp đầu ra chuẩn dạng streaming.
  3. Chuyển đổi các định dạng JSON đặc trưng của mỗi CLI thành hệ thống phân loại thông điệp thống nhất của riêng bạn (text / thinking / tool-use / tool-result / status / log / error).
  4. Mọi thứ phía trên lớp này (giao nhiệm vụ, lập lịch, bình luận, autopilot, kỹ năng, giao diện người dùng) đều xử lý các agent một cách đồng nhất.

Nếu bạn chỉ áp dụng một ý tưởng kiến trúc từ Multica, thì đây chính là nó. Đây là điều làm cho dự án trở nên khả thi, độc lập với nhà cung cấp và dễ dàng mở rộng (chỉ thêm một tệp mới là có thêm một agent mới).

README của dự án cũng trích dẫn nguồn cảm hứng một cách rõ ràng: _”Nó phản ánh mẫu Happy-CLI AgentBackend, được chuyển đổi sang phong cách Go chuẩn.”_

🏗️ 4. Tổng Quan Kiến Trúc

4.1 🌐 Cấu Trúc Tiến Trình / Dịch Vụ


[Frontend]   → [Go API + WS]   → [Postgres + pgvector]
                  │
                  ↕  Redis streams (tùy chọn, cho việc phân phối đa nút)
                  │
                  ↕  Daemon WS + HTTP poll
                  │
              [Local Daemon] → spawns → [agent CLIs]

4.2 📂 Bố Trí Kho Mã Nguồn (cấp cao nhất)


apps/
  web/           Next.js 16 App Router
  desktop/       Electron (electron-vite)
  docs/          Mintlify/MDX docs
packages/
  core/          Logic không giao diện — zustand stores, react-query, api client (không react-dom)
  ui/            Các thành phần nguyên tử (shadcn / Base UI; không logic nghiệp vụ)
  views/         Các thành phần/trang nghiệp vụ (không next/* hoặc react-router)
server/
  cmd/server/    Điểm vào API HTTP
  cmd/multica/   Điểm vào CLI + daemon (cobra)
  cmd/migrate/   Trình chạy di chuyển cơ sở dữ liệu
  internal/
    handler/     HTTP handlers (Chi)
    service/     Logic nghiệp vụ
    daemon/      Daemon cục bộ
    daemonws/    Hub WS phía daemon
    realtime/    Hub WS hướng người dùng + chuyển tiếp Redis stream
    cli/         Các helper CLI
    auth/        JWT + Google OAuth
    middleware/  Auth, CSP, request log
    events/      Bus sự kiện nội bộ
  pkg/
    agent/       *** Giao diện Backend + 11 triển khai ***
    db/queries/  Đầu vào sqlc
    db/generated/ Đầu ra sqlc
  migrations/    156 tệp SQL (Postgres)
  sqlc.yaml
e2e/             Playwright (chống lại docker-compose đầy đủ)
.github/workflows/  ci.yml, desktop-smoke.yml, release.yml
.goreleaser.yml
Makefile
docker-compose.{,selfhost.,selfhost.build.}yml

4.3 ⚙️ Ngăn Xếp Công Nghệ (các phần cốt lõi)

Server (Go 1.26)

  • github.com/go-chi/chi/v5 — router + middleware chain
  • jackc/pgx/v5 + pgxpool — Postgres
  • sqlc — SQL có kiểu → Go (đầu vào: pkg/db/queries/, đầu ra: pkg/db/generated/)
  • gorilla/websocket — cả WS hướng người dùng và hướng daemon
  • redis/go-redis/v9 — phân tán tùy chọn
  • golang-jwt/jwt/v5 — xác thực
  • spf13/cobra — CLI cho binary multica
  • robfig/cron/v3 — lập lịch autopilot
  • resend-go — email
  • aws-sdk-go-v2/s3 + CloudFront signed URLs
  • prometheus/client_golang — đo lường
  • stdlib log/slog + lmittmann/tint (đẹp trong dev)

Frontend (TS / React 19)

  • React 19, TS 5.9, Vite, Tailwind v4
  • Zustand 5 cho trạng thái client, TanStack Query 5 cho trạng thái server — phân tách nghiêm ngặt
  • TanStack Table 8
  • Vitest 4 + Testing Library, Playwright cho e2e
  • Turborepo cho điều phối, pnpm catalog để ghim phiên bản thống nhất

Cơ sở hạ tầng

  • PostgreSQL 17 + pgvector
  • Redis 7 (tùy chọn)
  • GoReleaser cho các binary CLI (mac/linux/win × amd64/arm64)
  • Homebrew tap (multica-ai/homebrew-tap) tự động xuất bản khi gắn tag
  • Docker images trên GHCR để tự host

🔌 5. Giao Diện Backend Agent (trừu tượng then chốt)

Mọi thứ dưới đây đều nằm trong server/pkg/agent/. **Khi tái tạo dự án này, hãy đọc agent.go trước tiên.**

5.1 🔗 Giao Diện


package agent

type Backend interface {
    Execute(ctx context.Context, prompt string, opts ExecOptions) (*Session, error)
}

type ExecOptions struct {
    Cwd                        string
    Model                      string
    SystemPrompt               string
    MaxTurns                   int
    Timeout                    time.Duration
    SemanticInactivityTimeout  time.Duration  // ngắt nếu không có sự kiện ngữ nghĩa trong N
    ResumeSessionID            string         // tiếp tục phiên agent trước đó
    CustomArgs                 []string       // được thêm vào sau các flag của chúng ta
    McpConfig                  json.RawMessage // được ghi vào tệp tạm thời, --mcp-config <path>
}

type Session struct {
    Messages <-chan Message  // được truyền phát; đóng khi agent thoát
    Result   <-chan Result   // chính xác một Result, sau đó đóng
}

type Message struct {
    Type      MessageType    // text | thinking | tool-use | tool-result | status | error | log
    Content   string
    Tool      string
    CallID    string
    Input     map[string]any
    Output    string
    Status    string
    Level     string
    SessionID string
}

type Result struct {
    Status     string  // completed | failed | aborted | timeout | cancelled
    Output     string
    Error      string
    DurationMs int64
    SessionID  string
    Usage      map[string]TokenUsage // theo từng mô hình: input/output/cache_read/cache_write
}

5.2 🏭 Factory


func New(name string, cfg Config) (Backend, error) {
    switch name {
    case "claude":   return newClaude(cfg)
    case "codex":    return newCodex(cfg)
    case "cursor":   return newCursor(cfg)
    case "gemini":   return newGemini(cfg)
    case "copilot":  return newCopilot(cfg)
    case "opencode": return newOpenCode(cfg)
    case "openclaw": return newOpenClaw(cfg)
    case "hermes":   return newHermes(cfg)
    case "pi":       return newPi(cfg)
    case "kimi":     return newKimi(cfg)
    case "kiro":     return newKiro(cfg)
    }
    return nil, fmt.Errorf("unknown backend %q", name)
}

5.3 📐 Mẫu Triển Khai Tiêu Chuẩn (Code của Claude)

claude.go (~17 KB) là backend rõ ràng nhất để nghiên cứu. Vòng lặp streaming là mẫu chung:


cmd := exec.CommandContext(ctx, c.path, args...)
cmd.Dir = opts.Cwd
cmd.Env = mergedEnv
stdout, _ := cmd.StdoutPipe()
stdin,  _ := cmd.StdinPipe()
stderrTail := newStderrTail(64 * 1024)  // bộ đệm vòng tròn giới hạn
cmd.Stderr = stderrTail

cmd.Start()
io.WriteString(stdin, prompt)  // truyền prompt qua stdin
stdin.Close()

scanner := bufio.NewScanner(stdout)
scanner.Buffer(make([]byte, 0, 1024*1024), 10*1024*1024)  // dòng 10 MB

for scanner.Scan() {
    var msg claudeSDKMessage
    if json.Unmarshal(scanner.Bytes(), &msg); err != nil { continue }
    switch msg.Type {
    case "assistant": handleAssistant(msg)  // text / thinking / tool-use; đếm token
    case "user":      handleUser(msg)       // tool-result
    case "system":    trySend(MessageStatus{...})
    case "result":    finalOutput, finalStatus, finalSessionID = ...
    case "log":       trySend(MessageLog{...})
    }
}

exitErr := cmd.Wait()
result := Result{
    Status:    classify(exitErr, finalStatus, ctx.Err()),
    Output:    finalOutput,
    Error:     errorWithStderrTail(exitErr, stderrTail),  // quan trọng: lỗi V8/bun abort chỉ hiển thị "exit 3"
    SessionID: finalSessionID,
    Usage:     usageMap,
    DurationMs: ...,
}

5.4 🔍 Những Điểm Đặc Thù Cần Biết Của Từng Backend

Backend Chi tiết đáng chú ý
claude.go Sử dụng --output-format stream-json (NDJSON qua stdout); tự động chấp thuận tất cả các yêu cầu điều khiển sử dụng công cụ vì việc chấp thuận của con người diễn ra ở cấp độ vấn đề/bình luận.
codex.go (33 KB) Khởi chạy codex app-server; CODEX_HOME riêng cho từng tác vụ để kỹ năng không làm ô nhiễm hệ thống; chính sách sandbox thay đổi theo phiên bản được phát hiện (codex_sandbox.go).
hermes.go / kimi.go / kiro.go Nói giao thức ACP.
cursor.go Có các tệp đặc trưng theo nền tảng (cursor_invocation_windows.go) cho các quirks của Windows.
openclaw.go Không đọc AGENTS.md từ workdir, do đó system prompt được truyền trực tiếp.
models.go (27 KB) Danh mục tĩnh + ListModels() mà daemon truy vấn theo nhịp tim cho bộ chọn mô hình của giao diện người dùng.
version.go DetectVersion(ctx, path) chạy <bin> --version; CheckMinVersion(name, version) là cổng ngăn daemon đăng ký một runtime quá cũ.
stderr_tail.go Bộ đệm vòng tròn giới hạn 64 KB. Quan trọng: nếu không có nó, các lỗi native trong CLI cơ bản sẽ hiển thị là "exit status 3" mà không có chẩn đoán.
proc_other.go / proc_windows.go Các helper đa nền tảng cho nhóm tiến trình + ẩn cửa sổ.

5.5 🏆 Vì Sao Thiết Kế Này Đáng Giá

  • Thêm một agent = một tệp Go. Chỉ vậy thôi. Không thay đổi giao thức, không di chuyển DB, không thay đổi giao diện người dùng.
  • Không bị khóa nhà cung cấp. Người dùng giữ các gói đăng ký / khóa API / cấu hình riêng cho bất kỳ CLI nào họ muốn.
  • Không có rủi ro lỗi thời. CLI của agent tốt hơn → nền tảng của bạn tốt hơn, miễn phí.
  • Bề mặt lỗi được giới hạn. Một sự cố CLI không làm sập server của bạn.

🔄 6. Daemon cục bộ – Lấy Dữ Liệu, Đánh Thức, Xử Lý Song Song

server/internal/daemon/daemon.go (~53 KB). Chạy trên máy người dùng thông qua multica daemon start.

6.1 🔄 Vòng Đời (`Daemon.Run`)

  1. Gắn cổng sức khỏe sớm (mặc định :19514)
    • → Điểm cuối /health
    • → Lỗi nhanh nếu một daemon khác đang chạy
  2. resolveAuth() — tải token từ ~/.multica/config.json
  3. syncWorkspacesFromAPI — cho mỗi không gian làm việc mà người dùng thuộc về:
    • – Kiểm tra từng CLI agent qua exec.LookPath
    • – Chạy agent.DetectVersion + CheckMinVersion
    • – Gửi POST /api/daemon/register với {name, type, version, status}
    • – Cache các runtimeID trả về
  4. Khởi động các goroutine nền:
    • workspaceSyncLoop (30s) — đồng bộ lại tư cách thành viên không gian làm việc
    • taskWakeupLoop — mở daemon WS, lắng nghe các sự kiện đánh thức tức thì
    • heartbeatLoop (15s) — POST /api/daemon/heartbeat

      phản hồi có thể đính kèm: PendingUpdate, PendingModelList, PendingLocalSkills, PendingLocalSkillImport
    • gcLoop — dọn dẹp ~/multica_workspaces/ cho các vấn đề đã hoàn thành
    • serveHealth — JSON /health cục bộ (thời gian hoạt động, số tác vụ đang hoạt động)
  5. Vào pollLoop (trái tim của daemon)

6.2 🔁 Vòng Lặp Lấy Dữ Liệu (Poll Loop)


sem := make(chan struct{}, cfg.MaxConcurrentTasks)  // mặc định 20

for {
    runtimeIDs := d.allRuntimeIDs()
    for i := 0; i < len(runtimeIDs); i++ {
        sem <- struct{}{}                         // giành một vị trí (khóa nếu đầy)
        rid := runtimeIDs[(pollOffset+i)%len(runtimeIDs)]  // luân phiên
        task, _ := d.client.ClaimTask(ctx, rid)
        if task != nil {
            wg.Add(1); d.activeTasks.Add(1)
            go func(t Task) {
                defer wg.Done()
                defer d.activeTasks.Add(-1)
                defer func() { <-sem }()           // giải phóng vị trí
                d.handleTask(ctx, t)
            }(*task)
            break  // đã nhận được gì đó; chờ trước vòng tiếp theo
        } else {
            <-sem  // không nhận được gì; giải phóng vị trí
        }
    }
    sleepWithContextOrWakeup(ctx, cfg.PollInterval, taskWakeups)
}

Mặc định: PollInterval = 3s, MaxConcurrentTasks = 20, AgentTimeout = 2h.

Kênh đánh thức. taskWakeups được cung cấp bởi daemon WS – khi server đưa một tác vụ vào hàng đợi cho một runtime thuộc daemon này, nó sẽ gửi một tín hiệu đánh thức, và sleepWithContextOrWakeup sẽ trả về ngay lập tức. Điều này mang lại độ trễ nhận tác vụ dưới một giây mà không làm mất đi tính mạnh mẽ của việc polling.

6.3 ⚙️ Quy Trình Xử Lý Theo Từng Tác Vụ (`handleTask` → `runTask`)


1. POST /api/daemon/tasks/{id}/start
2. Gửi tiến độ: "Khởi chạy {provider} (1/2)"
3. Khởi tạo goroutine theo dõi hủy bỏ:
       mỗi 5s: GET /api/daemon/tasks/{id}/status
       nếu status == "cancelled": gọi runCancel() → kill nhóm tiến trình
4. BẢO MẬT: từ chối nếu task.WorkspaceID == ""
   (không có fallback ngầm định đến cấu hình toàn cục người dùng giữa các workspace)
5. Xây dựng TaskContext (vấn đề, agent, kỹ năng, repo, các cờ autopilot/chat/quick-create)
6. execenv.Prepare hoặc execenv.Reuse:
   - {WorkspacesRoot}/{workspace_id}/{task_id_short}/{workdir,output,logs}/
   - Đối với codex: cũng tạo CODEX_HOME riêng cho từng tác vụ
7. execenv.InjectRuntimeConfig — ghi CLAUDE.md / AGENTS.md / GEMINI.md
   vào workdir; ghi các gói kỹ năng vào các thư mục kỹ năng gốc
8. daemon.BuildPrompt(task) → chuỗi prompt
9. Xây dựng agentEnv:
     MULTICA_TOKEN, MULTICA_SERVER_URL, MULTICA_DAEMON_PORT
     MULTICA_WORKSPACE_ID, MULTICA_AGENT_NAME, MULTICA_AGENT_ID, MULTICA_TASK_ID
     [tùy chọn] MULTICA_AUTOPILOT_*, MULTICA_QUICK_CREATE_TASK_ID
     CODEX_HOME (chỉ codex)
     PATH-prepend để agent được khởi tạo có thể gọi `multica` chính nó
   Hợp nhất agent.CustomEnv với một BLOCKLIST để người dùng không thể ghi đè các biến daemon
10. backend, _ := agent.New(provider, cfg)
    session, _ := backend.Execute(ctx, prompt, execOpts)
11. executeAndDrain(session):
       for msg := range session.Messages {
           batch = append(batch, msg)
           if shouldFlush(batch) { client.ReportTaskMessages(taskID, batch) }
       }
       result := <-session.Result
12. Ngay khi agent phát ra SessionID đầu tiên:
       client.PinTaskSession(taskID, sessionID)   // con trỏ tiếp tục an toàn khi gặp sự cố
13. Fallback tiếp tục: nếu Status==failed && PriorSessionID!="" && SessionID==""
       thử lại một lần với ResumeSessionID = ""
14. POST /usage, sau đó /complete (output, branch_name, session_id, work_dir)
                   hoặc /fail (error, session_id, work_dir, failure_reason)
15. Duy trì .gc_meta.json (issue_id, workspace_id, completed_at) để GC
    có thể ánh xạ workdir → issue và thu hồi khi issue hoàn thành|hủy

6.4 🔎 Tự Động Phát Hiện Các CLI Đã Cài Đặt

LoadConfig duyệt qua một danh sách các nhà cung cấp đã biết và kiểm tra từng cái qua exec.LookPath. Chỉ những cái có mặt mới được đăng ký làm runtime. Các ghi đè môi trường cho từng nhà cung cấp tồn tại:


MULTICA_<PROVIDER>_PATH    # ghi đè đường dẫn binary
MULTICA_<PROVIDER>_MODEL   # ghi đè mô hình mặc định

Do đó, daemon thích ứng với bất cứ điều gì đã cài đặt mà không cần cấu hình người dùng – và người dùng có thể ghim các binary cụ thể khi họ muốn.

6.5 🆔 ID Daemon Ổn Định

EnsureDaemonID(profile) ghi một UUID vào ~/.multica/profiles/<name>/daemon.id một lần và sử dụng lại mãi mãi. Nếu không có điều này, việc thay đổi hostname (ví dụ: hậu tố .local xuất hiện/biến mất trên macOS) sẽ tạo ra các hàng runtime trùng lặp trên server. LegacyDaemonIDs(host, profile) được gửi tại thời điểm đăng ký để server có thể hợp nhất các hàng cũ được tạo từ hostname.

6.6 👤 Hồ Sơ (Profiles)

multica setup self-host --profile staging cho phép một máy nói chuyện với nhiều server. Mỗi profile có thư mục ~/.multica/profiles/<name>/ riêng với cấu hình, ID daemon, cổng sức khỏe và thư mục gốc của workspace.

📁 7. Workdir Riêng Cho Từng Tác Vụ + Chèn Cấu Hình Tự Nhiên

Đây là quyết định thiết kế quan trọng thứ hai sau Mục 3. **Mỗi agent tự khởi động thông qua quy ước tệp cấu hình tự nhiên của riêng nó – bạn không cần phải tạo ra một giao thức.**

7.1 📁 Workdir Riêng Cho Từng Tác Vụ


~/multica_workspaces/
  {workspace_id}/
    {task_id_short}/
      workdir/      ← thư mục làm việc của tiến trình agent; nơi chứa git checkout
      output/       ← các đầu ra được thu thập
      logs/         ← stdout/stderr đã ghi lại
      .gc_meta.json ← {issue_id, workspace_id, completed_at}

Sự cô lập là theo từng tác vụ, không phải theo từng vấn đề. Việc tái sử dụng trên cùng một agent+vấn đề là tùy chọn thông qua task.PriorWorkDir.

7.2 🧩 “Meta-Skill” – Tệp Cấu Hình Tự Nhiên Cho Mỗi Nhà Cung Cấp

execenv.InjectRuntimeConfig ghi một tệp cấu hình vào thư mục gốc của workdir mà mỗi agent đọc một cách tự nhiên khi khởi động:

Nhà cung cấp Tệp cấu hình được ghi
claude CLAUDE.md
codex / copilot / opencode / openclaw / hermes / pi / cursor / kimi / kiro AGENTS.md
gemini GEMINI.md

Nội dung được xây dựng bởi buildMetaSkillContent(provider, ctx) và về cơ bản là một **system prompt dạy agent hành động như một đồng đội của Multica**:

  1. Khối nhận dạng — “Bạn là: {tên agent} (ID: …)” + hướng dẫn persona của agent.
  2. Danh mục CLI — mọi lệnh con multica mà agent có thể sử dụng:
    • Đọc: issue get, issue list, issue comment list, workspace members
    • Ghi: issue create, issue update, issue assign, issue label add, issue subscriber add, issue comment add, label create, autopilot create|update|trigger|delete
  3. Quy tắc cứng: luôn truyền --output json để agent nhận được các ID ổn định.
  4. Quy tắc nội dung nhiều dòng: phải sử dụng --content-stdin với HEREDOC (vì bash không mở rộng \n trong chuỗi được đặt trong dấu ngoặc kép – đã quan sát thực nghiệm, được mã hóa cứng như một biện pháp bảo vệ).
  5. Những điều đặc biệt của từng nhà cung cấp — ví dụ: Codex có xu hướng tuân theo lệnh trả lời theo từng lượt một cách nguyên văn → hướng dẫn nó sử dụng --content-stdin.
  6. Phần quy trình làm việc — phân nhánh theo loại tác vụ: chat, quick-create, autopilot run-only, comment-triggered, mặc định.

Giờ đây, agent có _nó là ai_, _những công cụ nào nó có_ và _cách sử dụng chúng_, tất cả đều thông qua định dạng tệp mà nó đã đọc tự nhiên. **Không cần tạo ra giao thức nào cả.**

7.3 📚 Tệp Kỹ Năng (Skill Files) Trong Thư Mục Kỹ Năng Gốc

Các kỹ năng được ghi vào thư mục kỹ năng gốc của mỗi agent:

Nhà cung cấp Thư mục kỹ năng
claude .claude/skills/
codex .codex/skills/
cursor .cursor/skills/
openclaw .openclaw/skills/
opencode .config/opencode/skills/
copilot .github/skills/
pi .pi/skills/
hermes (fallback) .agent_context/skills/

Mỗi agent khám phá chúng thông qua cơ chế tự nhiên của riêng nó. **Bạn ghi vào đĩa; CLI của agent làm phần còn lại.**

🧠 8. Kỹ Năng (Skills) – Lớp Khả Năng Kết Hợp

Một Kỹ năng chỉ đơn giản là:


{ name: string, content: string /* markdown */, files: { path: string, content: string }[] }

Chỉ vậy thôi. Giá trị của nền tảng đến từ **quản lý** (danh mục theo không gian làm việc, liên kết agent, cài đặt từ marketplace, lockfile), chứ không phải từ sự phức tạp của định dạng.

8.1 🔒 Cài Đặt Có Thể Tái Tạo Thông Qua Lockfile

skills-lock.json tại thư mục gốc của repo ghim mỗi kỹ năng marketplace:


{
  "skills": {
    "frontend-design": {
      "source": "github.com/anthropics/skills",
      "ref": "abc123…",
      "computedHash": "sha256:…"
    },
    ...
  }
}

Các nguồn bao gồm anthropics/skills, shadcn/ui, vercel-labs/agent-skills. computedHash giúp xác minh cài đặt.

8.2 ✂️ Tách Biệt Prompt và Skill

Một nguyên tắc tinh tế nhưng quan trọng: **prompt là tối thiểu; kỹ năng mang theo ngữ cảnh.** BuildPrompt(task) là một đoạn văn ngắn cho mỗi loại tác vụ. Mọi thứ mô tả _cách_ nền tảng hoạt động đều nằm trong meta-skill (CLAUDE.md / AGENTS.md), nếu không bạn sẽ phải phát lại chúng trong mỗi prompt.

8.3 🎛️ Tùy Chỉnh Theo Từng Agent

Bảng agent lưu trữ các tùy chỉnh mà người dùng có thể thực hiện đối với hành vi của agent:

  • instructions — persona / system prompt
  • skills[] — ID kỹ năng được liên kết (kết nối với danh mục kỹ năng theo không gian làm việc)
  • custom_env — k/v được chèn vào mỗi tác vụ (với một danh sách chặn phía daemon)
  • custom_args — được thêm vào sau các đối số CLI tích hợp của daemon
  • mcp_config — JSON thô, được ghi vào một tệp tạm thời và truyền --mcp-config <path>
  • model
  • max_concurrent_tasks
  • visibilityworkspace | private

LaunchHeader(provider) được hiển thị trong giao diện người dùng để người dùng xem khung sườn mà custom_args của họ mở rộng.

▶️ 9. Các Phiên Có Thể Tiếp Tục và Tái Sử Dụng Workdir

Các agent lập trình có ngữ cảnh đắt tiền. Vứt bỏ nó sau mỗi lượt là lãng phí. Multica xử lý điều này bằng hai phần trạng thái được chuyển tiếp:

9.1 📌 Ghim Phiên Đang Thực Thi

Ngay khi một backend phát ra SessionID, daemon gọi client.PinTaskSession(taskID, sessionID) → server lưu trữ nó trên hàng tác vụ. **An toàn khi gặp sự cố**: nếu daemon chết giữa tác vụ, con trỏ tiếp tục đã có trên server.

9.2 ▶️ Tiếp Tục Khi Yêu Cầu Kế Tiếp

Khi server giao tác vụ tiếp theo trên cùng một agent+vấn đề, nó bao gồm:

  • PriorSessionID — được truyền lại dưới dạng ExecOptions.ResumeSessionID (ví dụ: claude --resume <id>)
  • PriorWorkDir — daemon gọi execenv.Reuse(...) thay vì execenv.Prepare(...) → cùng một git checkout, cùng một scratchpad

9.3 🔁 Tiếp Tục Dự Phòng

Nếu việc tiếp tục thất bại trước khi thiết lập một phiên (Status==failed && PriorSessionID!="" && SessionID==""), daemon sẽ thử lại **một lần** với ResumeSessionID="" — bắt đầu lại từ đầu. Điều này cứu người dùng khỏi một ID phiên cũ mà không gây vòng lặp vô hạn.

9.4 🗑️ Dọn Rác (GC)

gcLoop dọn dẹp ~/multica_workspaces/:

  • Workdir có vấn đề done|cancelled và cũ hơn MULTICA_GC_TTL (mặc định 24h)
  • Thư mục mồ côi (không có .gc_meta.json) cũ hơn MULTICA_GC_ORPHAN_TTL (mặc định 72h)
  • Server trả về 404 trên vấn đề → dọn dẹp ngay lập tức

🖥️ 10. Server – Mô Hình Dữ Liệu, Thời Gian Thực, Đa Thuê

10.1 🎭 Actors Đa Hình (Polymorphic Actors)

Quyết định lược đồ quan trọng nhất:


issues.assignee_type  CHECK (assignee_type IN ('member', 'agent'))
issues.assignee_id    UUID
comments.author_type  CHECK (author_type IN ('member', 'agent'))
inbox.recipient_type  ...

Một khi bạn cam kết sử dụng tính đa hình trên mọi trường actor, các agent là những thành phần tự do ở mọi nơi trong API — không có điểm cuối đặc biệt, không có giao diện người dùng song song.

10.2 🔒 Đa Thuê (Multi-Tenancy)

  • Mọi truy vấn đều lọc theo workspace_id.
  • Bảng thành viên kiểm soát quyền truy cập (hàng member kết nối userworkspace với một role).
  • Frontend gửi X-Workspace-ID trên mọi yêu cầu để định tuyến đến workspace đang hoạt động.
  • Middleware:
    • Auth(queries) — JWT hoặc PAT
    • DaemonAuth(queries) — token daemon
    • RequireWorkspaceMemberFromURL(queries, "id")
    • RequireWorkspaceRoleFromURL(queries, "id", "owner", "admin")

10.3 💾 Lớp Lưu Trữ Dữ Liệu

  • 156 tệp di chuyển SQL được đánh số (server/migrations/001_init.up.sql …) — **lịch sử bất biến; không bao giờ chỉnh sửa một di chuyển đã áp dụng**.
  • sqlc biến pkg/db/queries/*.sql thành mã Go có kiểu trong pkg/db/generated/.
  • pgxpool được sử dụng xuyên suốt; không có ORM.
  • pgvector được kích hoạt cho tìm kiếm dựa trên embedding (kỹ năng, vấn đề).

10.4 🔗 Phân Lớp: Handler → Service → Repo


handler (Chi routes)  ←  Bộ điều hợp HTTP/WS; không bao giờ chạm vào DB
   ↓
service               ←  Logic nghiệp vụ; giao dịch; gọi nhiều truy vấn
   ↓
queries (sqlc)        ←  Chỉ SQL có kiểu

DI dựa trên hàm tạo:


taskSvc := service.NewTaskService(queries, pool, hub, bus, daemonWakeup)
autoSvc := service.NewAutopilotService(queries, taskSvc, ...)

Không có biến toàn cục. Không có init().

10.5 📡 Bus Sự Kiện Nội Bộ (In-Process Event Bus)

events.Bus là một publisher đồng bộ với các listener dựa trên chủ đề. **Thứ tự đăng ký quan trọng và được ghi lại** trong cmd/server/main.go:


// Subscribers PHẢI đăng ký TRƯỚC các thông báo, vì các thông báo
// phụ thuộc vào danh sách subscriber được cập nhật.
events.RegisterSubscriberListeners(bus, queries)
events.RegisterNotificationListeners(bus, queries, ...)
events.RegisterActivityListeners(bus, queries)
events.RegisterAutopilotListeners(bus, queries, autoSvc)

Khi một service phát ra một sự kiện, các listener ghi trạng thái phái sinh (mục hộp thư đến, hàng hoạt động) và phát ra các sự kiện broadcaster chảy ra qua WS.

10.6 🔌 Hai Hệ Thống Con WebSocket

Đường dẫn Đối tượng Xác thực Mục đích
/ws Trình duyệt / Desktop JWT (PAT hoặc session cookie); kiểm tra origin chống lại ALLOWED_ORIGINS Truyền các cập nhật: vấn đề mới, bình luận, trạng thái hiện diện, tiến độ tác vụ
/api/daemon/ws Daemon Token Daemon Server → daemon đánh thức khi một tác vụ được đưa vào hàng đợi

10.7 🌐 Thời Gian Thực Đơn Nút so với Đa Nút

Không có REDIS_URL: Hub trong tiến trình — một nút API duy nhất.

Với REDIS_URL: realtime.NewShardedStreamRelay sử dụng Redis streams để phân phối sự kiện qua các nút. Khóa sharding + nhóm người tiêu dùng trên mỗi shard. Kênh đánh thức daemon tương tự định tuyến qua daemonws.NewRelayNotifier(hub, sharded) để một runtime được kết nối với nút API A có thể được đánh thức khi nút B nhận tác vụ của nó.

Có một công tắc môi trường legacy / dual / sharded (REALTIME_RELAY_MODE) để triển khai an toàn.

Nguyên tắc chính: không bắt buộc phải có Redis. Tự host đơn nút nên chạy chỉ với Postgres.

10.8 🐛 Phân Tích Cú Pháp UUID Nghiêm Ngặt (một lỗi thực sự)

CLAUDE.md tài liệu ba hàm trợ giúp có tên, phát sinh từ lỗi #1661, nơi một util.ParseUUID chung chung đã âm thầm trả về UUID không, khiến các thao tác DELETE trả về 204 trong khi không khớp hàng nào:


parseUUIDOrBadRequest(s)  // cho đầu vào người dùng — trả về 400 nếu không hợp lệ
parseUUID(s)              // cho các vòng lặp đáng tin cậy — panic → được bắt bởi Recoverer
loadIssueForUser(ctx, queries, key)  // chấp nhận UUID hoặc ID người dùng "MUL-123"
loadAgentForUser(...)

Bài học: **các trình phân tích cú pháp có kiểu tại mọi ranh giới tin cậy**. Không bao giờ tạo một hàm trợ giúp chung chung ẩn lỗi.

⏰ 11. Autopilot – Tự Động Hóa Theo Lịch và Kích Hoạt

server/internal/service/autopilot.go + cron.go. Hai chế độ:

  • create_issue — lập lịch tạo một vấn đề mới và giao cho agent. Quy trình tác vụ bình thường diễn ra sau đó.
  • run_only — không có vấn đề nào tồn tại; lập lịch đưa một tác vụ vào agent_task_queue với ngữ cảnh autopilot. Daemon nhận nó; meta-skill phát hiện MULTICA_AUTOPILOT_RUN_ID và chuyển sang quy trình làm việc autopilot (không gọi multica issue get).

Bảng triggers chứa:

  • cron — biểu thức robfig/cron + múi giờ
  • webhook — băm điểm cuối (mô hình dữ liệu tồn tại, việc điều phối chưa được kết nối theo CLI_AND_DAEMON.md)
  • api — kích hoạt API thủ công (cùng trạng thái)

runAutopilotScheduler(ctx, queries, autopilotSvc) chạy định kỳ; các trigger đến hạn gọi autopilotSvc.RunOnce.

CLI hiện chỉ cung cấp các trigger cron:


multica autopilot trigger-add \
  --cron "0 9 * * 1-5" \
  --timezone "America/New_York"

🖼️ 12. Frontend – Ranh Giới Trạng Thái Nghiêm Ngặt

Đây là nơi kỷ luật của dự án thực sự được thể hiện. Các quy tắc được mã hóa trong CLAUDE.md và được thực thi thông qua ranh giới gói.

12.1 📦 Chia Ba Gói


packages/core/     logic không giao diện
  - zustand stores (TẤT CẢ, kể cả những cái liên quan đến view)
  - react-query hooks
  - api client
  - StorageAdapter, NavigationAdapter (giao diện)
  - KHÔNG react-dom
  - KHÔNG localStorage (sử dụng StorageAdapter)
  - KHÔNG process.env

packages/ui/       các thành phần nguyên tử (biến thể shadcn / Base UI)
  - components/ui/button.tsx, card.tsx, ...
  - KHÔNG nhập @multica/core
  - KHÔNG logic nghiệp vụ

packages/views/    các thành phần/trang nghiệp vụ
  - Một thành phần cho mỗi route (IssuesPage, AutopilotsPage, ...)
  - KHÔNG nhập next/*
  - KHÔNG react-router-dom
  - KHÔNG nhập store trực tiếp (đọc qua core hooks)
  - Định tuyến qua NavigationAdapter

apps/web/          Kết nối Next.js
apps/desktop/      Kết nối Electron
  - Mỗi cái cung cấp StorageAdapter, NavigationAdapter, CoreProvider
  - Đây là lớp DUY NHẤT nơi các API của Next.js / Electron xuất hiện

12.2 🔄 Trạng Thái Server so với Trạng Thái Client

  • TanStack Query cho mọi thứ bắt nguồn từ API. Luôn luôn.
  • Zustand cho trạng thái chỉ có trong UI (chọn, modal, bản nháp, trạng thái hiện diện).
  • Các sự kiện WebSocket làm mất hiệu lực Query. Chúng không bao giờ ghi trực tiếp vào stores.
  • Tất cả các truy vấn có phạm vi workspace đều dựa trên wsId, do đó việc chuyển đổi workspace tự động làm mất hiệu lực bộ nhớ cache.

12.3 🧩 Mẫu Gói Nội Bộ

Các gói xuất các tệp .ts / .tsx thô. Bundler của người tiêu dùng (Vite / Next) biên dịch trực tiếp. Không cấu hình HMR, chuyển đến định nghĩa tức thì, không có bước build giữa các gói.

12.4 📋 Catalog pnpm

pnpm-workspace.yaml khai báo một catalog các phiên bản đã được ghim. Mọi gói đều import "react": "catalog:". Việc cập nhật phiên bản diễn ra ở một nơi duy nhất.

12.5 🚫 Quy Tắc Không Trùng Lặp

“Nếu cùng một logic tồn tại trong cả hai ứng dụng, nó phải được trích xuất vào một gói chia sẻ.”

Quy tắc này thường xuyên được nhắc lại trong CLAUDE.md. Đây là điều giữ cho ứng dụng web và desktop không bị phân tách.

📦 13. Đóng Gói, Phát Hành, Tự Host

13.1 🚀 GoReleaser cho CLI

.goreleaser.yml xây dựng:

  • darwin / linux / windows × amd64 / arm64
  • Cả các tarball **có tên cũ** và **có phiên bản** (tên cũ giúp multica update hoạt động — tương thích ngược)
  • Checksums
  • Tự động xuất bản công thức Homebrew tới multica-ai/homebrew-tap khi gắn tag

Đường dẫn cài đặt cho người dùng:

  • brew install multica-ai/tap/multica
  • curl https://multica.ai/install.sh | sh
  • iwr https://multica.ai/install.ps1 | iex
  • Tất cả các script đều hỗ trợ --with-server để khởi động toàn bộ stack cùng với CLI.

13.2 🐳 Docker cho Server

  • Dockerfile (server) + Dockerfile.web (frontend) — được xuất bản tới GHCR (ghcr.io/multica-ai/multica-backend, multica-web).
  • Ba tệp compose:
    • docker-compose.yml — dev (chỉ Postgres)
    • docker-compose.selfhost.yml — production tự host
    • docker-compose.selfhost.build.yml — ghi đè để build cục bộ

13.3 🔧 Makefile (hướng dẫn quy trình làm việc)

Với 12.5 KB, tệp này được đánh bóng một cách khác thường:


make dev               # khởi động stack dev
make selfhost          # production tự host
make selfhost-build    # build cục bộ thay vì kéo
make selfhost-stop
make check             # pipeline CI đầy đủ cục bộ
make sqlc              # tái tạo SQL có kiểu
make migrate-up / migrate-down / migrate-status
make migrate-new name=add_foo_table
make db-reset          # từ chối nếu DATABASE_URL trỏ đến remote
make worktree-env      # tạo .env.worktree với tên DB + cổng duy nhất
                       # → chạy nhiều git worktrees song song chống lại một Postgres

13.4 ✅ CI

.github/workflows/ci.yml — hai job:

  • frontend — pnpm + Node 22 + turbo build typecheck test --filter='!@multica/docs'
  • backend — Go 1.26 + Postgres 17 + pgvector + Redis 7 services; go build ./..., chạy migrations, go test ./.... REDIS_TEST_URL=redis://localhost:6379/1 riêng cho các bài kiểm tra kỹ năng cục bộ thời gian chạy.

.github/workflows/release.yml — tự động kích hoạt khi gắn tag v*: Go tests → GoReleaser → GitHub Releases + Homebrew tap.

.github/workflows/desktop-smoke.yml — Electron build/package cho mỗi nền tảng.

13.5 🔐 Cổng Tự Host


ALLOW_SIGNUP=false
ALLOWED_EMAIL_DOMAINS=acme.com
ALLOWED_EMAILS=alice@example.com,bob@example.com

Cùng với MULTICA_DEV_VERIFICATION_CODE cho phát triển cục bộ (bị từ chối khi APP_ENV=production).

🏆 14. Các Thực Tiễn Kỹ Thuật Đáng Học Hỏi

Một bộ sưu tập các thực tiễn, được xếp hạng theo mức độ ảnh hưởng:

  1. CLAUDE.md như kinh thánh kỹ thuật (21 KB). Mọi quy tắc kiến trúc đều được ghi lại với số lỗi đã thúc đẩy nó. Quy tắc cứng, lý do cứng. AGENTS.md là một con trỏ 2 KB chỉ nói với các agent đọc CLAUDE.md. Một nguồn thông tin đáng tin cậy duy nhất, các con trỏ mỏng ở mọi nơi khác.
  2. DI dựa trên hàm tạo ở mọi nơi. Không có biến toàn cục. Không có init(). Khả năng mock miễn phí.
  3. Vị trí kiểm thử được quy định: các kiểm thử logic chia sẻ nằm trong gói mà chúng kiểm thử; các kiểm thử wiring đặc trưng cho framework nằm trong ứng dụng. Mỗi tệp Go có một tệp _test.go tương ứng (thường có kích thước tương đương hoặc lớn hơn).
  4. CI sử dụng các dịch vụ Postgres + Redis thật (không phải testcontainers). Nhanh hơn, đơn giản hơn.
  5. Bộ đệm vòng stderr giới hạn cho mọi tiến trình được tạo. Nếu không có điều này, các sự cố native chỉ hiển thị "exit status 3".
  6. Các trường actor đa hình từ ngày đầu (*_type + *_id). Việc điều chỉnh lại rất khó khăn.
  7. Khóa truy vấn có phạm vi workspace. Chuyển đổi tenant tự động làm mất hiệu lực bộ nhớ cache.
  8. Monorepo không cần cấu hình. Các gói xuất TS thô; bundler của người tiêu dùng biên dịch. HMR tức thì + chuyển đến định nghĩa.
  9. Ghim giữa chừng. Ghim trạng thái dễ bay hơi (ID phiên) vào server ngay khi nó được tạo — không chờ hoàn thành.
  10. Makefile thân thiện với worktree. Tạo .env.worktree với tên DB + cổng duy nhất. Chạy N nhánh song song chống lại một Postgres.
  11. Không bắt buộc phải có Redis. Phân tán tùy chọn, mặc định đơn nút.
  12. Phân giải mô hình hai tầng: ghi đè rõ ràng > env toàn cục daemon > mặc định CLI. Không có lựa chọn bắt buộc.
  13. Các biến môi trường MULTICA_* + hợp nhất agent.CustomEnv với danh sách chặn. Người dùng có thể đặt env riêng mà không ghi đè các biến do daemon đặt.
  14. Tự động phát hiện các CLI đã cài đặt qua exec.LookPath. Daemon thích ứng với bất cứ điều gì đã cài đặt; các ghi đè rõ ràng tồn tại khi cần.
  15. chi.Recoverer để các panic từ parseUUID (biến thể đáng tin cậy) không làm sập server — chúng được ghi log và trả về 500.
  16. Thứ tự đăng ký listener được ghi lại trong các bình luận mã, vì nó rất quan trọng.
  17. Bảo vệ an ninh theo từng tenant: daemon từ chối khởi tạo nếu task.WorkspaceID == "". Không có fallback ngầm định đến cấu hình toàn cục người dùng giữa các workspace.
  18. Cổng sức khỏe được gắn đầu tiên. Phát hiện một daemon khác đang chạy trước khi làm bất cứ điều gì khác.
  19. ID daemon ổn định được duy trì trên đĩa. Thay đổi hostname là một nguồn thực sự của các hàng runtime trùng lặp.
  20. Các tarball có tên cũ tương thích ngược để multica update cũ tiếp tục hoạt động mãi mãi.

🗺️ 15. Kế Hoạch Xây Dựng Từng Bước (12 Giai Đoạn)

Xây dựng một bản sao Multica tối thiểu khả thi. Mỗi giai đoạn đều có thể triển khai. Đừng bỏ qua các bước.

🌱 Giai đoạn 1 — Khung Sườn (1 ngày)

  • Khởi tạo monorepo: apps/web, packages/core, packages/ui, packages/views, server/.
  • pnpm workspace + Turborepo.
  • Postgres cục bộ; một migration: user, workspace, member.
  • Xác thực email + mật khẩu (hoặc magic-link) → JWT.
  • Điểm cuối sức khỏe. Bộ định tuyến Chi cơ bản. Ghi log có cấu trúc qua slog.

Hoàn thành khi: make dev khởi động Postgres + Go server + Next.js, bạn có thể đăng ký và thấy workspace của mình.

📝 Giai đoạn 2 — CRUD Vấn Đề (2 ngày)

  • Migrations: issue, issue_label, comment. assignee_type + assignee_id đa hình.
  • sqlc + queries.
  • Handler → service → repo cho vấn đề + bình luận.
  • Giao diện người dùng kiểu Linear: danh sách, chi tiết, modal tạo.
  • TanStack Query cho mọi thứ bắt nguồn từ API.

Hoàn thành khi: Con người có thể tạo, giao, bình luận về các vấn đề, giống như một Linear nhỏ.

🔌 Giai đoạn 3 — WebSocket Hướng Người Dùng (1 ngày)

  • Điểm cuối /ws với JWT auth + kiểm tra origin.
  • events.Bus trong tiến trình. Các listener phát ra sự kiện broadcaster về các thay đổi của vấn đề/bình luận.
  • Client WS frontend làm mất hiệu lực Query trên các sự kiện liên quan.

Hoàn thành khi: Hai tab trình duyệt nhìn thấy các chỉnh sửa của nhau trong thời gian thực.

🔗 Giai đoạn 4 — Giao Diện Backend Agent (1 ngày)

Đây là điểm then chốt. Hãy làm đúng.

  • server/pkg/agent/agent.go — giao diện, kiểu, factory.
  • claude.go — triển khai đầu tiên. Trình phân tích cú pháp stdout streaming, bộ đệm stderr giới hạn, dịch theo loại thông điệp sang phân loại của bạn.
  • version.go, models.go.
  • Unit test với một CLI giả (một shell script in NDJSON đóng gói).

Hoàn thành khi: Một unit test có thể chạy Backend.Execute("hello") chống lại một fixture stdout giả và quan sát luồng thông điệp thống nhất + kết quả cuối cùng.

🔄 Giai đoạn 5 — Khung Sườn Daemon Cục Bộ (2 ngày)

  • Cobra CLI: multica daemon start.
  • Gắn cổng sức khỏe (lỗi nhanh). ID daemon ổn định được duy trì trên đĩa.
  • LoadConfig kiểm tra các CLI đã cài đặt qua exec.LookPath.
  • POST /api/daemon/register.
  • Vòng lặp heartbeat.

Hoàn thành khi: Daemon khởi động, đăng ký một runtime, server hiển thị nó trực tuyến.

✅ Giai đoạn 6 — Vòng Đời Tác Vụ Từ Đầu Đến Cuối (3 ngày)

  • DB: bảng agent, agent_task_queue, runtime, task.
  • Điểm cuối server: nhận tác vụ, bắt đầu, thông điệp (batch), sử dụng, hoàn thành, thất bại, trạng thái.
  • Vòng lặp lấy dữ liệu daemon với semaphore + luân phiên.
  • Workdir theo tác vụ: ~/multica_workspaces/{ws}/{task}/workdir/.
  • Chèn CLAUDE.md (hoặc AGENTS.md) vào thư mục gốc của workdir với một meta-skill tối thiểu.
  • Xây dựng agentEnv với các biến MULTICA_*; hợp nhất agent.CustomEnv với danh sách chặn.
  • Chạy agent → truyền thông điệp → báo cáo.

Hoàn thành khi: Giao diện người dùng hiển thị đầu ra trực tiếp từng token cho một vấn đề đã được giao thực tế.

🧠 Giai đoạn 7 — Kỹ Năng + Chèn Cấu Hình Theo Nhà Cung Cấp (1 ngày)

  • Mô hình kỹ năng: { name, content, files[] }. Danh mục theo workspace.
  • Ghi kỹ năng vào các thư mục gốc (.claude/skills/, v.v.).
  • Xây dựng nội dung meta-skill: nhận dạng + danh mục CLI + quy trình làm việc.
  • Thêm các lệnh con CLI multica issue để agent có thể gọi chúng: get, list, comment add (với --content-stdin), update, assign, label add.

Hoàn thành khi: Một agent trên một vấn đề được giao gọi multica issue getmultica issue comment add và các bình luận xuất hiện trong giao diện người dùng do agent tạo.

⚡ Giai đoạn 8 — Đánh Thức Daemon Qua WS (½ ngày)

  • Điểm cuối /api/daemon/ws.
  • daemonws.Hub với các kênh đánh thức tác vụ cho mỗi runtime.
  • sleepWithContextOrWakeup trả về ngay lập tức khi đánh thức.

Hoàn thành khi: Độ trễ từ “giao nhiệm vụ” đến “thông điệp agent đến” là < 1s, không phải 3s.

▶️ Giai đoạn 9 — Các Phiên Có Thể Tiếp Tục (1 ngày)

  • PinTaskSession giữa chừng.
  • Chuyển tiếp PriorSessionID + PriorWorkDir trên yêu cầu tiếp theo.
  • execenv.Reuse vs execenv.Prepare.
  • Fallback tiếp tục: thử lại một lần với ResumeSessionID trống nếu việc tiếp tục thất bại trước khi thiết lập một phiên.
  • Vòng lặp GC cho ~/multica_workspaces/.

Hoàn thành khi: Hai bình luận liên tiếp trên cùng một vấn đề không bị mất ngữ cảnh, và các workdir của các vấn đề đã hoàn thành được dọn dẹp.

➕ Giai đoạn 10 — Thêm Backend Thứ Hai + Ba (1 ngày)

  • gemini.go (đơn giản hơn, stream-json). codex.go (phức tạp hơn, chế độ app-server + CODEX_HOME theo tác vụ).
  • Xác minh trừu tượng hóa được giữ vững — không thay đổi lược đồ, không thay đổi giao diện người dùng.

Hoàn thành khi: Giao diện người dùng hiển thị bộ chọn mô hình với nhiều nhà cung cấp, và việc giao cho một agent khác sử dụng một CLI khác.

⏰ Giai đoạn 11 — Autopilot (1 ngày)

  • Bảng autopilot + trigger.
  • Bộ lập lịch robfig/cron/v3 trong một goroutine.
  • Chế độ RunOnce: đưa một tác vụ vào hàng đợi với ngữ cảnh autopilot (env MULTICA_AUTOPILOT_*).
  • Nhánh meta-skill cho chạy autopilot.
  • Chế độ CreateIssue: bộ lập lịch tạo một vấn đề và giao nó.
  • CLI: multica autopilot create / trigger-add / list / delete.

Hoàn thành khi: Một autopilot được kích hoạt bằng cron hoạt động và tạo ra đầu ra trong giao diện người dùng mà không cần can thiệp của con người.

📦 Giai đoạn 12 — Đóng Gói + Tự Host (1 ngày)

  • Cấu hình GoReleaser: mac/linux/win × amd64/arm64.
  • Tự động xuất bản Homebrew tap khi gắn tag.
  • install.shinstall.ps1 phát hiện Homebrew nếu có.
  • Image GHCR cho server + web.
  • docker-compose.selfhost.yml cho người dùng cuối.
  • Cổng xác thực: ALLOW_SIGNUP, ALLOWED_EMAILS, ALLOWED_EMAIL_DOMAINS.

Hoàn thành khi: Một người lạ có thể brew install you/tap/yourcli && yourcli setup self-host chống lại một backend đã được Docker-Compose.

⚠️ 16. Những Cạm Bẫy Thường Gặp và Các “Lan Can” Khó Khăn

Đây là những lỗi thực tế mà Multica đã ghi lại trong CLAUDE.md — hãy tham khảo chúng thay vì tự mình khám phá lại.

Cạm bẫy Lan can bảo vệ
ParseUUID chung chung trả về UUID không một cách âm thầm → DELETEs trả về 204 không khớp gì. Ba hàm trợ giúp có tên: parseUUIDOrBadRequest (ranh giới đầu vào), parseUUID (đáng tin cậy, panic), loadXForUser (chấp nhận UUID hoặc ID người dùng như MUL-123).
Các sự cố CLI native hiển thị là "exit status 3" mà không có chẩn đoán. Bộ đệm vòng stderr giới hạn; đính kèm 64 KB cuối cùng vào Result.Error.
Thay đổi hostname tạo ra các hàng runtime trùng lặp. Lưu ID daemon vào đĩa; báo cáo các ID cũ được tạo từ hostname tại thời điểm đăng ký để server có thể hợp nhất.
Daemon âm thầm sử dụng cấu hình toàn cục người dùng giữa các workspace. Từ chối khởi tạo nếu task.WorkspaceID == "".
Hai daemon chạy trên một máy → tranh chấp. Gắn cổng sức khỏe đầu tiên; lỗi nhanh.
Người dùng CLI agent ghi đè các biến môi trường do daemon đặt. Danh sách chặn trên việc hợp nhất agent.CustomEnv vào agentEnv.
Ký tự \n trong chuỗi bash được đặt trong dấu ngoặc kép không mở rộng → bình luận agent nhiều dòng bị sai lệch. Quy tắc được mã hóa cứng trong meta-skill: luôn sử dụng --content-stdin với HEREDOCs.
Tiếp tục với ID phiên cũ thất bại một cách âm thầm. Fallback tiếp tục: thử lại một lần với ResumeSessionID trống.
Workdir tăng không giới hạn. Vòng lặp GC với MULTICA_GC_TTL (mặc định 24h) và TTL cho các tệp mồ côi. 404 trên vấn đề → dọn dẹp ngay lập tức.
Daemon WS chết → các sự kiện đánh thức bị mất một cách âm thầm. Vòng lặp lấy dữ liệu luôn bật làm lớp dưới cùng; WS chỉ là một bộ tăng tốc.
Thứ tự đăng ký listener khiến các thông báo bỏ lỡ người đăng ký. Ghi lại thứ tự trong các bình luận mã; người đăng ký đăng ký trước các thông báo.
Người dùng Anthrope chạy nhiều worktree xung đột trên Postgres. make worktree-env tạo .env.worktree với tên DB + cổng duy nhất.
Các binary CLI cũ bị hỏng sau khi đổi tên. Các tarball có tên cũ tương thích ngược cùng với các tarball có phiên bản — multica update tiếp tục hoạt động.
Kỹ năng Codex làm ô nhiễm ~/.codex/. CODEX_HOME theo tác vụ.
Tự host production đơn nút bị chặn bởi dependency Redis. Redis tùy chọn; hub trong bộ nhớ mặc định.
Agent lặp lại trên các bình luận xác nhận thuần túy của nhau. Quy tắc meta-skill: “Nếu bình luận trước đó là một lời xác nhận/cảm ơn thuần túy VÀ bạn không tạo ra công việc nào, thì ĐỪNG trả lời — im lặng được ưu tiên hơn.”
Ghi trạng thái server từ các sự kiện WS làm hỏng bộ nhớ cache. Các sự kiện WS làm mất hiệu lực Query; chúng không bao giờ ghi trực tiếp vào stores.

📋 17. Cheat Sheet

📖 Các Tệp Nên Đọc Đầu Tiên (theo thứ tự)

  1. server/pkg/agent/agent.go — giao diện.
  2. server/pkg/agent/claude.go — triển khai tiêu chuẩn.
  3. server/internal/daemon/daemon.go — vòng đời + vòng lặp lấy dữ liệu.
  4. server/internal/daemon/execenv/runtime_config.go — trình xây dựng meta-skill.
  5. server/internal/daemon/prompt.go — prompt phân nhánh theo loại tác vụ.
  6. server/cmd/server/main.go — khởi động server.
  7. server/cmd/server/router.go — cây route đầy đủ.
  8. server/migrations/001_init.up.sql — lược đồ cốt lõi.
  9. CLAUDE.md — mọi quy tắc quan trọng, với lỗi đã thúc đẩy nó.
  10. Makefile — quy trình làm việc.

⚙️ Giá Trị Cấu Hình Mặc Định

Thiết lập Mặc định Biến môi trường
Khoảng thời gian lấy dữ liệu 3 s MULTICA_DAEMON_POLL_INTERVAL
Khoảng thời gian heartbeat 15 s MULTICA_DAEMON_HEARTBEAT_INTERVAL
Thời gian chờ Agent 2 h MULTICA_AGENT_TIMEOUT
Thời gian chờ không hoạt động ngữ nghĩa của Codex 10 m MULTICA_CODEX_SEMANTIC_INACTIVITY_TIMEOUT
Số tác vụ đồng thời tối đa trên mỗi daemon 20 MULTICA_DAEMON_MAX_CONCURRENT_TASKS
Cổng sức khỏe 19514 (cờ CLI)
Thư mục gốc của Workspaces ~/multica_workspaces/ MULTICA_WORKSPACES_ROOT
TTL GC (các vấn đề đã hoàn thành) 24 h MULTICA_GC_TTL
TTL GC cho các tệp mồ côi 72 h MULTICA_GC_ORPHAN_TTL

📐 Phân Loại Thông Điệp Thống Nhất (không lệch)


text          văn xuôi của trợ lý
thinking      lý luận của trợ lý
tool-use      gọi công cụ (Tool, CallID, Input)
tool-result   kết quả công cụ (CallID, Output)
status        sự kiện vòng đời (mô hình đã tải, sandbox sẵn sàng, …)
error         lỗi không nghiêm trọng
log           log debug

🔖 Các Trạng Thái Kết Quả Thống Nhất


completed    đường dẫn thành công
failed       agent trả về mã lỗi khác 0
aborted      ctx bị người dùng hủy
timeout      đạt AgentTimeout / SemanticInactivityTimeout
cancelled    hủy từ phía server

🗣️ Từ Vựng CLI Của Agent (những gì meta-skill dạy)


multica issue get <id> --output json
multica issue list --output json
multica issue comment list <id> --output json
multica workspace members --output json
multica issue create --title ... --content-stdin <<EOF ... EOF --output json
multica issue update <id> ... --output json
multica issue assign <id> --to <member-or-agent> --output json
multica issue label add <id> --label ... --output json
multica issue subscriber add <id> --user ... --output json
multica issue comment add <id> --content-stdin <<EOF ... EOF --output json
multica label create --name ... --color ... --output json
multica autopilot create / update / trigger / delete ...

🎭 Mẫu Actor Đa Hình


CREATE TABLE issue (
    id           UUID PRIMARY KEY,
    workspace_id UUID NOT NULL REFERENCES workspace,
    title        TEXT NOT NULL,
    content      TEXT,
    status       TEXT NOT NULL,
    assignee_type TEXT CHECK (assignee_type IN ('member', 'agent')),
    assignee_id   UUID,
    creator_type  TEXT CHECK (creator_type IN ('member', 'agent')),
    creator_id    UUID NOT NULL,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
    ...
);

🚫 Các Quy Tắc Bất Di Bất Dịch

  • Mọi truy vấn server đều lọc theo workspace_id.
  • Mọi khóa TanStack Query đều bao gồm wsId.
  • packages/core/ không có react-dom, không localStorage, không process.env.
  • packages/views/ không có next/*, không react-router-dom.
  • packages/ui/ không nhập @multica/core.
  • Thứ tự đăng ký listener: người đăng ký trước các thông báo.
  • Daemon từ chối khởi tạo nếu task.WorkspaceID == "".
  • Luôn truyền --output json từ các lệnh CLI của agent.
  • Luôn sử dụng --content-stdin với HEREDOCs cho nội dung nhiều dòng.
  • Các sự kiện WS làm mất hiệu lực Query; chúng không bao giờ ghi trực tiếp vào stores.
  • Migrations chỉ được thêm vào. Không bao giờ chỉnh sửa một migration đã áp dụng.

💭 Lời Kết

Sức mạnh siêu việt của Multica không nằm ở ML mới lạ mà ở **tính kỷ luật**:

  • Một giao diện cho các agent (Backend.Execute), mười một triển khai.
  • Một quy ước workdir (~/multica_workspaces/{ws}/{task}/), mọi agent tự khởi động thông qua định dạng tệp cấu hình tự nhiên của nó.
  • Một nguồn thông tin đáng tin cậy (Postgres), một bus sự kiện, hai hệ thống con WS với đối tượng khác nhau.
  • Một kinh thánh kỹ thuật (CLAUDE.md), mọi quy tắc đều được chú thích với lỗi đã thúc đẩy nó.

Nếu bạn hiểu rõ Mục 3 (không tự xây dựng vòng lặp, hãy bọc lại nó) và Mục 5 (giao diện Backend), và bạn duy trì kỷ luật đó khi phát triển, bạn có thể tái tạo điều này trong khoảng 10–14 ngày làm việc tập trung cho phiên bản 1.

Bây giờ, hãy bắt tay vào xây dựng!

Chỉ mục