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
📋 Mục Lục Chi Tiết
- 🧐 Multica Là Gì – Và Không Là Gì?
- ⚡ Mô Hình Tư Duy 30 Giây
- 💡 Ý 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ó
- 🏗️ Tổng Quan Kiến Trúc
- 🔌 Giao Diện Backend Agent (trừu tượng then chốt)
- 🔄 Daemon cục bộ – Lấy Dữ Liệu, Đánh Thức, Xử Lý Song Song
- 📁 Workdir Riêng Cho Từng Tác Vụ + Chèn Cấu Hình Tự Nhiên
- 🧠 Kỹ Năng (Skills) – Lớp Khả Năng Kết Hợp
- ▶️ Các Phiên Có Thể Tiếp Tục và Tái Sử Dụng Workdir
- 🖥️ Server – Mô Hình Dữ Liệu, Thời Gian Thực, Đa Thuê
- 10.1 🎭 Actors Đa Hình (Polymorphic Actors)
- 10.2 🔒 Đa Thuê (Multi-Tenancy)
- 10.3 💾 Lớp Lưu Trữ Dữ Liệu
- 10.4 🔗 Phân Lớp: Handler → Service → Repo
- 10.5 📡 Bus Sự Kiện Nội Bộ (In-Process Event Bus)
- 10.6 🔌 Hai Hệ Thống Con WebSocket
- 10.7 🌐 Thời Gian Thực Đơn Nút so với Đa Nút
- 10.8 🐛 Phân Tích Cú Pháp UUID Nghiêm Ngặt (một lỗi thực sự)
- ⏰ Autopilot – Tự Động Hóa Theo Lịch và Kích Hoạt
- 🖼️ Frontend – Ranh Giới Trạng Thái Nghiêm Ngặt
- 📦 Đóng Gói, Phát Hành, Tự Host
- 🏆 Các Thực Tiễn Kỹ Thuật Đáng Học Hỏi
- 🗺️ Kế Hoạch Xây Dựng Từng Bước (12 Giai Đoạn)
- 🌱 Giai đoạn 1 — Khung Sườn (1 ngày)
- 📝 Giai đoạn 2 — CRUD Vấn Đề (2 ngày)
- 🔌 Giai đoạn 3 — WebSocket Hướng Người Dùng (1 ngày)
- 🔗 Giai đoạn 4 — Giao Diện Backend Agent (1 ngày)
- 🔄 Giai đoạn 5 — Khung Sườn Daemon Cục Bộ (2 ngày)
- ✅ Giai đoạn 6 — Vòng Đời Tác Vụ Từ Đầu Đến Cuối (3 ngày)
- 🧠 Giai đoạn 7 — Kỹ Năng + Chèn Cấu Hình Theo Nhà Cung Cấp (1 ngày)
- ⚡ Giai đoạn 8 — Đánh Thức Daemon Qua WS (½ ngày)
- ▶️ Giai đoạn 9 — Các Phiên Có Thể Tiếp Tục (1 ngày)
- ➕ Giai đoạn 10 — Thêm Backend Thứ Hai + Ba (1 ngày)
- ⏰ Giai đoạn 11 — Autopilot (1 ngày)
- 📦 Giai đoạn 12 — Đóng Gói + Tự Host (1 ngày)
- ⚠️ Những Cạm Bẫy Thường Gặp và Các “Lan Can” Khó Khăn
- 📋 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ể:
- Định nghĩa một giao diện Go duy nhất —
Backend— với phương thứcExecutedạng streaming. - 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.Commandcùng với một trình phân tích cú pháp đầu ra chuẩn dạng streaming. - 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).
- 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 chainjackc/pgx/v5+pgxpool— Postgressqlc— 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 daemonredis/go-redis/v9— phân tán tùy chọngolang-jwt/jwt/v5— xác thựcspf13/cobra— CLI cho binarymulticarobfig/cron/v3— lập lịch autopilotresend-go— emailaws-sdk-go-v2/s3+ CloudFront signed URLsprometheus/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`)
- 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
- → Điểm cuối
resolveAuth()— tải token từ~/.multica/config.jsonsyncWorkspacesFromAPI— 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/registervới{name, type, version, status} - – Cache các
runtimeIDtrả về
- – Kiểm tra từng CLI agent qua
- 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ệctaskWakeupLoop— 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,PendingLocalSkillImportgcLoop— dọn dẹp~/multica_workspaces/cho các vấn đề đã hoàn thànhserveHealth— JSON/healthcục bộ (thời gian hoạt động, số tác vụ đang hoạt động)
- 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**:
- Khối nhận dạng — “Bạn là: {tên agent} (ID: …)” + hướng dẫn persona của agent.
- Danh mục CLI — mọi lệnh con
multicamà 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
- Đọc:
- Quy tắc cứng: luôn truyền
--output jsonđể agent nhận được các ID ổn định. - Quy tắc nội dung nhiều dòng: phải sử dụng
--content-stdinvới HEREDOC (vì bash không mở rộng\ntrong 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ệ). - 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. - 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 promptskills[]— 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 daemonmcp_config— JSON thô, được ghi vào một tệp tạm thời và truyền--mcp-config <path>modelmax_concurrent_tasksvisibility—workspace|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ạngExecOptions.ResumeSessionID(ví dụ:claude --resume <id>)PriorWorkDir— daemon gọiexecenv.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|cancelledvà cũ hơnMULTICA_GC_TTL(mặc định 24h) - Thư mục mồ côi (không có
.gc_meta.json) cũ hơnMULTICA_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
memberkết nốiuservàworkspacevới mộtrole). - Frontend gửi
X-Workspace-IDtrên mọi yêu cầu để định tuyến đến workspace đang hoạt động. - Middleware:
Auth(queries)— JWT hoặc PATDaemonAuth(queries)— token daemonRequireWorkspaceMemberFromURL(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/*.sqlthành mã Go có kiểu trongpkg/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àoagent_task_queuevới ngữ cảnh autopilot. Daemon nhận nó; meta-skill phát hiệnMULTICA_AUTOPILOT_RUN_IDvà chuyển sang quy trình làm việc autopilot (không gọimultica 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 theoCLI_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 updatehoạ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-tapkhi gắn tag
Đường dẫn cài đặt cho người dùng:
brew install multica-ai/tap/multicacurl https://multica.ai/install.sh | shiwr 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ự hostdocker-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/1riê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:
CLAUDE.mdnhư 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.mdlà một con trỏ 2 KB chỉ nói với các agent đọcCLAUDE.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.- 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í. - 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.gotương ứng (thường có kích thước tương đương hoặc lớn hơn). - 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.
- 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". - 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. - 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.
- 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.
- 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.
- Makefile thân thiện với worktree. Tạo
.env.worktreevới tên DB + cổng duy nhất. Chạy N nhánh song song chống lại một Postgres. - Không bắt buộc phải có Redis. Phân tán tùy chọn, mặc định đơn nút.
- 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.
- Các biến môi trường
MULTICA_*+ hợp nhấtagent.CustomEnvvớ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. - 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. 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.- Thứ tự đăng ký listener được ghi lại trong các bình luận mã, vì nó rất quan trọng.
- 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. - 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.
- 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.
- Các tarball có tên cũ tương thích ngược để
multica updatecũ 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
/wsvới JWT auth + kiểm tra origin. events.Bustrong 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.
LoadConfigkiểm tra các CLI đã cài đặt quaexec.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ấtagent.CustomEnvvớ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 get và multica 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.Hubvới các kênh đánh thức tác vụ cho mỗi runtime.sleepWithContextOrWakeuptrả 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)
PinTaskSessiongiữa chừng.- Chuyển tiếp
PriorSessionID+PriorWorkDirtrên yêu cầu tiếp theo. execenv.Reusevsexecenv.Prepare.- Fallback tiếp tục: thử lại một lần với
ResumeSessionIDtrố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_HOMEtheo 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/v3trong một goroutine. - Chế độ
RunOnce: đưa một tác vụ vào hàng đợi với ngữ cảnh autopilot (envMULTICA_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.shvàinstall.ps1phát hiện Homebrew nếu có.- Image GHCR cho server + web.
docker-compose.selfhost.ymlcho 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ự)
server/pkg/agent/agent.go— giao diện.server/pkg/agent/claude.go— triển khai tiêu chuẩn.server/internal/daemon/daemon.go— vòng đời + vòng lặp lấy dữ liệu.server/internal/daemon/execenv/runtime_config.go— trình xây dựng meta-skill.server/internal/daemon/prompt.go— prompt phân nhánh theo loại tác vụ.server/cmd/server/main.go— khởi động server.server/cmd/server/router.go— cây route đầy đủ.server/migrations/001_init.up.sql— lược đồ cốt lõi.CLAUDE.md— mọi quy tắc quan trọng, với lỗi đã thúc đẩy nó.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ônglocalStorage, khôngprocess.env.packages/views/không cónext/*, khôngreact-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 jsontừ các lệnh CLI của agent. - Luôn sử dụng
--content-stdinvớ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!



