Compare commits

...

3 Commits

Author SHA1 Message Date
J
fc9ad03408 docs(i18n): align residual Korean UI/product terms with the app
Address review: sweep the .ko.mdx corpus for product/UI terms left in
English and match the in-app Korean locale.

- skills page title Skills → 스킬
- UI nav paths localized: Settings → 설정, Runtimes → 런타임, Agents →
  에이전트, Projects → 프로젝트, Squads/New squad → 스쿼드/새 스쿼드,
  Usage → 사용량, Personal Access Tokens → API 토큰, Provider → 제공자,
  and the agent-create form labels (Name/Provider/Model/Instructions →
  이름/제공자/모델/지침)
- see-also links Issues/Workspaces/Environment variables and
  'Providers Matrix' → Korean
- kept as literals (verified): code blocks, the conventions i18n glossary
  data, 'Anthropic Agent Skills' (standard name), the Squad Operating
  Protocol/Roster/Instructions prompt-block names, the literal 'Project
  Context' prompt section, and Xcode's Settings path
- add a docsAlternates test asserting ko hreflang is emitted when a real
  *.ko.mdx page exists

MUL-2817

Co-authored-by: multica-agent <github@multica.ai>
2026-05-29 16:43:56 +08:00
J
f77f75ed2e feat(docs): serve Korean docs content, remove English-fallback stopgap
Now that the *.ko.mdx corpus exists, drop the temporary docsContentLang
ko→en shim and the static-params fallback-synthesis loop so /docs/ko/*
renders real Korean content. Korean is now a first-class locale whose
params come straight from source.generateParams(). Also align the docs
home hero copy (agent→에이전트) with the app and the translated body.

MUL-2817

Co-authored-by: multica-agent <github@multica.ai>
2026-05-29 16:19:17 +08:00
J
fc2617089c docs(i18n): translate documentation corpus to Korean
Add Korean (.ko.mdx) translations for all 32 navigable docs pages plus
meta.ko.json navigation, mirroring the English source. Product terms
(Issue→이슈, Agent→에이전트, Squad→스쿼드, Runtime→런타임, Skill→스킬,
Workspace→워크스페이스, etc.) follow the in-app Korean locale at
packages/views/locales/ko/. Roles (owner/admin/member) and issue status
enums stay lowercase English per the conventions glossary.

MUL-2817

Co-authored-by: multica-agent <github@multica.ai>
2026-05-29 16:19:11 +08:00
42 changed files with 4172 additions and 42 deletions

View File

@@ -9,7 +9,7 @@ import { notFound } from "next/navigation";
import defaultMdxComponents from "fumadocs-ui/mdx";
import type { Metadata } from "next";
import { docsAlternates } from "@/lib/site";
import { docsContentLang, i18n, type Lang } from "@/lib/i18n";
import { i18n, type Lang } from "@/lib/i18n";
import { DocsLocaleProvider, LocaleLink } from "@/components/locale-link";
import { docsSlugStaticParams } from "@/lib/static-params";
@@ -24,7 +24,7 @@ export default async function Page(props: {
}) {
const params = await props.params;
const lang = asLang(params.lang);
const page = source.getPage(params.slug, docsContentLang(lang));
const page = source.getPage(params.slug, lang);
if (!page) notFound();
const MDX = page.data.body;
@@ -51,7 +51,7 @@ export async function generateMetadata(props: {
}): Promise<Metadata> {
const params = await props.params;
const lang = asLang(params.lang);
const page = source.getPage(params.slug, docsContentLang(lang));
const page = source.getPage(params.slug, lang);
if (!page) notFound();
return {

View File

@@ -7,7 +7,7 @@ import type { Metadata } from "next";
import { cn } from "@multica/ui/lib/utils";
import { baseOptions } from "@/app/layout.config";
import { source } from "@/lib/source";
import { docsContentLang, i18n, type Lang } from "@/lib/i18n";
import { i18n, type Lang } from "@/lib/i18n";
import { uiTranslations, localeLabels } from "@/lib/translations";
import { DocsSettings } from "@/components/docs-settings";
@@ -76,7 +76,6 @@ export default async function Layout({
const lang = (i18n.languages as readonly string[]).includes(rawLang)
? (rawLang as Lang)
: (i18n.defaultLanguage as Lang);
const contentLang = docsContentLang(lang);
const locales = i18n.languages.map((l) => ({
locale: l,
name: localeLabels[l],
@@ -84,7 +83,7 @@ export default async function Layout({
return (
<html
lang={contentLang}
lang={lang}
suppressHydrationWarning
className={cn(
"antialiased",
@@ -103,7 +102,7 @@ export default async function Layout({
search={{ options: { api: "/docs/api/search" } }}
>
<DocsLayout
tree={source.getPageTree(contentLang)}
tree={source.getPageTree(lang)}
// Suppress Fumadocs's default sidebar-footer icons (theme +
// language + search). Our custom <DocsSettings> is mounted as
// the sidebar footer instead — two labelled buttons, not three

View File

@@ -5,7 +5,7 @@ import defaultMdxComponents from "fumadocs-ui/mdx";
import type { Metadata } from "next";
import { DocsHero } from "@/components/hero";
import { Byline, NumberedCards, NumberedCard, NumberedSteps, Step } from "@/components/editorial";
import { docsContentLang, i18n, type Lang } from "@/lib/i18n";
import { i18n, type Lang } from "@/lib/i18n";
import { homeCopy } from "@/lib/translations";
import { docsAlternates } from "@/lib/site";
import { DocsLocaleProvider, LocaleLink } from "@/components/locale-link";
@@ -31,7 +31,7 @@ export default async function Page({
}) {
const { lang: rawLang } = await params;
const lang = asLang(rawLang);
const page = source.getPage([], docsContentLang(lang));
const page = source.getPage([], lang);
if (!page) notFound();
const MDX = page.data.body;
@@ -77,7 +77,7 @@ export async function generateMetadata({
}): Promise<Metadata> {
const { lang: rawLang } = await params;
const lang = asLang(rawLang);
const page = source.getPage([], docsContentLang(lang));
const page = source.getPage([], lang);
if (!page) notFound();
return {

View File

@@ -0,0 +1,127 @@
---
title: 에이전트 생성 및 구성
description: 에이전트를 생성하는 데 필요한 최소 필드와 모든 선택적 설정 — 시스템 지침, 환경 변수, 공개 범위, 동시 실행 제한, 보관.
---
import { Callout } from "fumadocs-ui/components/callout";
[에이전트](/agents)를 생성하는 데는 단 두 가지만 필요합니다. **이름** 하나와 **[AI 코딩 도구](/providers) 선택** 하나입니다. 나머지는 모두 선택 사항입니다 — 시스템 지침, 모델, 환경 변수, CLI 인자, 공개 범위, 동시 실행 제한 — 기본값으로도 문제없이 작동합니다. 먼저 실행해 보고 나중에 조정하세요. 모든 필드는 언제든지 변경할 수 있습니다.
## 에이전트 생성
전제 조건: 사용 중인 기기에 지원되는 [AI 코딩 도구](/providers)가 최소 하나는 설치되어 있고(Claude Code, Codex 등) [데몬](/daemon-runtimes)이 실행 중이어야 합니다. 아직 여기까지 준비되지 않았다면 [Cloud 빠른 시작](/cloud-quickstart)이나 [자체 호스팅 빠른 시작](/self-host-quickstart)부터 시작하세요.
준비가 끝나면 워크스페이스의 **에이전트** 페이지로 이동해 **+ New**를 클릭하거나 CLI를 사용하세요.
```bash
multica agent create
```
이 폼에는 필수 필드가 두 개뿐입니다. **이름**(워크스페이스 내에서 고유해야 함)과 **런타임**(= AI 코딩 도구 선택)입니다. 나머지 모든 필드는 아래에서 섹션별로 다룹니다.
## AI 코딩 도구 선택
각 런타임은 특정 AI 코딩 도구를 기반으로 합니다. Multica는 그중 12개를 지원합니다. 가장 일반적인 선택지는 다음과 같습니다.
| 도구 | 적합한 경우 |
|---|---|
| **Claude Code** | Anthropic의 공식 도구로, 가장 완성도 높은 기능 집합을 제공합니다. **첫 선택으로 가장 좋습니다** |
| **Codex** | OpenAI 제품으로, 주류 대안입니다 |
| **Cursor** | Cursor 에디터 생태계 사용자 |
| **Copilot** | GitHub 계정 권한을 활용하는 팀 |
| **Gemini** | Google 생태계 사용자 |
나머지 7개(Antigravity, Hermes, Kimi, Kiro CLI, OpenCode, Pi, OpenClaw)와 각 도구의 전체 기능 비교표(세션 재개, MCP, 스킬 주입 경로, 모델 선택)는 [AI 코딩 도구 비교](/providers)에서 다룹니다.
## 시스템 지침 작성
**시스템 지침**(`instructions`)은 모든 작업 앞에 추가되어, 에이전트가 어떤 역할을 맡고 어떤 규칙을 따라야 하는지 알려줍니다.
```text
You're a frontend code-review agent. When an issue comes in, read the diff first. Focus only on:
- Styling issues (tailwind class names, box model)
- Accessibility (a11y)
Don't change code — leave suggestions in a comment.
```
비워 두면(기본값) 에이전트는 추가 제약 없이 기반이 되는 AI 코딩 도구의 기본 동작을 사용합니다.
## 모델 선택
대부분의 AI 코딩 도구는 모델 선택을 지원합니다(예를 들어 Claude Code는 Sonnet과 Opus 중에서 고를 수 있습니다). 비워 두면 도구 자체의 기본값이 사용되고, 명시적으로 하나를 선택하면 그 모델이 실행됩니다. 각 도구가 지원하는 모델은 [AI 코딩 도구 비교](/providers)에 정리되어 있습니다.
모델 변경은 **새 작업에만 적용됩니다**. 이미 디스패치된 작업은 디스패치 시점에 고정된 모델로 계속 실행됩니다.
## 사용자 지정 환경 변수 (custom_env)
**사용자 지정 환경 변수**(`custom_env`)를 사용하면 작업 실행 시점에 추가 환경 변수를 주입할 수 있습니다. 대표적인 용도는 API 키 설정이나 업스트림 엔드포인트 전환입니다.
```
ANTHROPIC_API_KEY = sk-...
ANTHROPIC_BASE_URL = https://my-proxy.example.com
```
시스템에 핵심적인 변수는 재정의할 수 없습니다. `PATH`, `HOME`, `USER`, `SHELL`, `TERM`, `CODEX_HOME`, 그리고 `MULTICA_*`로 시작하는 모든 키는 데몬이 조용히 무시합니다(경고 로그는 남기지만 오류는 발생하지 않습니다).
<Callout type="warning">
**`custom_env`의 값은 Multica 서버 데이터베이스에 평문으로 저장됩니다.** 에이전트 list/get 응답은 더 이상 환경 변수 값을 전혀 포함하지 않으며, 불투명한 개수만 반환합니다. 실제 값을 읽으려면 워크스페이스 owner 또는 admin이 전용으로 감사되는 `GET /api/agents/{id}/env` 엔드포인트(CLI: `multica agent env get <id>`)를 호출해야 합니다. 작업을 실행 중인 에이전트는 호스트의 owner 자격 증명을 이용해 다른 에이전트의 환경 변수를 드러낼 수 없습니다. 이 엔드포인트는 에이전트 액터 세션을 거부합니다.
**가치가 높은 secret은 `custom_env`에 넣지 마세요**(운영 데이터베이스 비밀번호, root 수준 토큰 등). 에이전트에는 **권한 범위가 제한된 전용 자격 증명**(읽기 전용 API 키, 단일 스코프 PAT)을 사용하고 정기적으로 교체하세요. 데이터베이스 백업과 DB 감사는 여전히 의미 있는 노출 표면으로 남아 있습니다.
</Callout>
## 사용자 지정 CLI 인자 (custom_args)
**사용자 지정 CLI 인자**(`custom_args`)는 AI 코딩 도구의 명령줄에 하나씩 차례로 덧붙는 문자열 배열입니다.
```json
["--max-turns", "100", "--append-system-prompt", "always respond in Chinese"]
```
최종 명령은 다음과 같이 만들어집니다.
```bash
claude --model <model> --max-turns 100 --append-system-prompt "always respond in Chinese" [...]
```
인자는 셸을 거치지 않고 있는 그대로 전달되므로(주입 위험 없음), 특정 플래그가 인식되는지 여부는 AI 코딩 도구 자체에 달려 있습니다. 이 부분은 도구마다 상당한 차이가 있습니다.
<Callout type="tip">
`custom_env`와 `custom_args`에는 엄격한 상한이 없지만, 실제로는 **각각 10개 이내로 유지하세요**. 너무 많으면 명령줄이 길어지고 시작이 느려지며 유지 관리도 어려워집니다.
</Callout>
## 공개 범위
- **워크스페이스**(`workspace`) — 워크스페이스의 모든 멤버가 할당할 수 있습니다
- **비공개**(`private`) — 워크스페이스 owner, admin, 또는 에이전트 생성자만 할당할 수 있습니다
새 에이전트는 기본적으로 `private`입니다.
**비공개라고 해서 숨겨지는 것은 아닙니다** — 모든 멤버가 목록에서 비공개 에이전트의 이름과 설명을 볼 수 있으며, 다만 민감한 구성은 읽을 수 없습니다(환경 변수 값은 에이전트 list/get 응답에 절대 나타나지 않으며, MCP 구성은 owner가 아닌 사용자에게는 마스킹됩니다). 자세한 의미는 [에이전트 → 누가 에이전트를 할당할 수 있나요](/agents#who-can-assign-an-agent)를 참고하세요.
## 동시 실행 제한
**동시 실행 제한**(`max_concurrent_tasks`)은 이 에이전트가 한 번에 병렬로 실행할 수 있는 작업 수를 제어합니다. 기본값은 **6**입니다. 상한에 도달한 새 작업은 거부되지 않고 대기열에서 대기합니다.
이것은 두 단계 제한 중 "에이전트 계층"에 불과합니다. 데몬 자체가 더 넓은 상한(기본값 20)을 적용하며, 둘 중 더 빡빡한 쪽이 우선합니다. 자세한 내용은 [데몬과 런타임 → 병렬로 몇 개의 작업을 실행할 수 있나요](/daemon-runtimes#how-many-tasks-can-run-in-parallel)에 있습니다.
이 값을 변경해도 **이미 실행 중인 작업은 취소되지 않으며**, 다음에 처리될 작업부터만 적용됩니다.
## 도메인 전문성 연결: 스킬
생성된 에이전트에는 **스킬**을 연결할 수 있습니다 — 작업 실행 시점에 AI 코딩 도구로 자동 전달되는 **지식 팩**(`SKILL.md` + 보조 파일)입니다. 새 스킬을 만들거나, GitHub 또는 ClawHub에서 가져오거나, 기기에 있는 기존 스킬 디렉터리에서 스캔할 수 있습니다. [스킬](/skills)을 참고하세요.
## 보관 및 복원
더 이상 사용하지 않는 에이전트는 **보관**할 수 있습니다 — 일상적인 화면에서는 사라지지만, 이력 데이터(실행한 작업, 작성한 댓글)는 모두 그대로 유지됩니다. 언제든지 **복원**하여 다시 작업에 투입할 수 있습니다.
<Callout type="warning">
**보관은 해당 에이전트에 속한 완료되지 않은 모든 작업을 즉시 취소합니다** — 실행 중, 디스패치됨, 대기 중인 작업이 모두 `cancelled`로 표시되며 계속 진행되지 않습니다. 진행 중인 중요한 작업이 있다면 보관하기 전에 끝까지 완료되도록 두세요.
</Callout>
보관된 에이전트에는 새 작업을 할당할 수 없습니다.
## 다음 단계
- [스킬](/skills) — 에이전트에 지식 팩 연결하기
- [AI 코딩 도구 비교](/providers) — 12개 도구 전체의 기능 비교표
- [에이전트에게 이슈 할당하기](/assigning-issues) — 새로 만든 에이전트를 작업에 투입하기

View File

@@ -0,0 +1,49 @@
---
title: 에이전트
description: "에이전트는 Multica 워크스페이스의 일급 멤버입니다 — 이슈를 할당받고, 댓글을 달고, @로 멘션될 수 있습니다. 사람과의 핵심 차이는, 에이전트는 스스로 작업을 시작하며 알림을 받지 않는다는 점입니다."
---
import { Callout } from "fumadocs-ui/components/callout";
에이전트는 Multica [워크스페이스](/workspaces)의 **일급 멤버**입니다 — 사람과 마찬가지로 [이슈를 할당받고](/assigning-issues), [댓글](/comments)에서 발언하고, [`@`로 멘션되며](/mentioning-agents), [프로젝트](/projects)를 이끌 수 있습니다. 핵심 차이는 이것입니다. 모든 에이전트 뒤에는 여러분의 기기에서 실행되는 [AI 코딩 도구](/providers)가 있습니다. 에이전트에게 작업을 할당하면 별다른 재촉 없이 **수 초 내에 스스로 작업을 시작**합니다 — 닦달할 필요도, 오프라인이 되지도 않으며, 24시간 내내 가용합니다.
## 에이전트가 할 수 있는 일
에이전트는 사람과 동일한 "멤버" 표면을 사용하며, UI에서는 거의 구분되지 않습니다.
- **[이슈를 할당받기](/assigning-issues)** — 담당자로 지정되는 순간 자동으로 작업을 시작합니다
- **[`@`로 멘션되기](/mentioning-agents)** — 댓글에 `@agent-name`을 쓰면 깨어나 해당 댓글을 읽습니다
- **[댓글](/comments) 작성** — 이슈 아래에서 진행 상황을 보고하고 사람들에게 답글을 답니다
- **[프로젝트](/projects) 이끌기** — 사람과 마찬가지로 프로젝트 리더로 지정될 수 있습니다
- **스스로 [이슈](/issues) 열기** — 작업을 실행하는 동안 관련 문제를 발견하면 직접 새 이슈를 생성할 수 있습니다
협업 뷰에서 보면 에이전트는 그저 워크스페이스의 한 멤버일 뿐입니다 — 사람과 같은 멤버 목록에 이름이 자리하며, 보통 앞에 작은 로봇 아이콘이 붙습니다.
## 사람과 다른 점
몇 가지 핵심 차이는 실제로 에이전트를 사용하기 시작해야 비로소 드러납니다.
- **스스로 시작합니다** — 이슈를 할당하거나 `@`로 멘션하면 Multica가 즉시 해당 작업을 에이전트의 런타임에 디스패치합니다. 사람처럼 메시지를 보고 응답할 때까지 기다리지 않습니다. 트리거에 대한 자세한 내용은 [에이전트에게 이슈 할당하기](/assigning-issues)와 [댓글에서 에이전트 @-멘션하기](/mentioning-agents)를 참고하세요.
- **알림을 받지 않습니다** — 에이전트는 여러분의 [인박스](/inbox) 건너편에 결코 나타나지 않으며, `@all`의 수신 대상에도 포함되지 않습니다. 에이전트는 "메시지를 읽는 수신자"가 아니라 "작업을 실행하도록 트리거되는 작업 단위"입니다.
- **하나의 AI 코딩 도구에 묶여 있습니다** — 모든 에이전트는 런타임에 묶여 있습니다(런타임 = 데몬 × 하나의 AI 코딩 도구. [데몬과 런타임](/daemon-runtimes) 참고). 도구가 오프라인이면 에이전트는 작업할 수 없으며, 새 작업은 런타임이 돌아올 때까지 대기합니다.
- **보관할 수 있습니다** — 더 이상 사용하지 않는 에이전트를 보관하면 일상적인 뷰에서 사라지며, 원하면 언제든지 복원할 수 있습니다. 보관하면 현재 실행 중인 작업은 모두 취소됩니다.
## 누가 에이전트를 할당할 수 있나
에이전트를 생성할 때, 누가 그 에이전트를 이슈에 할당하거나 프로젝트 리더로 지정할 수 있는지를 제어하는 **가시성(visibility)** 을 선택합니다.
- **워크스페이스(Workspace)** — 워크스페이스의 모든 멤버가 할당할 수 있습니다
- **비공개(Private)** — 워크스페이스의 owner, admin, 또는 에이전트 생성자만 할당할 수 있습니다
새 에이전트는 기본적으로 **비공개**입니다. 전체 워크스페이스에서 사용할 수 있게 하려면, 생성 시 가시성을 `workspace`로 설정하거나 이후에 에이전트 설정에서 변경하세요. 전체 역할-권한 매트릭스는 [멤버와 역할](/members-roles)을 참고하세요.
<Callout type="info">
**비공개는 "누가 할당할 수 있는지를 제한"한다는 뜻이지, "다른 모든 사람에게 숨긴다"는 뜻이 아닙니다.** 워크스페이스의 모든 멤버는 에이전트 목록에서 비공개 에이전트의 이름과 설명을 볼 수 있습니다 — 단지 설정 세부 정보는 볼 수 없을 뿐입니다(사용자 정의 환경 변수, MCP 설정 및 기타 민감한 필드는 마스킹됩니다). "단 한 사람에게만 보이게" 하고 싶다면, 현재로서는 불가능합니다.
</Callout>
## 다음 단계
- [에이전트 생성 및 구성](/agents-create) — 에이전트를 만드는 방법
- [스킬](/skills) — 에이전트에 지식 팩 연결하기
- [스쿼드](/squads) — 적합한 에이전트가 적합한 이슈를 맡도록 리더 아래 에이전트를 그룹으로 묶기
- [데몬과 런타임](/daemon-runtimes) — 에이전트가 실제로 실행되기 위해 필요한 것

View File

@@ -0,0 +1,83 @@
---
title: 에이전트에게 이슈 할당하기
description: 이슈를 에이전트에게 넘기면 작업이 끝날 때까지 공식 담당자로 인계받습니다 — 전체 컨텍스트를 갖고 이슈 상태와 필드를 변경할 수 있습니다.
---
import { Callout } from "fumadocs-ui/components/callout";
[이슈](/issues)를 [에이전트](/agents)에게 할당하면, 작업이 끝날 때까지 **공식 담당자**로서 일합니다 — 이슈의 전체 컨텍스트(설명 + 모든 [댓글](/comments))를 읽을 수 있고, 상태를 변경하고, 댓글을 남기고, 필드를 수정할 수 있습니다. 이것은 Multica의 네 가지 트리거 경로 중 **가장 일반적이고 가장 무거운** 방식입니다. 동일한 흐름은 [스쿼드](/squads)를 담당자로 받을 수도 있습니다 — 이 경우 Multica는 대신 스쿼드의 **리더 에이전트**를 트리거합니다.
| 경로 | 사용 시점 | 이슈 변경 | 컨텍스트 | 우선순위 | 자동 재시도 |
|---|---|---|---|---|---|
| **할당** | 에이전트에게 소유권을 넘김 | 담당자 변경 | 이슈 + 모든 댓글 | 이슈에서 상속 | ✓ |
| [**@-멘션**](/mentioning-agents) | 잠깐 살펴보도록 끌어들임 | 변경 없음 | 이슈 + 트리거 댓글 | 이슈에서 상속 | ✓ |
| [**채팅**](/chat) | 이슈와 무관한 일대일 대화 | 이슈 관여 없음 | 현재 대화 기록 | 고정 중간 | ✓ |
| [**오토파일럿**](/autopilots) | 예약 또는 수동 자동화 | 모드에 따라 다름 | 모드에 따라 다름 | 오토파일럿이 설정 | ✗ |
"자동 재시도"는 인프라 장애(런타임 오프라인, 타임아웃) 이후의 재시도를 의미합니다. 에이전트 쪽의 비즈니스 오류(예: 모델이 오류를 보고하는 경우)는 재시도되지 않습니다. 자세한 내용은 [**작업**](/tasks)을 참고하세요.
## UI에서 할당하기
이슈 상세 페이지에서 **담당자** 선택기를 클릭하세요. 워크스페이스의 모든 멤버, 보관되지 않은 모든 에이전트, 보관되지 않은 모든 [스쿼드](/squads)가 목록에 표시됩니다. 에이전트(또는 스쿼드)를 선택하면 이슈가 즉시 할당됩니다.
몇 가지 규칙이 있습니다.
- **워크스페이스 에이전트**는 어떤 멤버든 할당할 수 있습니다. **프라이빗 에이전트**는 owner 또는 워크스페이스 admin만 할당할 수 있습니다.
- **온라인 런타임이 있는** 에이전트에게만 할당할 수 있습니다 — 아무도 실행하고 있지 않은 에이전트는 선택기에서 사용 불가로 표시됩니다.
- 이슈 상태가 **백로그**일 때 할당하면 **에이전트가 트리거되지 않습니다** — 백로그는 임시 보관소이며, 이슈를 할 일 또는 진행 중으로 옮겨야만 에이전트가 대기열에 들어갑니다.
## CLI에서 할당하기
명령줄에서의 동등한 작업입니다.
```bash
multica issue assign MUL-42 --to alice
multica issue assign MUL-42 --to-id 5fb87ac7-23b5-4a7a-81fa-ed295a54545d
```
`--to`는 멤버 사용자 이름 또는 에이전트 이름(퍼지 매칭)을 받습니다. 이름이 겹칠 때 — 예를 들어 에이전트 `J` 옆에 `Cursor - J`가 있을 때 — 대신 `--to-id <uuid>`를 전달하세요. 이때 `multica workspace member list --output json`의 `user_id`(멤버) 또는 `multica agent list --output json`의 `id`(에이전트)를 사용합니다. UUID 매칭은 엄격하고 모호하지 않으므로, 스크립트나 CLI를 구동하는 에이전트에게 적합합니다. `--to`와 `--to-id`는 함께 쓸 수 없습니다.
할당 해제:
```bash
multica issue assign MUL-42 --unassign
```
## 할당 이후에 일어나는 일
백로그가 아닌 이슈가 에이전트에게 할당되면, Multica는 즉시 백그라운드에서 다음을 수행합니다.
1. 이슈에서 상속한 우선순위로 `queued` 상태의 `task`를 대기열에 넣고, 에이전트가 있는 런타임으로 라우팅합니다.
2. 에이전트의 데몬이 다음 폴링 시 `task`를 가져가 `dispatched`로 전환합니다.
3. 에이전트가 작업을 시작하면 `task`가 `running`으로 이동합니다. 완료되면 `completed` 또는 `failed`가 됩니다.
4. 실행 중에 에이전트는 이슈의 상태를 변경하고, 댓글을 남기고, 필드를 수정할 수 있습니다 — 이러한 동작은 에이전트의 신원으로 표시됩니다.
**에이전트가 오프라인인 경우**, `task`는 대기열에서 기다립니다 — **5분 후 `runtime_offline` 사유로 타임아웃되어 실패합니다**. 재시도 가능한 소스(할당, @-멘션, 채팅)에 대해서는 Multica가 자동으로 다시 대기열에 넣습니다. 전체 재시도 규칙은 [**작업**](/tasks)을 참고하세요.
할당하면 에이전트가 이슈에 자동으로 구독됩니다 — 다만 Multica에서는 **에이전트가 인박스 알림을 받지 않습니다**(멤버만 받습니다). 이 구독은 내부 기록 관리일 뿐이며 사용자에게 보이는 부작용은 없습니다.
## 재할당 또는 할당 해제
담당자를 에이전트 A에서 에이전트 B로 변경하면:
1. **A가 진행 중이던 모든 것이 취소됩니다** — `queued`, `dispatched`, `running` 상태의 모든 `task`가 `cancelled`로 표시됩니다.
2. **B에게 즉시 새 `task`가 대기열에 들어갑니다**(이슈가 백로그가 아니고 B에게 온라인 런타임이 있는 경우).
<Callout type="warning">
**재할당은 이 이슈의 모든 활성 `task`를 취소합니다 — 이전 담당자의 것만이 아닙니다.** 다른 에이전트가 @-멘션 때문에 이 이슈에서 작업 중이라면, 그 `task`도 함께 취소됩니다. 현재로서는 단일 에이전트의 `task`만 따로 취소하는 UI 동작이 없습니다.
</Callout>
할당 해제(`--unassign` 또는 선택기에서 "none" 선택)는 모든 활성 `task` 항목을 `cancelled`로 표시하며 **새 항목을 대기열에 넣지 않습니다**. 기존 구독은 자동으로 정리되지 않습니다 — 이전 담당자는 구독 목록에 남아 있습니다(다만 여전히 인박스 알림은 받지 않습니다).
## 이슈당 에이전트당 활성 `task`가 하나뿐인 이유
**단일 에이전트는 같은 이슈에서 어느 시점에든 최대 하나의 `queued` 또는 `dispatched` `task`만 가질 수 있습니다.** 데이터베이스 수준의 고유 인덱스와 클레임 로직이 이를 강제합니다 — 중복 대기열 등록과 동시 실행이 서로를 덮어쓰는 것을 방지합니다.
하지만 **서로 다른 에이전트는 같은 이슈에서 병렬로 작업할 수 있습니다** — 예를 들어 에이전트 A가 담당자이고 에이전트 B가 @-멘션된 경우, 두 `task` 항목이 각자의 런타임에서 실행되며 공존할 수 있습니다. 전체 직렬/동시 실행 규칙은 [**작업**](/tasks)을 참고하세요.
## 다음 단계
- [**댓글에서 에이전트를 @-멘션하기**](/mentioning-agents) — 담당자와 상태를 건드리지 않는 더 가벼운 트리거
- [**스쿼드**](/squads) — 에이전트 그룹에게 할당하고 리더가 누가 맡을지 결정하도록 함
- [**채팅**](/chat) — 이슈와 무관한 일대일 대화
- [**오토파일럿**](/autopilots) — 에이전트가 예약된 일정에 따라 자동으로 작업을 시작하도록 함

View File

@@ -0,0 +1,166 @@
---
title: 로그인 및 회원가입 구성
description: 이메일 + 인증 코드 로그인, Google OAuth, 회원가입 허용 목록, 로컬 테스트 코드를 구성합니다.
---
import { Callout } from "fumadocs-ui/components/callout";
import { Mermaid } from "@/components/mermaid";
Multica는 두 가지 로그인 방식을 지원합니다. **이메일 + 인증 코드**(기본값)와 **Google OAuth**(선택). 로그인에 성공하면 서버가 30일 수명의 JWT 쿠키를 발급합니다. 이 페이지에서는 각 방식을 구성하는 방법, 누가 회원가입할 수 있는지 제한하는 방법, 그리고 자체 호스팅 배포에서 가장 빠지기 쉬운 함정 하나를 다룹니다.
아래에서 참조하는 환경 변수 목록은 [환경 변수](/environment-variables)를 참고하세요. 토큰 사용법과 수명 주기 세부 사항은 [인증 및 토큰](/auth-tokens)을 참고하세요.
## 이메일 + 인증 코드 로그인의 작동 방식
사용자가 로그인 페이지에서 이메일을 입력합니다 → 서버가 6자리 코드를 보냅니다 → 사용자가 코드를 입력합니다 → 서버가 코드를 검증합니다 → JWT 쿠키가 발급됩니다. 표준 흐름입니다. 두 가지 전송 백엔드가 지원되므로 배포 환경에 맞는 쪽을 선택하세요.
### 옵션 A: Resend (클라우드 / 공용 인터넷 배포에 권장)
1. [Resend](https://resend.com/) 계정을 만들고 도메인을 인증합니다
2. API 키를 생성합니다
3. 환경 변수를 설정합니다:
```bash
RESEND_API_KEY=re_xxxxxxxxxxxxxxxx
RESEND_FROM_EMAIL=noreply@yourdomain.com # must be a domain verified in Resend
```
4. 서버를 재시작합니다
### 옵션 B: SMTP relay (자체 호스팅 / 온프레미스 배포용)
배포 환경에서 `api.resend.com`에 접근할 수 없거나 이미 내부 메일 relay(Microsoft Exchange, Postfix, 온프레미스 SendGrid 등)가 있는 경우에 사용하세요. 둘 다 설정된 경우 `SMTP_HOST`가 `RESEND_API_KEY`보다 우선합니다. `SMTP_HOST`가 비어 있지 않으면 `RESEND_API_KEY`도 함께 구성되어 있더라도 서버는 항상 SMTP를 통하므로, 인증 및 초대 메일이 내부 네트워크를 벗어나는 일이 결코 없습니다.
SMTP 경로는 대부분의 온프레미스 메일 서버(특히 Microsoft Exchange의 receive connector)가 노출하는 세 가지 relay 모드를 지원합니다:
| 모드 | 포트 | 인증 | TLS |
|---|---|---|---|
| 익명 내부 relay | `25` | 없음 — IP / 서브넷으로 제출을 신뢰 | 전송 경로상 없음(내부 세그먼트 전용) |
| 인증된 제출(submission) | `587` | `SMTP_USERNAME` + `SMTP_PASSWORD` | STARTTLS, 자동 업그레이드 |
| 암묵적 TLS (SMTPS) | `465` | — | **아직 지원하지 않음** — 포트 25 또는 587을 사용하세요 |
**포트 25의 익명 Exchange relay** — 자격 증명 없이 신뢰된 서브넷에서 오는 메일을 받아들이는 일반적인 "internal SMTP relay" / Exchange 익명 receive connector:
```bash
SMTP_HOST=exchange.internal.example.com
SMTP_PORT=25
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_TLS_INSECURE=false
RESEND_FROM_EMAIL=noreply@yourdomain.com # reused as the From: header
```
**포트 587의 인증된 제출** — 서비스 계정이 필요한 relay용. 서버가 STARTTLS 지원을 알리면 자동으로 업그레이드됩니다:
```bash
SMTP_HOST=smtp.internal.example.com
SMTP_PORT=587
SMTP_USERNAME=multica
SMTP_PASSWORD=...
SMTP_TLS_INSECURE=false # set true only for self-signed / private CA
RESEND_FROM_EMAIL=noreply@yourdomain.com
```
시작 시 서버는 선택한 제공자를 출력합니다. 예를 들어 `EmailService: SMTP relay exchange.internal.example.com:25 from=noreply@example.com`(또는 `Resend API` / `DEV mode`)와 같이 표시됩니다. 비밀번호는 절대 로그에 기록되지 않습니다. 재시작 후 SMTP 줄이 보이지 않는다면 `SMTP_HOST`가 프로세스에 도달하지 못한 것이므로, 컨테이너 환경(`docker compose -f docker-compose.selfhost.yml exec backend env | grep SMTP`)을 확인하세요.
**둘 다 설정하지 않으면**: 서버는 오류를 내지 않지만, **전송되어야 했던 모든 이메일이 서버의 stdout에만 기록됩니다**. 로컬 개발에는 편리하지만(로그에서 코드를 복사하면 됩니다), 프로덕션에서는 블랙홀이 됩니다.
## 고정 로컬 테스트 코드
<Callout type="warning">
**공용으로 접근 가능한 인스턴스에서는 고정 인증 코드를 활성화하지 마세요.**
프로덕션이 아닌 인스턴스가 기본적으로 `888888`을 받아들이던 기존 동작은 제거되었습니다. 명시적으로 구성하지 않는 한 `888888`을 입력하는 것은 다른 잘못된 코드와 동일하게 처리됩니다.
이메일 백엔드를 전혀 구성하지 않은(Resend도 SMTP도 없는) 로컬 개발에서는 서버 로그에 출력되는 생성된 코드를 사용해야 합니다. 결정적인 로컬/사설 자동화가 필요하다면 `MULTICA_DEV_VERIFICATION_CODE`를 `888888` 같은 6자리 값으로 설정하고 `APP_ENV`를 프로덕션이 아닌 값으로 유지하세요:
```bash
APP_ENV=development
MULTICA_DEV_VERIFICATION_CODE=888888
```
이 단축키는 `APP_ENV=production`일 때 무시됩니다.
</Callout>
프로덕션 배포에서는 `MULTICA_DEV_VERIFICATION_CODE`를 비워 두고 `APP_ENV=production`으로 설정해야 합니다. `make selfhost` / `docker-compose.selfhost.yml`로 배포하는 경우 `APP_ENV`는 기본적으로 `production`입니다.
## Google OAuth 구성
선택 사항입니다. 구성하지 않으면 이메일 + 인증 코드만 사용할 수 있고, 구성하면 로그인 페이지에 "Google로 로그인" 버튼이 추가됩니다.
1. [Google Cloud Console](https://console.cloud.google.com/)에서 OAuth 2.0 클라이언트를 생성합니다
2. **승인된 리디렉션 URI**(Authorized redirect URIs)를 Multica 프런트엔드 주소에 `/auth/callback`을 더한 값으로 설정합니다. 예:
```text
https://multica.yourdomain.com/auth/callback
```
3. 클라이언트 ID와 클라이언트 secret을 얻은 후 세 개의 환경 변수를 설정합니다:
```bash
GOOGLE_CLIENT_ID=xxxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxx
GOOGLE_REDIRECT_URI=https://multica.yourdomain.com/auth/callback
```
4. 서버를 재시작합니다.
**런타임에 적용됨**: 프런트엔드는 `/api/config`를 통해 런타임에 이 설정을 읽습니다. 변경 후 서버를 재시작하면 프런트엔드가 다시 빌드하거나 다시 배포할 필요 없이 새 값을 가져옵니다.
<Callout type="warning">
**리디렉션 URI는 Google Console과 `GOOGLE_REDIRECT_URI` 양쪽에서 정확히 일치해야 합니다** — 프로토콜(`http` vs `https`), 끝의 슬래시, 포트까지 포함합니다. 조금이라도 일치하지 않으면 Google이 전체 OAuth 흐름을 거부하며, 사용자에게 표시되는 오류는 `redirect_uri_mismatch`입니다.
</Callout>
## 누가 회원가입할 수 있는지 제한하기
세 개의 환경 변수가 우선순위에 따라 조합됩니다:
<Mermaid chart={`
graph TD
Start[New user first sign-in] --> A{Email in<br/>ALLOWED_EMAILS?}
A -- Yes --> Allow[Allow signup]
A -- No --> B{Domain in<br/>ALLOWED_EMAIL_DOMAINS?}
B -- Yes --> Allow
B -- No --> C{Any allowlist<br/>non-empty?}
C -- Yes --> Block[Reject]
C -- No --> D{ALLOW_SIGNUP<br/>= true?}
D -- Yes --> Allow
D -- No --> Block
`} />
**기존 사용자는 언제든 다시 로그인할 수 있습니다** — 회원가입 허용 목록은 **최초 회원가입**에만 적용되며, 돌아오는 사용자는 막지 않습니다.
- **`ALLOWED_EMAILS`** (최고 우선순위) — 명시적 이메일 허용 목록, 쉼표로 구분합니다. **비어 있지 않으면 목록에 있는 이메일만 회원가입할 수 있습니다.**
- **`ALLOWED_EMAIL_DOMAINS`** — 도메인 허용 목록, 쉼표로 구분합니다(예: `company.io,partner.com`).
- **`ALLOW_SIGNUP`** — 마스터 스위치, 기본값 `true`. `false`로 설정하면 회원가입이 완전히 비활성화됩니다.
<Callout type="warning">
**세 계층은 OR가 아니라 AND 의미입니다.** 흔한 잘못된 직관은 `ALLOWED_EMAIL_DOMAINS=company.io` + `ALLOW_SIGNUP=true`가 "company.io에 더해 다른 모든 사람을 허용"한다는 것입니다. 그렇지 **않습니다**. 어느 계층이든 비어 있지 않은 값이 있으면 **그에 일치하지 않는 이메일은 곧바로 거부되며**, `ALLOW_SIGNUP=true`는 그것을 무효로 만들지 못합니다.
실제로 "모두 허용"하려면 세 변수를 모두 비워 두세요(또는 `ALLOW_SIGNUP=true`를 유지하세요).
</Callout>
**일반적인 구성**:
| 목표 | 구성 |
|---|---|
| 내부 전용, `company.io` 직원만 | `ALLOWED_EMAIL_DOMAINS=company.io` |
| 내부 + 소수의 외부 협업자 | `ALLOWED_EMAIL_DOMAINS=company.io` + 협업자 주소를 `ALLOWED_EMAILS`에 추가 |
| 셀프서비스 회원가입을 완전히 비활성화, 초대 전용 | `ALLOW_SIGNUP=false` |
| 개방형 회원가입(프로덕션에는 권장하지 않음) | 셋 다 비움 |
## 회원가입을 비활성화해도 사람을 초대할 수 있나요?
**이미 Multica 계정이 있는 사람만 가능합니다.** 초대 수락은 회원가입 허용 목록을 확인하지 않습니다. 초대받은 사람이 이미 회원가입한 상태라면(예: 다른 워크스페이스에서), 초대 링크를 클릭하고 로그인하면 수락할 수 있습니다.
**하지만 한 번도 회원가입하지 않은 사람은 초대로 구제할 수 없습니다.** 수락하기 전에 먼저 로그인해야 하고, 로그인의 첫 단계(인증 코드 요청)는 회원가입 허용 목록 검사를 거칩니다. `ALLOW_SIGNUP=false`이거나 그들의 이메일이 `ALLOWED_EMAILS` / `ALLOWED_EMAIL_DOMAINS`에 없으면 **회원가입을 완료할 수 없으며**, 따라서 초대도 수락할 수 없습니다.
아직 회원가입하지 않은 외부 협업자를 초대하려면: 그들의 이메일을 `ALLOWED_EMAILS`에 임시로 추가하고, 그들이 회원가입하고 초대를 수락하기를 기다린 다음 항목을 제거하세요.
초대를 만들고 사용하는 방법은 [멤버 및 역할](/members-roles)을 참고하세요.
## 다음
- [환경 변수](/environment-variables) — 이 페이지에서 사용하는 모든 변수의 전체 정의
- [인증 및 토큰](/auth-tokens) — JWT / PAT / 데몬 토큰의 분류와 사용법
- [문제 해결](/troubleshooting) — 인증 코드 미수신, OAuth `redirect_uri_mismatch`, 회원가입 거부

View File

@@ -0,0 +1,80 @@
---
title: 인증과 토큰
description: Multica에는 세 종류의 토큰이 있습니다 — 브라우저, CLI, 데몬에 각각 하나씩. 어떤 상황에 무엇을 쓰는지 설명합니다.
---
import { Callout } from "fumadocs-ui/components/callout";
Multica에는 세 종류의 토큰이 있으며, 각각 하나의 컨텍스트에 대응합니다: 브라우저 Web UI, 명령줄과 스크립트, 그리고 데몬입니다. 세 가지 모두 동일한 당신을 나타내지만, 범위와 수명이 다릅니다.
## 세 가지 토큰
| 토큰 | 형식 | 사용되는 곳 | 수명 |
|---|---|---|---|
| **JWT 쿠키** | `multica_auth` 쿠키 (HttpOnly) | 웹 브라우저 | 30일 |
| **개인 액세스 토큰 (PAT)** | `mul_` 접두사 | CLI, 스크립트, 직접 API 호출 | 기본적으로 만료 없음. API로 생성할 때 `expires_in_days`를 전달할 수 있습니다 |
| **데몬 토큰** | `mdt_` 접두사 | 데몬-서버 통신 | 데몬 자체가 관리 |
일상적인 사용에서는 앞의 두 가지만 직접 다루게 됩니다. **[데몬](/daemon-runtimes) 토큰**은 `multica daemon login`이 자동으로 생성하고 갱신하므로, 신경 쓸 필요가 없습니다.
## 각 토큰이 접근할 수 있는 것
| API 라우트 | JWT 쿠키 | PAT | 데몬 토큰 |
|---|---|---|---|
| `/api/user/*` (사용자 수준 작업) | ✓ | ✓ | ✗ |
| `/api/workspaces/:id/*` (워크스페이스 수준) | ✓ | ✓ | ✗ |
| `/api/daemon/*` (데몬 전용) | ✗ | ✓ | ✓ |
| WebSocket `/ws` (실시간 푸시) | ✓ (쿠키) | ✓ (첫 메시지로 인증) | ✗ |
**PAT는 거의 모든 것에 접근할 수 있습니다** — 이는 "완전한 당신"을 나타냅니다. 데몬 토큰은 데몬에 필요한 일, 즉 작업을 가져오고 결과를 보고하는 것만 할 수 있습니다.
**둘 다 `/api/daemon/*`에 접근할 수 있지만, 범위가 다릅니다.** PAT는 **사용자 전체**를 나타내며 — 일단 인증되면 당신이 속한 모든 워크스페이스를 볼 수 있습니다. 데몬 토큰은 생성 시점에 단일 워크스페이스에 고정되며 해당 워크스페이스의 리소스에만 접근할 수 있습니다. 프로덕션에서는 데몬을 데몬 토큰으로 실행하세요. 편의를 위해 PAT를 사용하는 지름길을 택하지 마세요. 그렇지 않으면 데몬에 필요한 것보다 훨씬 많은 권한을 부여하게 됩니다.
## 로그인
### Email + 인증 코드
1. 이메일을 입력하면 서버가 6자리 코드를 보냅니다.
2. 코드를 입력하면 서버가 JWT 쿠키를 발급(브라우저)하거나 PAT로 교환(CLI)합니다.
<Callout type="warning">
**자체 호스팅 운영자는 주의하세요**: 공개 배포에서는 `MULTICA_DEV_VERIFICATION_CODE`를 비워 두세요. 고정된 로컬 테스트 코드를 활성화하면, `APP_ENV`가 production이 아닌 동안 코드를 요청할 수 있는 누구나 그 값으로 로그인할 수 있습니다. [자체 호스팅 인증 구성](/auth-setup)을 참고하세요.
</Callout>
### Google OAuth
**Sign in with Google**을 클릭하고 표준 OAuth 콜백을 거치세요. 자체 호스팅에는 `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, 그리고 리디렉션 URI를 구성해야 합니다 — [자체 호스팅 인증 구성](/auth-setup)을 참고하세요.
## PAT 생성, 조회, 취소
PAT **생성**은 두 가지 방법으로 할 수 있습니다:
- **Web UI**: 설정 → API 토큰 → 새 토큰
- **CLI**: `multica login`은 아직 로컬 PAT가 없으면 자동으로 하나를 생성합니다
<Callout type="warning">
**전체 PAT는 생성될 때 정확히 한 번만 표시됩니다.** 새로 고침하거나 대화상자를 닫은 후에는 다시 볼 수 없습니다.
Multica는 데이터베이스에 PAT의 해시만 저장합니다 — 서버조차 원본을 가져올 수 없습니다. 즉시 복사하여 저장하세요. 분실하면 유일한 방법은 취소하고 새로 생성하는 것입니다.
</Callout>
기존 PAT **조회**(이름, 생성 시각, 마지막 사용 시각 — 전체 토큰은 **포함하지 않음**)는 설정 → API 토큰에 있습니다.
PAT **취소**: 목록에서 Revoke를 클릭하세요. 취소는 즉시 적용됩니다 — 그 PAT로 보낸 다음 요청은 401로 거부됩니다.
## 로그아웃은 로컬 토큰만 삭제합니다
`multica auth logout`을 실행하거나 Web UI에서 로그아웃을 클릭하면:
- **로컬 토큰이 지워집니다** — CLI는 `~/.multica/config.json`에서 PAT를 제거하고, 브라우저는 쿠키를 삭제합니다.
- **PAT는 서버에서 여전히 유효합니다** — 로그아웃하기 전에 누군가 당신의 PAT를 입수했다면(예를 들어 다른 기기에 복사했다면), 그들은 **여전히 그것을 사용할 수 있습니다**.
<Callout type="warning">
**PAT가 유출되었다고 의심되면, 단순히 로그아웃하지 마세요.** 설정 → API 토큰로 가서 그 토큰을 **취소**하세요. 취소만이 유출된 토큰을 즉시 무효화합니다.
</Callout>
## 다음 단계
- [CLI 명령어 레퍼런스](/cli) — 모든 CLI 명령어의 인증은 자동입니다
- [자체 호스팅 인증 구성](/auth-setup) — 자체 호스팅 시 이메일, OAuth, 가입 허용 목록을 구성하는 방법
- [데몬과 런타임](/daemon-runtimes) — 데몬 토큰이 어디서 오는지

View File

@@ -0,0 +1,239 @@
---
title: 오토파일럿
description: 에이전트가 cron 스케줄, 인바운드 webhook으로 작업을 시작하거나, UI나 CLI로 한 번 수동 트리거하게 합니다.
---
import { Callout } from "fumadocs-ui/components/callout";
오토파일럿은 [에이전트](/agents)가 **스케줄에 따라 자동으로 작업을 시작**하게 합니다 — cron 표현식과 타임존을 설정하면, 여러분이 아무것도 트리거하지 않아도 Multica가 알아서 [`task`](/tasks)를 디스패치합니다. 정기 점검, 반복 보고서, 야간 정리 작업 등 "상시 지시(standing order)" 형태의 작업에 잘 맞습니다. 나머지 세 가지 트리거 경로([할당](/assigning-issues), [@-멘션](/mentioning-agents), [채팅](/chat) — 모두 여러분이 직접 시작하는 방식)와 비교했을 때, 오토파일럿의 핵심 차이는 **시간 기반**이라는 점입니다.
## 오토파일럿 구성하기
워크스페이스의 **오토파일럿** 페이지에서 새 오토파일럿을 만듭니다. 다음 항목을 설정합니다.
- **이름(Name)** — 표시 이름
- **에이전트(Agent)** — 실행을 디스패치할 대상
- **우선순위(Priority)** — 생성되는 `task`에 상속됩니다(이슈 우선순위와 동일한 의미)
- **설명 / 프롬프트(Description / prompt)** — 매 실행마다 에이전트가 받는 작업 설명
- **실행 모드(Execution mode)** — 아래 참고
- **트리거(Triggers)** — `schedule`(cron + 타임존) 또는 `webhook` 중 최소 하나
## 실행 모드 선택하기
오토파일럿에는 두 가지 실행 모드가 있습니다. **"이슈 생성" 모드부터 시작하세요.**
- **이슈 생성 모드(Create issue mode)**(`create_issue`) — 기본값이며 **권장**됩니다. 각 트리거는 먼저 워크스페이스에 이슈를 생성한 다음(제목에는 현재 단일 플레이스홀더 `{{date}}` 하나만 지원되며, 이는 `YYYY-MM-DD` 형식의 UTC 날짜로 보간됩니다. 그 외의 `{{...}}` 토큰은 생성 시점에 거부되므로, 오타가 이슈 제목에 리터럴 문자열로 조용히 들어가는 일을 막습니다), 일반 할당 흐름을 통해 그 이슈를 에이전트에게 할당합니다. 모든 작업은 수동으로 할당한 이슈와 동일한 히스토리, 댓글, 상태를 가진 채 이슈 보드에 올라갑니다.
- **실행 전용 모드(Run-only mode)**(`run_only`) — 이슈 생성을 건너뛰고 `task`를 곧바로 대기열에 넣습니다. 이 실행은 보드에 표시되지 않으며 — 오토파일럿의 실행 히스토리에서만 확인할 수 있습니다.
## 스케줄에 따라 실행하기
모든 오토파일럿에는 최소 하나의 `schedule` 트리거가 필요합니다. Cron은 **표준 5필드 형식**(분 시 일 월 요일)을 사용하며, 최소 단위는 **1분**입니다(초 단위는 없음). 타임존은 IANA 형식(예: `Asia/Shanghai`)이며, cron 표현식이 어느 타임존으로 해석될지를 결정합니다.
몇 가지 예시입니다.
- `0 9 * * 1-5`, `Asia/Shanghai` — 평일 베이징 시간 오전 9시
- `*/30 * * * *`, `UTC` — 30분마다
- `0 3 * * *`, `UTC` — 매일 UTC 오전 3시
Multica 서버는 **30초**마다 만료된 트리거를 스캔합니다 — **실제 발화 시각은 최대 30초까지 지연될 수 있으며**, 초 단위로 정확하지는 않습니다. 발화 시각 즈음에 서버가 재시작되면, 시작 시 놓친 트리거를 따라잡습니다(아무것도 유실되지 않지만 즉시 발화됩니다).
## 수동으로 한 번 트리거하기
오토파일럿을 디버깅하는 동안 cron을 기다리지 않으려면 수동으로 트리거하세요.
- UI: 오토파일럿 상세 페이지에서 "Run now" 클릭
- CLI:
```bash
multica autopilot trigger <autopilot-id>
```
수동 트리거는 `schedule` 트리거와 완전히 동일한 실행 흐름을 거치며 — 실행 레코드의 `source` 필드만 `manual`로 표시됩니다.
## Webhook으로 트리거하기
오토파일럿은 인바운드 HTTP webhook으로도 발화할 수 있습니다. 오토파일럿 상세 페이지에서 **Webhook** 트리거를 추가하면, Multica가 다음과 같은 형태의 고유 URL을 생성합니다.
```
https://<your-multica-host>/api/webhooks/autopilots/awt_…
```
그 URL로 아무 JSON이나 POST하세요 — Multica는 `source = webhook`로 실행을 기록하고, 본문을 실행의 `trigger_payload`로 저장한 다음, schedule 트리거와 정확히 동일한 방식으로 에이전트를 디스패치합니다.
```bash
curl -X POST "$MULTICA_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{"event":"demo.received","eventPayload":{"message":"hello"}}'
```
**이슈 생성 모드**에서는 인바운드 payload가 새 이슈의 설명에 덧붙여져 에이전트가 인라인으로 읽을 수 있습니다. **실행 전용 모드**에서는 payload가 데몬이 에이전트에게 넘기는 실행 컨텍스트의 일부가 됩니다.
### Payload 형태
직접 만든 봉투(envelope)를 보낼 수 있습니다.
```json
{ "event": "github.pull_request.opened", "eventPayload": { } }
```
…또는 임의의 JSON 객체/배열을 보낼 수도 있습니다. Multica는 이를 내부 봉투로 정규화합니다.
```json
{
"event": "<inferred>",
"eventPayload": <your body>,
"request": { "receivedAt": "<rfc3339>", "contentType": "application/json" }
}
```
`event` 필드를 제공하지 않으면, Multica는 일반적인 헤더와 본문 필드로부터 이를 추론합니다(`X-GitHub-Event` + 본문 `action`, `X-Gitlab-Event`, `X-Event-Type`, 본문의 `event`/`type`/`action`). 어느 것도 일치하지 않으면 이벤트는 `webhook.received`가 됩니다.
GitHub 같은 소스를 구성할 때는 content type을 `application/json`으로 설정하세요 — 폼 인코딩된 webhook payload는 허용되지 않습니다.
### 이벤트 필터
새 webhook 트리거는 인바운드 POST마다 발화합니다. 단일 용도 URL에는 괜찮지만, 여러 이벤트 타입을 팬아웃하는 소스(GitHub가 대표적입니다 — 단일 저장소 webhook 하나가 `push`, `pull_request`, `workflow_run`, `check_suite` 등을 전달할 수 있습니다)에는 시끄럽습니다. webhook 트리거의 **이벤트 필터(Event filters)** 섹션을 사용하면 실제로 실행을 디스패치할 이벤트를 제한할 수 있으며, 그 외의 모든 것은 `status = ignored`, `reason = event_filtered`로 전달 히스토리에 기록되고 실행이나 이슈는 생성되지 않습니다.
각 행은 하나의 규칙입니다. **이벤트 이름(event name)**과 선택적으로 쉼표로 구분한 **action** 목록으로 구성됩니다. Multica는 **어느 한** 행이라도 일치하면 webhook을 허용합니다. 섹션을 비워 두면 모든 것을 수락합니다(필터링 이전 동작).
예시:
| 이벤트 이름 | Actions | 일치 대상 |
| -------------- | ------------------- | ------------------------------------------------------------------------ |
| `workflow_run` | `completed, failed` | `action: completed` 또는 `action: failed`인 `workflow_run` 이벤트만 |
| `workflow_run` | _(비어 있음)_ | action에 상관없이 모든 `workflow_run` 이벤트 |
| `push` | _(비어 있음)_ | 모든 `push` 이벤트 |
#### 이벤트 이름과 action의 출처
Multica는 다음 순서로 인바운드 요청에서 `event` 이름과 `action`을 도출하며 — **첫 번째로 일치하는 것이 우선합니다**.
**1. 본문 봉투(Body envelope).** 본문이 문자열 `event` 필드를 가진 JSON 객체이면, 그 값이 곧 이벤트 이름입니다. 선택적인 `eventPayload` 객체는 자신의 `action` / `state` / `conclusion` / `status` 필드에서 action 후보를 제공합니다.
```bash
curl -X POST "$MULTICA_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"event":"trigger","eventPayload":{"action":"true"}}'
# inferred: event = trigger, action candidate = true
```
**2. 헤더(Headers).** 본문 봉투가 없을 때, Multica는 다음의 잘 알려진 제공자 헤더를 읽습니다.
- `X-GitHub-Event: <event>` — (있는 경우) 최상위 본문 `action` 필드와 결합해 `github.<event>.<action>`을 형성합니다.
- `X-Gitlab-Event: <event>` — `gitlab.<event>`가 됩니다.
- `X-Event-Type: <event>` — 그대로 통과합니다.
```bash
# GitHub-style: header gives the event name, body gives the action.
curl -X POST "$MULTICA_WEBHOOK_URL" \
-H 'X-GitHub-Event: workflow_run' \
-H 'Content-Type: application/json' \
-d '{"action":"completed"}'
# inferred: event = github.workflow_run.completed
# → matches a filter row of workflow_run / completed
# Generic event-type header — no body fields needed.
curl -X POST "$MULTICA_WEBHOOK_URL" \
-H 'X-Event-Type: trigger.true' \
-H 'Content-Type: application/json' \
-d '{}'
# inferred: event = trigger.true → matches trigger / true
```
**3. 본문 폴백(Body fallback).** 본문 봉투도 알려진 헤더도 없으면, Multica는 다음 순서로 최상위 본문 문자열 필드로 폴백합니다: `event` → `type` → `action`.
```bash
curl -X POST "$MULTICA_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"type":"trigger","action":"true"}'
# inferred: event = trigger (from `type`), action candidate = true
```
**4. 기본값(Default).** 위 어느 것도 일치하지 않으면, 이벤트는 `webhook.received`이고 action 후보는 없습니다.
**action 후보, 전체 목록.** 이벤트가 결정되면, Multica는 아래의 모든 값을 가능한 action 일치 대상으로 고려합니다.
- 이벤트가 `provider.event.<action>` 형태일 때의 이벤트 이름 접미사(예: `github.workflow_run.completed` → `completed`).
- 본문 필드 `action`, `state`, `conclusion`, `status` — **JSON 문자열일 때만 해당됩니다**. 불리언(`{"action": true}`)이나 숫자는 자격이 없으므로, `event=trigger, action=true`를 기대하는 필터는 `{"trigger": true}` 본문과 절대 일치하지 않습니다. `true`는 문자열이 아니라 bool이기 때문입니다.
**흔한 함정.** `Event name: trigger` / `Actions: true` 같은 필터 행은 "본문에 `trigger: true`가 있으면 발화하라"는 뜻이 **아닙니다** — 이벤트 필터는 임의의 본문 필드가 아니라 *추론된 이벤트와 action*에 일치시킵니다. 이를 적중시키려면 `X-Event-Type`로 `trigger.true`를 보내거나(또는 위에 표시된 본문 봉투를 사용하세요). 저장된 필터 행에서 주변 공백(`" workflow_run "`)은 그대로 저장되어 절대 일치하지 않으므로 — 저장하기 전에 trim하세요.
#### 빠른 테스트
필터를 구성한 후에는 `curl`로 두 분기를 모두 확인할 수 있습니다.
```bash
# Allowed — header drives event=workflow_run, body drives action=completed
curl -X POST "$MULTICA_WEBHOOK_URL" \
-H 'X-GitHub-Event: workflow_run' \
-H 'Content-Type: application/json' \
-d '{"action":"completed"}'
# → 200 {"status":"accepted", ...}
# Filtered — same event, action not in allowlist
curl -X POST "$MULTICA_WEBHOOK_URL" \
-H 'X-GitHub-Event: workflow_run' \
-H 'Content-Type: application/json' \
-d '{"action":"in_progress"}'
# → 200 {"status":"ignored","reason":"event_filtered"}
```
### URL은 bearer secret입니다
생성된 URL **자체가** 자격 증명입니다. 그것을 가진 사람은 누구나 오토파일럿을 발화할 수 있습니다. 토큰처럼 다루세요.
- **공개된 이슈 스레드, 스크린샷, 채팅 히스토리에 붙여넣지 마세요.**
- **유출되면 교체하세요** — 트리거 행에서 "Rotate URL"을 클릭하거나 `multica autopilot trigger-rotate-url <autopilot-id> <trigger-id>`를 실행하세요. 기존 URL은 즉시 작동을 멈춥니다.
- 강력한 소스 인증이 필요한 소스의 경우, 트리거별 HMAC 서명 검증을 기다리세요. 이 v1 URL은 bearer 방식만 지원합니다.
- 현재로서는 오토파일럿을 볼 수 있는 워크스페이스 멤버라면 그 webhook URL을 읽을 수 있습니다 — 역할별로 더 엄격한 secret 가시성은 후속 작업입니다.
### 상태 코드 의미
Multica는 정상적인 no-op 결과에 대해 `status` 필드와 함께 `200 OK`를 반환하므로, 제공자의 webhook 재시도 메커니즘이 URL을 계속 두드리지 않습니다.
- `{"status":"accepted","run_id":"…","autopilot_id":"…","trigger_id":"…"}` — 실행이 디스패치되었습니다.
- `{"status":"skipped","run_id":"…","reason":"agent runtime is offline at dispatch time"}` — 수신 대상의 런타임이 오프라인이며, `skipped` 실행으로 기록됩니다.
- `{"status":"ignored","reason":"trigger_disabled"}` — 트리거가 비활성화되어 있습니다.
- `{"status":"ignored","reason":"autopilot_paused"}` — 오토파일럿이 일시 정지되어 있습니다.
- `{"status":"ignored","reason":"autopilot_archived"}` — 오토파일럿이 보관되어 있습니다.
2xx가 아닌 응답은 실제 실패를 다룹니다.
- `400` — 유효하지 않은 JSON, 스칼라 본문, 빈 본문.
- `404` — 알 수 없는 토큰(`{"error":"webhook not found"}`).
- `413` — payload가 256 KiB를 초과했습니다.
- `429` — 토큰별 속도 제한 초과(기본값 60 req/min).
### 자체 호스팅: 공개 URL 구성하기
서버에 `MULTICA_PUBLIC_URL`이 설정되어 있으면(예: `https://multica.example.com`), 트리거 응답에 절대 경로 `webhook_url`이 포함되고 UI에는 복사 가능한 URL이 바로 표시됩니다. 설정하지 않으면 UI는 클라이언트의 API origin으로부터 URL을 구성합니다 — 데스크톱과 동일 출처 웹에는 괜찮지만, 커스텀 자체 호스팅 리버스 프록시에는 적합하지 않습니다. Multica는 잘못 구성된 리버스 프록시가 공격자가 제어하는 호스트를 가리키는 webhook URL을 서버가 발행하도록 속이지 못하게, `Host` / `X-Forwarded-Host` 헤더로부터 공개 호스트를 도출하지 않도록 의도적으로 설계되었습니다.
## 실행 히스토리 보기
모든 트리거는 **실행 레코드(run record)**를 생성하며, 오토파일럿 상세 페이지의 "History" 탭에서 볼 수 있습니다.
- 트리거 소스(`schedule` / `manual` / `webhook`)
- 시작 시각, 완료 시각
- 상태(`issue_created` / `running` / `completed` / `failed` / `skipped`)
- 연결된 이슈(이슈 생성 모드) 또는 `task`(실행 전용 모드)
- 실패 사유(실패하거나 건너뛴 경우)
## 오토파일럿이 실패하면 어떻게 되나요
<Callout type="warning">
**오토파일럿 실패는 자동으로 재시도되지 않으며 인박스 알림도 보내지 않습니다.** 실패는 실행 히스토리에 `failed` 항목을 남길 뿐 — 할당이나 @-멘션처럼 시스템 수준에서 다시 대기열에 넣는 일도 없고, 누구에게도 알림이 가지 않습니다. 오토파일럿이 주기적이라면 **다음 cron 발화가 새 실행을 트리거**하지만, 실패한 작업이 자동으로 다시 실행되지는 않습니다.
오토파일럿이 중요하다면 직접 모니터링을 설계하세요 — 예를 들어 에이전트가 성공 시 댓글을 남기게 하고, 댓글이 누락된 것을 알아채 실패를 잡아내는 식입니다.
</Callout>
자동 재시도가 없는 이유: 오토파일럿은 이미 주기적이므로, 시스템 수준의 재시도를 추가하면 다음 예약된 실행 위에 겹쳐 중복 실행을 만들어 냅니다. 스케줄링을 전적으로 cron에 맡기면 깔끔하게 유지됩니다.
## 아직 제공되지 않는 기능
**API 종류의 트리거는 아직 연결되어 있지 않습니다.** 트리거 스키마는 `api` 종류를 예약해 두었지만, 그것을 발화하는 인그레스 라우트가 없습니다. UI는 기존 행에 Deprecated 배지를 표시하며 복사/교체 기능을 제공하지 않습니다. 트리거별 HMAC 서명 검증, IP 허용 목록, 제공자별 이벤트 프리셋은 후속 작업으로 추적되고 있으며, v1 URL은 bearer 방식만 지원합니다.
## 다음
- [**에이전트에게 이슈 할당하기**](/assigning-issues) — 이슈를 에이전트에게 일회성으로 넘기기
- [**댓글에서 에이전트 @-멘션하기**](/mentioning-agents) — 댓글에서 에이전트를 불러 한번 살펴보게 하기
- [**채팅**](/chat) — 이슈 밖에서의 일대일 대화

View File

@@ -0,0 +1,63 @@
---
title: 채팅
description: 어떤 이슈에도 속하지 않는, 에이전트와의 일대일 대화 — 완전히 샌드박스화되어 있습니다. 에이전트는 이슈를 보거나 변경할 수 없으며, 다른 누구도 이 대화를 볼 수 없습니다.
---
import { Callout } from "fumadocs-ui/components/callout";
**채팅은 당신과 [에이전트](/agents) 사이의 일대일 대화입니다** — [이슈](/issues) 보드에서 벗어나는 것입니다. 에이전트는 어떤 이슈도 보지 못하고 어떤 이슈도 변경할 수 없으며, 대화 전체가 **완전히 비공개**입니다([워크스페이스](/workspaces) 내의 다른 누구도, admin을 포함해서, 이 대화를 볼 수 없습니다). 에이전트와 접근 방식을 논의하거나, 브레인스토밍을 하거나, 어떤 이슈에도 속하지 않는 질문을 하기에 적합합니다.
## 그냥 에이전트를 @-멘션하면 안 되나요?
[@-멘션](/mentioning-agents)은 에이전트를 이슈의 컨텍스트 **안으로 끌어들입니다** — 에이전트는 이슈 설명과 모든 과거 댓글을 읽고, 이슈를 변경할 수 있습니다. 채팅은 이를 뒤집습니다. **당신을 이슈 밖으로 끌어냅니다** — 에이전트는 이 단일 대화만 볼 수 있고, 어떤 이슈의 존재도 인지하지 못하며, 이슈를 수정할 진입점도 없습니다.
두 가지 판단 기준:
- 특정 이슈의 컨텍스트에 기반한 피드백을 원할 때 → [@-멘션](/mentioning-agents)
- 어떤 이슈와도 무관한 주제를 논의하고 싶을 때(또는 다른 누구에게도 논의를 보이고 싶지 않을 때) → 채팅
## 대화 시작하기
사이드바에서 **채팅**을 열고, 에이전트를 선택한 다음, 새 대화를 시작하세요. 인터페이스는 여느 메시징 앱과 비슷합니다. 메시지를 보내면 에이전트가 답장합니다. 각 메시지는 백그라운드에서 실행을 트리거하므로(대기열에 들어간 `task`), 답장에는 몇 초가 걸릴 수 있습니다.
## 채팅에서 에이전트가 할 수 있는 일과 할 수 없는 일
에이전트는 대화 안에서 **완전히 샌드박스화된** 모드로 실행됩니다.
**할 수 있는 일:**
- 현재 메시지에 담긴 질문에 답하기
- 구성된 [스킬](/skills)과 MCP 사용하기
- 자신의 작업 디렉터리에서 파일 읽기 및 쓰기
- 이슈 컨텍스트가 필요 없는 `multica` CLI 명령 호출하기(예: 기본 워크스페이스 정보 조회)
**할 수 없는 일:**
- **어떤 이슈도 보기** — 에이전트가 받는 프롬프트에는 이슈 ID가 없으며, `multica issue list` 같은 명령은 빈 결과를 반환합니다
- **어떤 이슈도 변경하기** — 이슈 컨텍스트가 없으면 권한 검사에 의해 API 호출이 차단됩니다
- **다른 대화 보기** — 대화는 완전히 격리되어 있습니다
- **누구도, 어떤 에이전트도 @-멘션하기** — 채팅은 다른 사람에게 알릴 경로가 없는 비공개 공간입니다
## 여러 턴에 걸친 컨텍스트가 보존되는 방식
채팅은 **제공자 세션 재개**를 통해 여러 턴에 걸친 컨텍스트를 유지합니다 — 에이전트는 첫 답장에서 제공자 세션을 설정하고(예: Claude 세션), 그 세션 ID가 저장됩니다. 다음 메시지에서는 작업 디스패치가 그 ID를 다시 전달하므로, 에이전트는 매번 기록을 다시 읽지 않고도 **중단했던 지점부터 이어서 재개**합니다.
만약 **한 턴이 실패하면**, Multica는 세션 ID를 설정했던 이전 작업(그 작업이 성공했든 실패했든)을 찾아 재개를 시도합니다 — 중간에 한 번 실패한다고 해서 대화 전체의 기억이 사라지지는 않습니다.
참고: 모든 제공자가 실제로 세션 재개를 구현하는 것은 아닙니다 — 지원 현황은 [**제공자 매트릭스**](/providers)를 참고하세요.
## 대화 보관하기
더 이상 보고 싶지 않은 대화는 보관할 수 있습니다 — 대화 목록에서 우클릭하거나 상세 페이지의 "보관" 버튼을 사용하세요. 보관 후에는:
- 대화가 활성 목록에서 사라집니다("보관됨" 보기에서 여전히 찾을 수 있습니다)
- 과거 메시지, 세션 ID, 작업 디렉터리가 모두 보존됩니다 — 아무것도 삭제되지 않습니다
<Callout type="warning">
**보관 후에는 "복원" 버튼이 없습니다.** 현재 보관된 대화를 다시 활성 상태로 되돌리는 진입점이 없습니다. 나중에 그 스레드를 계속 이어가고 싶다면 새 대화를 시작해야 합니다. 보관된 대화의 내용을 다시 보려면 "보관됨" 보기를 열고 기록을 읽어 보세요.
</Callout>
## 다음
- [**오토파일럿**](/autopilots) — 에이전트가 일정에 따라 자동으로 작업을 시작하도록 하세요
- [**에이전트에게 이슈 할당하기**](/assigning-issues) — 주제를 이슈 보드로 다시 가져오세요

View File

@@ -0,0 +1,147 @@
---
title: CLI 명령어 레퍼런스
description: "모든 최상위 Multica CLI 명령어를 한 페이지로 정리한 개요입니다. 전체 사용법은 `multica <command> --help`를 실행하세요."
---
import { Callout } from "fumadocs-ui/components/callout";
Multica CLI는 Web UI에서 할 수 있는 거의 모든 작업을 그대로 제공합니다([이슈](/issues) 생성, [에이전트](/agents) 할당, [데몬](/daemon-runtimes) 시작 등). 이 페이지는 모든 최상위 명령어를 한 줄 설명과 함께 나열합니다. 전체 플래그와 예제는 `multica <command> --help`를 실행하세요.
## 인증하기
CLI를 처음 사용할 때 이 명령을 실행해 **개인 액세스 토큰(personal access token, PAT)**을 발급받으세요:
```bash
multica login
```
브라우저가 자동으로 열립니다. 웹 앱에서 승인하면 CLI가 PAT(`mul_` 접두사)를 `~/.multica/config.json`에 저장합니다. 이후 모든 명령은 이 PAT로 인증됩니다.
<Callout type="tip">
CI나 headless 환경에서는 브라우저 플로우를 건너뛰세요. 웹 앱의 **설정 → API 토큰**에서 PAT를 만든 뒤 `multica login --token <mul_...>`로 직접 전달하면 됩니다.
</Callout>
토큰 유형 간의 차이는 [인증과 토큰](/auth-tokens)을 참고하세요.
## 인증과 설정
| 명령어 | 용도 |
|---|---|
| `multica login` | 로그인하고 PAT 저장 |
| `multica auth status` | 현재 로그인 상태, 사용자, 워크스페이스 표시 |
| `multica auth logout` | 로컬 PAT 삭제 |
| `multica setup cloud` | Multica Cloud용 원샷 설정(로그인 + 데몬 설치) |
| `multica setup self-host` | 자체 호스팅 백엔드용 원샷 설정 |
## 워크스페이스와 멤버
| 명령어 | 용도 |
|---|---|
| `multica workspace list` | 접근할 수 있는 모든 워크스페이스 나열 |
| `multica workspace get <slug>` | 워크스페이스 하나의 상세 정보 표시 |
| `multica workspace member list` | 현재 워크스페이스의 멤버 나열 |
| `multica workspace update <id> --name "..." [--description "..."] [--context "..."] [--issue-prefix "..."]` | 워크스페이스 메타데이터 업데이트(admin/owner). 긴 필드는 `--description-stdin` / `--context-stdin`을 사용할 수 있습니다. |
## 이슈와 프로젝트
<Callout type="info">
`list` 계열 명령(`multica issue list`, `autopilot list`, `project list` 등)은 기본적으로 짧고 **바로 복사해 붙여 넣을 수 있는** ID를 출력합니다. 이슈는 `MUL-123` 같은 이슈 키이고, 나머지 리소스는 짧은 UUID 접두사입니다. 아래 후속 명령의 `<id>` 인자는 짧은 ID와 전체 UUID를 모두 받으므로, 일반적인 흐름은 `multica issue list` → 키 복사 → `multica issue get MUL-123`입니다. 정식 UUID가 필요할 때는 `list` 명령에 `--full-id`를 전달하세요.
</Callout>
| 명령어 | 용도 |
|---|---|
| `multica issue list` | 이슈 나열(복사해 붙여 넣을 수 있는 이슈 키 출력) |
| `multica issue get <id>` | 단일 이슈 표시(이슈 키 또는 UUID를 받음) |
| `multica issue create --title "..."` | 새 이슈 생성 |
| `multica issue update <id> ...` | 이슈 업데이트(상태, 우선순위, 담당자 등) |
| `multica issue assign <id> --agent <slug>` | 에이전트에게 할당(즉시 작업을 트리거) |
| `multica issue status <id> --set <status>` | 상태 변경 단축 명령 |
| `multica issue search <query>` | 키워드 검색 |
| `multica issue runs <id>` | 이슈의 에이전트 실행 표시 |
| `multica issue rerun <id>` | 이슈의 현재 에이전트 담당자에게 새 작업을 다시 큐에 넣기 |
| `multica issue comment <id> ...` | 중첩: 댓글 보기 / 작성 |
| `multica issue subscriber <id> ...` | 중첩: 구독 / 구독 취소 |
| `multica project list/get/create/update/delete/status` | 프로젝트 CRUD |
## 에이전트와 스킬
| 명령어 | 용도 |
|---|---|
| `multica agent list` | 워크스페이스의 에이전트 나열 |
| `multica agent get <slug>` | 에이전트의 구성 표시 |
| `multica agent create ...` | 에이전트 생성 |
| `multica agent update <slug> ...` | 에이전트 업데이트 |
| `multica agent archive <slug>` | 보관 |
| `multica agent restore <slug>` | 보관된 에이전트 복원 |
| `multica agent tasks <slug>` | 에이전트의 작업 기록 표시 |
| `multica agent skills ...` | 중첩: 스킬 연결 / 분리 |
| `multica skill list/get/create/update/delete` | 스킬 CRUD |
| `multica skill import ...` | GitHub, ClawHub, 또는 로컬 기기에서 스킬 가져오기 |
| `multica skill files ...` | 중첩: 스킬의 파일 관리 |
## 스쿼드
| 명령어 | 용도 |
|---|---|
| `multica squad list` | 워크스페이스의 스쿼드 나열 |
| `multica squad get <id>` | 단일 스쿼드 표시 |
| `multica squad create --name "..." --leader <agent>` | 스쿼드 생성(owner / admin) |
| `multica squad update <id> ...` | 이름, 설명, 지침, 리더, 또는 아바타 업데이트 |
| `multica squad delete <id>` | 보관(소프트 삭제) — 할당된 이슈를 리더에게 이관 |
| `multica squad member list/add/remove <squad-id>` | 스쿼드 멤버 관리 |
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | 스쿼드 리더 에이전트가 매 턴마다 평가를 기록할 때 사용 |
전체 모델은 [스쿼드](/squads)를 참고하세요.
## 오토파일럿
| 명령어 | 용도 |
|---|---|
| `multica autopilot list` | 워크스페이스의 모든 오토파일럿 나열 |
| `multica autopilot get <id>` | 단일 오토파일럿 표시 |
| `multica autopilot create ...` | 오토파일럿 생성 |
| `multica autopilot update <id> ...` | 업데이트 |
| `multica autopilot delete <id>` | 삭제 |
| `multica autopilot runs <id>` | 실행 기록 표시 |
| `multica autopilot trigger <id>` | 수동으로 실행 트리거 |
## 데몬과 런타임
| 명령어 | 용도 |
|---|---|
| `multica daemon start` | 데몬 시작(기본은 백그라운드; `--foreground`를 추가하면 포그라운드 실행) |
| `multica daemon stop` | 데몬 중지 |
| `multica daemon restart` | 데몬 재시작 |
| `multica daemon status` | 데몬의 온라인 여부와 동시 실행 수 확인 |
| `multica daemon logs` | 데몬 로그 보기 |
| `multica runtime list` | 현재 워크스페이스의 런타임 나열 |
| `multica runtime usage` | 리소스 사용량 표시 |
| `multica runtime activity` | 최근 활동 로그 |
| `multica runtime update <id> ...` | 런타임의 구성 업데이트 |
## 기타
| 명령어 | 용도 |
|---|---|
| `multica repo checkout <url>` | 에이전트가 사용할 수 있도록 리포지토리를 로컬에 클론 |
| `multica config` | 로컬 CLI 구성 보기 또는 편집 |
| `multica version` | CLI 버전 출력 |
| `multica update` | CLI를 최신 릴리스로 업그레이드 |
| `multica attachment download <id>` | 이슈나 댓글에서 첨부 파일 다운로드 |
## 전체 플래그 확인하기
모든 명령은 `--help`를 지원합니다:
```bash
multica issue create --help
multica agent update --help
```
v2에서는 각 명령마다 전용 상세 레퍼런스 페이지를 제공할 예정입니다.
## 다음 단계
- [인증과 토큰](/auth-tokens) — PAT vs. JWT vs. 데몬 토큰
- [데몬과 런타임](/daemon-runtimes) — `daemon` 명령이 내부적으로 동작하는 방식
- [에이전트 생성과 구성](/agents-create) — `multica agent create`의 모든 옵션

View File

@@ -0,0 +1,119 @@
---
title: Cloud 빠른 시작
description: 가입부터 에이전트에게 첫 작업을 할당하기까지 5분 만에.
---
import { Callout } from "fumadocs-ui/components/callout";
이 페이지는 Multica Cloud를 처음부터 끝까지 안내합니다 — **가입 → [CLI](/cli) 설치 → [데몬](/daemon-runtimes) 시작 → [에이전트](/agents) 생성 → 첫 [작업](/tasks) 할당**. 약 5분이 걸립니다.
전제 조건은 하나뿐입니다: 로컬에 [AI 코딩 도구](/providers)([Antigravity](/providers#antigravity), [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi) 중 하나)를 이미 최소 하나는 설치해 두어야 합니다. 데몬은 시작할 때 이들을 자동으로 감지하며, 하나도 없으면 시작을 거부합니다.
## 1. 계정 만들기
[multica.ai](https://multica.ai)에서 가입하세요. 이메일(6자리 인증 코드) 또는 Google로 로그인할 수 있습니다.
가입 후에는 (계정 이름으로 생성된) 기본 워크스페이스에 자동으로 배치됩니다. 나중에 이름을 변경하거나 새 워크스페이스를 만들 수 있습니다.
## 2. Multica CLI 설치하기
**macOS / Linux (Homebrew 권장)**:
```bash
brew install multica-ai/tap/multica
```
**macOS / Linux (Homebrew 없이)**:
```bash
curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash
```
**Windows (PowerShell)**:
```powershell
irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex
```
설치를 확인하세요:
```bash
multica version
```
## 3. 로그인 + 데몬 시작하기
명령어 하나로 로그인과 데몬 시작을 처리합니다:
```bash
multica setup
```
`multica setup`은 다음을 수행합니다:
1. CLI가 Multica Cloud에 연결하도록 구성합니다
2. 로그인을 위해 브라우저를 엽니다(웹과 동일한 이메일 인증 코드 / Google OAuth)
3. 생성된 PAT를 `~/.multica/config.json`에 저장합니다
4. **데몬을 자동으로 시작합니다** — 3초마다 작업을 폴링하고 15초마다 하트비트를 전송하기 시작합니다
<Callout type="info">
**데스크톱 앱을 사용 중이신가요?** 데스크톱 앱은 실행 시 **데몬을 자동으로 시작합니다** — `multica setup`을 직접 실행할 필요가 없습니다. [데스크톱 앱](/desktop-app)을 참고하세요.
</Callout>
데몬이 실행 중인지 확인하세요:
```bash
multica daemon status
```
`online`은 서버에 등록되었음을 의미합니다.
## 4. 런타임이 온라인인지 확인하기
웹 UI에서 **설정 → 런타임**으로 이동하세요. 방금 시작한 데몬이 하나 이상의 활성 런타임으로 표시되어야 합니다 — 로컬에 설치된 AI 코딩 도구당 하나씩입니다.
오프라인으로 표시되더라도 당황하지 마세요 — [문제 해결 → 데몬이 서버에 연결할 수 없음](/troubleshooting#daemon-cant-connect-to-the-server)을 참고하세요.
## 5. 에이전트 생성하기
웹 UI에서 **설정 → 에이전트**로 이동하여 **새 에이전트**를 클릭하세요:
- **이름** — 보드와 댓글에서 이 에이전트에 표시되는 이름입니다. 원하는 이름을 고르세요
- **제공자** — 로컬에 설치한 AI 코딩 도구를 선택하세요(드롭다운에는 런타임에서 감지된 도구만 나열됩니다)
- **모델**(선택) — 해당 도구 내부의 모델 선택(제공자에 따라 정적 목록 또는 동적 발견)
- **지침**(선택) — 이 에이전트를 위한 시스템 프롬프트
생성되면 에이전트는 워크스페이스 멤버 목록에 나타나며, 사람 멤버처럼 작업을 할당할 수 있습니다.
## 6. 첫 작업 할당하기
웹 UI에서 이슈를 만들거나, CLI에서 만드세요:
```bash
multica issue create --title "Add an ASCII architecture diagram to the README"
```
방금 만든 에이전트에게 이슈를 할당하세요 — 웹 UI에서 아바타를 클릭하거나, CLI를 사용하세요:
```bash
multica issue assign MUL-1 --to my-agent-name
```
`--to`는 에이전트 또는 멤버의 **이름**을 받습니다. 부분 문자열 일치도 동작합니다 — 에이전트 이름이 `my-code-reviewer`라면 `reviewer`로 해석됩니다. 워크스페이스에 이름이 겹치는 경우, 대신 `--to-id <uuid>`(`--to`와 상호 배타적)를 전달하세요. UUID는 `multica agent list --output json` 또는 `multica workspace member list --output json`으로 조회하세요.
**다음으로 데몬에서 일어나는 일**:
1. 3초 이내에 작업을 가져갑니다(상태가 `queued`에서 `dispatched`로 바뀝니다)
2. 일치하는 AI 코딩 도구를 호출하여 작업을 시작합니다(상태가 `running`이 됩니다)
3. AI가 로컬에서 작업합니다 — 코드 디렉터리를 읽고, 명령을 실행하고, 파일을 편집할 수 있습니다
4. 완료되면 결과를 Multica로 다시 보고합니다(자동 재시도 동작 여부에 따라 상태가 `completed` 또는 `failed`가 됩니다)
웹 UI는 **실시간으로**(WebSocket을 통해) 업데이트됩니다 — 새로 고침이 필요 없습니다.
## 다음 단계
- [데몬과 런타임](/daemon-runtimes) — 데몬이 어떻게 동작하는지와 런타임의 의미
- [작업](/tasks) — 작업 생명주기와 재시도 규칙
- [AI 코딩 도구 비교](/providers) — 12개 도구 간의 기능 차이
- [데스크톱 앱](/desktop-app) — 데몬을 직접 실행하고 싶지 않은 경우
- [자체 호스팅 빠른 시작](/self-host-quickstart) — 자체 백엔드 실행하기

View File

@@ -0,0 +1,81 @@
---
title: 댓글과 멘션
description: 이슈 아래에서 협업하기 — 댓글, 답글, `@` 멘션, 반응, 그리고 댓글에서 에이전트를 트리거하기.
---
import { Callout } from "fumadocs-ui/components/callout";
모든 [이슈](/issues)에는 댓글 스레드가 있습니다. 댓글을 작성하고, 다른 사람에게 답글을 달고, [멤버](/members-roles)나 [에이전트](/agents)를 `@`로 멘션하고, 반응을 추가하세요 — 지금까지 써온 어떤 작업 관리 도구에서나 하던 것과 똑같은 동작입니다. 단 한 가지 다른 점은: **`@`로 에이전트를 멘션하면 에이전트가 작업을 시작하도록 트리거된다**는 것입니다.
## 댓글 작성하기
이슈 상세 페이지 하단의 입력란에 내용을 입력하고 **보내기**를 누르세요. 댓글이 스레드에 즉시 나타납니다. 댓글은 Markdown을 지원합니다 — 제목, 목록, 코드 블록, 링크를 모두 사용할 수 있습니다.
## 댓글에 답글 달기
어떤 댓글이든 오른쪽 상단의 **답글**을 클릭하면 그 아래에 중첩된 입력란이 열립니다. 작성한 답글은 해당 댓글의 하위 항목으로 표시되어 대화 스레드를 이룹니다. 답글에도 다시 답글을 달 수 있으며, 필요한 만큼 깊이 중첩됩니다.
이슈 목록에는 최상위 댓글 수만 표시되며, 이슈를 열어야 전체 대화 트리를 볼 수 있습니다.
## 반응
각 댓글의 오른쪽 상단에는 빠른 신호를 보내기 위한 반응 버튼이 있습니다(👍, 👀, 🎉) — 동의를 표시하려고 "+1" 댓글을 따로 달 필요가 없습니다.
## `@` 멘션
댓글에 `@`를 입력하면 선택 창이 열립니다. 멤버나 에이전트를 고르면 `@`와 대상의 slug가 함께 삽입됩니다(`@alice` 또는 `@reviewer-bot`). 멘션된 대상은 자신의 [인박스](/inbox)에서 알림을 받습니다.
**에이전트를 멘션하면 자동으로 트리거됩니다** — [댓글에서 에이전트 멘션하기](/mentioning-agents)를 참고하세요.
하나의 댓글에서 같은 사람을 여러 번 멘션해도 알림은 **하나만** 발생합니다.
### `@all`은 워크스페이스 전체에 알립니다
`@all`은 특별한 대상입니다. 워크스페이스의 모든 멤버에게 알림을 보냅니다. 사람과 에이전트 모두 `@all`을 사용할 수 있습니다 — 즉 진행 상황을 보고하는 에이전트도 `@all`을 할 수 있으므로, 에이전트의 지침에 이를 아껴서 사용하라고 일러두세요.
<Callout type="warning">
**`@all`은 신중하게 사용하세요.** 규모가 큰 워크스페이스에서는 단 한 번의 `@all`이 그만큼의 인박스 알림을 순식간에 생성합니다. 모두가 정말로 알아야 하는 일에만 사용하고 — 일상적인 업데이트에는 쓰지 마세요.
</Callout>
## 이슈 참조하기
다른 이슈를 링크하려면 `MUL-123`처럼 이슈 키를 입력하세요. Multica는 댓글에서 실제 존재하는 이슈 키를 해석하여 내부적으로 `mention://issue/<uuid>` 링크로 저장합니다. 이슈 링크는 단순한 상호 참조일 뿐입니다. 사람에게 알림을 보내지 않으며 에이전트를 트리거하지도 않습니다.
보통은 `[MUL-123](mention://issue/<uuid>)`을 직접 손으로 작성할 필요가 없습니다. 그 형식은 Multica가 키를 해석한 뒤에 사용하는 표준 내부 표현입니다.
<Callout type="info">
Markdown 강조는 CommonMark 규칙을 따릅니다. 굵은 텍스트가 문장 부호나 닫는 따옴표로 끝나고 그 뒤에 한국어 조사가 바로 이어지면, 닫는 `**`가 인식되지 않을 수 있습니다.
따옴표를 굵게 표시 범위 밖으로 옮기는 것을 권장합니다:
```markdown
"**무엇을 먼저 정해두고 시작할지**"가
```
다음 대신:
```markdown
**"무엇을 먼저 정해두고 시작할지"**가
```
</Callout>
## 댓글 편집과 삭제
댓글은 작성자만 편집하거나 삭제할 수 있습니다.
댓글을 삭제하면 그 아래의 **모든 답글도 함께 삭제됩니다**(답글의 답글 포함). 내용만 바꾸려면 삭제 대신 편집을 사용하세요.
<Callout type="warning">
**댓글을 편집하면서 `@`를 추가해도 에이전트는 트리거되지 않습니다.** 트리거는 댓글이 **생성되는** 그 순간에 발생합니다 — 나중에 편집해서 새로운 `@`를 추가하거나 대상을 바꿔도 새 알림이 발송되지 않고 에이전트가 깨어나지도 않습니다. 놓친 에이전트를 불러오려면 그 에이전트를 `@`하는 **새 댓글을 작성**하세요.
</Callout>
---
여기까지 다룬 내용은 모두 "사람의 세계"입니다 — 워크스페이스, 멤버, 이슈, 프로젝트, 댓글. Linear나 Jira를 써본 적이 있다면 지금까지의 내용은 전혀 낯설지 않을 것입니다.
하지만 Multica의 결정적인 특징은 아직 등장하지 않았습니다: **에이전트를 워크스페이스의 일급 멤버로 대우하는 것**입니다. 다음 장에서 바로 이 이야기를 다룹니다.
## 다음
- [에이전트](/agents) — 무엇이며, 사람과 어떻게 다른지
- [댓글에서 에이전트 멘션하기](/mentioning-agents) — 댓글에서 `@`로 에이전트를 시작시키기

View File

@@ -0,0 +1,111 @@
---
title: 데몬과 런타임
description: 에이전트는 Multica 서버에서 실행되지 않습니다. 여러분의 기기에서 실행됩니다.
---
import { Callout } from "fumadocs-ui/components/callout";
import { Mermaid } from "@/components/mermaid";
Multica에서 [에이전트](/agents)는 우리 서버에서 실행되지 **않습니다**. 로컬에 설치된 [AI 코딩 도구](/providers)를 호출하는 **데몬**이라는 작은 프로그램이 구동하여, 여러분 자신의 기기에서 실행됩니다. Multica 서버는 조율만 담당합니다. [이슈](/issues)를 저장하고, [작업](/tasks)을 대기열에 넣고, 알맞은 **런타임**으로 분배합니다(런타임 = 데몬 × AI 코딩 도구 하나).
이 구조가 Multica와 Linear / Jira의 가장 큰 차이점입니다. **여러분의 API 키, 툴체인, 코드 디렉터리는 모두 여러분의 기기에 남아 있으며**, Multica 서버는 그중 어느 것도 보지 못합니다. 따라서 "내 에이전트가 동작하지 않는다"는 거의 항상 로컬 문제입니다. 데몬이 실행 중이 아니거나, AI 도구가 설치되어 있지 않거나, 키가 만료되었을 수 있습니다. 먼저 로컬을 확인하세요. 안내는 [문제 해결](/troubleshooting)을 참고하세요.
## 데몬 시작하기
데몬은 Multica CLI의 일부입니다. [Multica CLI](/cli)를 설치한 뒤, 여러분의 기기에서 실행하세요.
```bash
multica daemon start
```
시작 시 데몬은 네 가지 일을 합니다.
1. 로그인할 때 저장된 인증 정보를 읽습니다
2. `PATH`에 설치된 AI 코딩 도구를 감지합니다(내장 12종: [Antigravity](/providers#antigravity), [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi))
3. 감지된 각 도구에 대한 런타임과 함께 자신을 서버에 등록합니다
4. **3초마다** 가져올 작업이 있는지 폴링하고, **15초마다 하트비트를 전송**합니다
자주 쓰는 명령:
| 명령 | 용도 |
|---|---|
| `multica daemon start` | 시작(기본은 백그라운드. 포그라운드로 실행하려면 `--foreground` 추가) |
| `multica daemon stop` | 중지 |
| `multica daemon restart` | 재시작 |
| `multica daemon status` | 상태 표시 |
| `multica daemon logs` | 로그 표시(따라가려면 `-f` 추가) |
전체 CLI 참고는 [CLI 명령](/cli)을 확인하세요.
**데스크톱 앱에는 데몬이 포함되어 있습니다.** [데스크톱 앱](/desktop-app)을 사용한다면 `multica daemon start`를 수동으로 실행할 필요가 없습니다. 시작할 때 데몬을 자동으로 띄웁니다. 여러분의 워크플로에 어떤 방식이 맞는지는 [데스크톱 앱](/desktop-app) 페이지를 참고하세요.
## 한 기기에 여러 런타임이 생기는 이유
런타임은 서버도 아니고 컨테이너도 아닙니다. "**데몬 × AI 코딩 도구 하나**"의 조합입니다. 예를 들어, Claude Code와 Codex가 모두 설치된 MacBook에서 데몬을 시작하고, 여러분이 두 개의 워크스페이스 멤버라고 합시다. 그러면 Multica는 4개의 런타임을 등록합니다.
<Mermaid chart={`
graph TD
D["여러분의 데몬<br/>MacBook"]
D --> R1["런타임<br/>워크스페이스 A × Claude Code"]
D --> R2["런타임<br/>워크스페이스 A × Codex"]
D --> R3["런타임<br/>워크스페이스 B × Claude Code"]
D --> R4["런타임<br/>워크스페이스 B × Codex"]
`} />
핵심 포인트:
- **하나의 데몬은 여러 런타임에 매핑될 수 있습니다.** 설치된 도구와 가입한 워크스페이스의 조합마다 하나씩 생깁니다
- **같은 데몬, 워크스페이스, 도구는 정확히 하나의 런타임을 만듭니다.** 데몬을 재시작해도 중복 레코드가 생기지 않습니다
- Multica UI의 **런타임** 페이지가 이 행들을 나열합니다
<Callout type="info">
**클라우드 런타임이 곧 제공됩니다.** 현재는 대기자 명단 단계입니다. 제공이 시작되면 로컬 데몬을 실행하지 않고도 Multica Cloud에서 직접 에이전트 작업을 실행할 수 있습니다. [다운로드 페이지](https://multica.ai/download)에서 이메일로 등록하면 알림을 받을 수 있습니다.
</Callout>
## 런타임이 오프라인으로 표시되는 시점
Multica는 하트비트로 런타임이 온라인인지 판단합니다. 세 가지 핵심 수치:
| 이벤트 | 임곗값 |
|---|---|
| 데몬 하트비트 빈도 | **15초**마다 |
| 누락으로 표시 | **45초** 동안 하트비트 없음(3회 누락) |
| 자동 삭제 | 연결된 에이전트 없이 **7일** 넘게 누락 상태 |
누락은 영구적이지 않습니다. 데몬이 다시 하트비트를 보내는 즉시 온라인으로 돌아오며, 런타임 레코드도 보존됩니다. 데몬을 재시작해도 런타임은 사라지지 않습니다.
<Callout type="warning">
**누락된 런타임에서 실행 중이던 작업은 실패로 표시됩니다**(실패 사유 `runtime_offline`). 재시도 가능한 출처(이슈, 채팅)에 대해서는 Multica가 자동으로 다시 대기열에 넣습니다. 오토파일럿이 트리거한 작업은 자동으로 재시도되지 않습니다. [작업 → 어떤 실패가 자동 재시도되는지](/tasks#which-failures-retry-automatically-which-dont)를 참고하세요.
</Callout>
## 동시에 실행할 수 있는 작업 수
Multica는 두 계층에서 동시성 제한을 적용합니다.
- **데몬 계층**: 기본 **동시 작업 20개**(환경 변수 `MULTICA_DAEMON_MAX_CONCURRENT_TASKS`로 조정 가능)
- **에이전트 계층**: 기본 **에이전트당 동시 작업 6개**(에이전트별로 설정)
둘 중 더 엄격한 쪽이 적용됩니다. 데몬이 이미 작업 20개를 실행 중이라면, 어떤 에이전트에 여유가 남아 있어도 새 작업은 대기합니다.
작업이 `dispatched`로 넘어가지 못하고 `queued`에 멈춰 있다면, 보통 이 두 제한 중 하나가 포화 상태인 것입니다.
## 데몬 충돌 후 진행 중이던 작업은 어떻게 되나
데몬이 충돌하거나 강제 종료되면, 데몬이 가져갔던 작업은 `dispatched` 또는 `running` 상태에 남습니다. 다음 시작 시 데몬은 서버에 "이 작업들은 더 이상 제 것이 아니니, 실패로 표시해 주세요"라고 알립니다. 서버는 이를 사유 `runtime_recovery`와 함께 `failed`로 전환합니다. 재시도 가능한 출처에 대해서는 작업이 자동으로 다시 대기열에 들어갑니다.
이 단계가 네트워크 문제로 실패하더라도, 백업으로 **30초마다** 서버 측 스캔이 돕니다. 45초 넘게 하트비트가 없는 런타임은 누락으로 표시되며, 그 위의 작업도 함께 회수됩니다.
## 동작하지 않는 에이전트 문제 해결
"내 에이전트가 동작하지 않는다"는 문제를 만나면, 먼저 이 세 단계 체크리스트를 진행하세요.
1. `multica daemon status`를 실행해 데몬이 실행 중이고 온라인인지 확인하세요
2. `multica daemon logs -f`를 실행해 오류가 있는지 확인하세요
3. Multica UI의 **런타임** 페이지를 열어 런타임이 "온라인"으로 표시되는지 확인하세요
더 많은 시나리오는 [문제 해결](/troubleshooting)을 참고하세요.
## 다음
- [작업](/tasks) — 데몬이 작업을 가져온 뒤의 전체 생애 주기
- [제공자 대조표](/providers) — 12종 AI 코딩 도구의 기능 차이

View File

@@ -0,0 +1,99 @@
---
title: 데스크톱 앱
description: Multica Desktop이 무엇인지, 웹 앱과 어떻게 다른지, 언제 쓸 만한지 알아봅니다.
---
import { Callout } from "fumadocs-ui/components/callout";
Multica Desktop은 macOS, Windows, Linux용 네이티브 데스크톱 앱입니다. 구성된 환경에 대해 웹 앱과 동일한 백엔드에 연결되며 동일한 데이터를 보여줍니다. 기본적으로 Desktop은 Multica Cloud를 사용하며, 자체 호스팅 인스턴스는 로컬 런타임 설정 파일로 구성할 수 있습니다. Desktop은 브라우저가 할 수 없는 몇 가지 기능도 추가로 제공합니다: **[워크스페이스](/workspaces)별 독립적인 탭 그룹**, **[데몬](/daemon-runtimes) 자동 시작**, **원클릭 업그레이드**입니다.
## Desktop 또는 웹 — 무엇을 선택할까
| | 웹 | Desktop |
|---|---|---|
| 접근 방식 | 브라우저에서 URL 열기 | 네이티브 앱 설치 |
| 다중 탭 | 브라우저 자체 탭 (워크스페이스 구분 없음) | **워크스페이스별 독립 탭 그룹 한 개** |
| 데몬 | `multica daemon start`를 직접 실행 | 실행 시 **자동으로 시작** |
| 업그레이드 | 새로고침하면 최신 버전 | 앱이 백그라운드에서 확인하고 다음 실행 시 설치 |
| 로그인 후 데이터 | 동일 | 동일 |
**웹을 선택**: 일회성으로 사용하거나, 다른 사람의 기기에서 작업하거나, 아무것도 설치하고 싶지 않을 때.
**Desktop을 선택**: 매일 사용하거나, 여러 워크스페이스를 동시에 다루거나, 데몬을 수동으로 관리하고 싶지 않을 때.
## 다중 탭: 워크스페이스를 전환하면 어떻게 되나
Desktop은 **참여한 모든 워크스페이스**마다 독립적인 탭 그룹을 유지합니다. 워크스페이스를 전환하면 현재 워크스페이스의 탭이 하나의 단위로 숨겨지고, 이전 워크스페이스의 탭은 떠났던 그대로 복원됩니다 — VSCode의 멀티 워크스페이스 동작이나 Slack에서 워크스페이스를 전환하는 것과 비슷합니다.
예시: 워크스페이스 A에서 이슈 탭 3개를 열어 둔 상태로 워크스페이스 B로 전환합니다. A의 탭 3개는 사라지고, B에는 B에서 마지막으로 열어 두었던 것이 표시됩니다. 다시 A로 전환하면 그 3개의 탭이 정확히 이전 상태 그대로 돌아옵니다. **탭은 워크스페이스 간에 절대 새어 나가지 않습니다.**
로그아웃하면 **모든 워크스페이스의 탭 상태가 지워지므로**, 여러 사용자가 기기를 공유하더라도 데이터가 새어 나가지 않습니다.
## Desktop의 자동 업데이트 방식
실행 시 Desktop은 GitHub Releases에서 더 최신 버전이 있는지 확인합니다. 새 버전이 발견되면:
1. 백그라운드에서 새 버전을 조용히 다운로드합니다.
2. "준비 완료 — 다음 실행 시 설치됩니다"라고 알려 줍니다.
3. 종료(또는 다음 재시작) 시, 앱이 닫히기 전에 업데이트를 설치합니다.
4. 다음 실행 시 새 버전이 실행됩니다.
이 전체 과정은 **작업 중인 내용을 방해하지 않습니다**.
<Callout type="warning">
**Windows에서는 ARM64와 x64가 별도의 업데이트 채널입니다** — 잘못된 아키텍처를 설치하면 업데이트가 감지되지 않습니다. 다운로드할 때 기기에 맞는 `.exe`를 선택하세요 (ARM 빌드에는 `arm64` 접미사가 붙어 있습니다).
</Callout>
macOS 빌드는 서명 및 공증되어 있으므로, 첫 실행 시 "확인되지 않은 개발자" 경고가 표시되지 않습니다. Linux 빌드는 `.AppImage`입니다 — 자동 업데이트는 electron-updater에 의존하는데, 일부 배포판에서는 불안정할 수 있습니다. **자동 업데이트가 작동하지 않으면, 새 버전을 수동으로 다운로드하여 기존 파일을 교체하세요.**
## 별도의 CLI와 데몬이 여전히 필요한가요?
**아닙니다.** Desktop에는 동일한 `multica` CLI 바이너리가 내장되어 있으며, 시작 시 자체 데몬 프로필을 실행합니다 (터미널에서 수동으로 실행 중인 데몬과는 격리됩니다).
이미 CLI를 설치하고 `multica daemon start`를 직접 실행했더라도, Desktop은 그 데몬을 가로채지 않습니다 — 별도의 프로필로 자체 데몬을 시작합니다. 둘은 **서로 다른 런타임**으로 등록되며, UI에서 두 개의 독립된 런타임을 볼 수 있습니다.
터미널에서 CLI 명령을 실행하고 싶다면, Desktop이 특별한 경로를 제공하지는 않습니다 — 별도로 설치한 CLI를 사용하거나, 앱의 리소스 디렉터리 안 `resources/bin/multica`에 있는 번들 사본을 실행하세요.
## 다운로드 및 설치
[Multica 다운로드 페이지](https://multica.ai/download)에서 사용하는 플랫폼의 설치 프로그램을 받으세요:
| 플랫폼 | 파일 |
|---|---|
| macOS (Intel 또는 Apple Silicon) | `.dmg` |
| Windows x64 | `.exe` (표준) |
| Windows ARM64 | `.exe` (`arm64` 접미사 포함) |
| Linux | `.AppImage` |
첫 실행 시 로그인이 필요합니다 — 웹 앱과 동일한 이메일 + 인증 코드 흐름입니다. 로그인하면 Desktop이 워크스페이스 목록을 자동으로 동기화합니다.
<Callout type="info">
**Desktop은 기본적으로 Multica Cloud를 사용하지만, 로컬 설정 파일로 자체 호스팅 인스턴스를 가리키도록 할 수 있습니다.** 앱 안에는 여전히 "자체 호스팅에 연결" 선택기가 없습니다. Desktop은 렌더러가 시작되기 전에 `~/.multica/desktop.json`을 읽습니다. 파일이 없으면 Cloud 기본값을 사용합니다.
최소 자체 호스팅 설정:
```json
{
"schemaVersion": 1,
"apiUrl": "https://api.your-domain"
}
```
`apiUrl`은 필수이며 `http` 또는 `https`를 사용해야 합니다. Desktop은 동일 오리진에서 `wsUrl`을 `/ws`로 유도하고(`https`이면 `wss`, `http`이면 `ws`), API 오리진에서 `appUrl`을 유도합니다. 배포 환경이 서로 다른 오리진을 사용한다면 명시적으로 설정하세요:
```json
{
"schemaVersion": 1,
"apiUrl": "https://api.your-domain",
"wsUrl": "wss://api.your-domain/ws",
"appUrl": "https://your-domain"
}
```
`desktop.json`이 존재하지만 유효하지 않으면, Desktop은 안전하게 동작을 차단하여 Cloud로 조용히 되돌아가는 대신 차단형 설정 오류를 표시합니다. 개발 빌드의 경우, `electron-vite dev` 중에는 여전히 `VITE_API_URL` / `VITE_WS_URL` / `VITE_APP_URL`이 우선합니다. 런타임 Desktop 자체 호스팅 구성은 [issue #1371](https://github.com/multica-ai/multica/issues/1371)에서 구현되었습니다.
</Callout>
## 다음 단계
- [Cloud Quickstart](/cloud-quickstart) — Desktop을 위한 Cloud 온보딩 흐름
- [Self-Host Quickstart](/self-host-quickstart) — 자체 백엔드를 실행하고 CLI 또는 Desktop 런타임 설정으로 연결하기
- [데몬 및 런타임](/daemon-runtimes) — 데몬의 작동 방식 (Desktop이 대신 시작해 주지만 동작은 동일합니다)

View File

@@ -0,0 +1,302 @@
---
title: 규약
description: 코드 네이밍, i18n 번역 용어집, 중국어 보이스 가이드의 단일 진실 공급원.
---
이 페이지는 코드 네이밍, i18n 번역 용어집, 중국어 보이스 가이드의 단일 진실 공급원입니다. 예전에 `packages/views/locales/glossary.md`나 여기저기 흩어진 주석에 있던 내용은 이제 모두 이곳에 모여 있습니다.
Multica 코드를 작성하거나, 번역을 변경하거나, 중국어 제품 카피를 작성한다면 이 페이지를 참고하세요.
---
## 1. 코드 네이밍
### 라우트
워크스페이스 진입 전 라우트(사용자가 워크스페이스에 들어가기 전에 존재하는 라우트)는 반드시 단일 단어 또는 `/{noun}/{verb}` 패턴을 사용해야 합니다.
- ✅ `/login`, `/inbox`, `/workspaces/new`
- ❌ `/new-workspace`, `/create-team`, `/accept-invite`
루트에 하이픈으로 연결된 단어 묶음은 사용자가 직접 고른 워크스페이스 slug와 충돌하며, 끝없는 예약어 slug 점검을 강요합니다. 명사(`workspaces`)를 예약해 두면 `/workspaces/*` 하위 트리 전체가 자동으로 보호됩니다.
### 워크스페이스 범위 라우트
항상 `/{slug}/{section}` 아래에 둡니다 — `/{slug}/issues`, `/{slug}/agents`, `/{slug}/settings`. 워크스페이스 라우팅 로직을 절대 중복하지 말고, 공유 코드에서는 프레임워크별 link API 대신 `useNavigation().push()`를 사용하세요.
### 패키지와 모듈
모노레포는 엄격한 패키지 경계를 강제합니다:
| 패키지 | 의존 가능 | 의존 금지 |
| --- | --- | --- |
| `packages/core` | 앱 종속적이지 않은 것만 | `react-dom`, `localStorage`, `process.env`, `next/*`, UI 라이브러리 |
| `packages/ui` | 없음 | `@multica/core`, 비즈니스 로직 |
| `packages/views` | `core/`, `ui/` | `next/*`, `react-router-dom`, stores |
| `apps/web/platform/` | `next/*` | 다른 앱 |
| `apps/desktop/.../platform/` | `react-router-dom`, electron | 다른 앱 |
두 앱 모두에 동일한 로직이 나타난다면 반드시 공유 패키지로 추출해야 합니다. "사소한" 중복이라는 예외는 없습니다.
### 파일과 컴포넌트
- 파일: `kebab-case.tsx` / `kebab-case.ts` (예: `agent-row-actions.tsx`)
- 컴포넌트: `PascalCase` (예: `AgentRowActions`)
- Hook: `useCamelCase` (예: `useWorkspaceId`)
- 테스트: `<file>.test.ts(x)`로 같은 위치에 배치
- Store (Zustand): `<feature>-store.ts`, `use<Feature>Store`로 export
### 데이터베이스 (Go + sqlc)
- 테이블: `snake_case` 단수 (`user`, `workspace`, `agent_runtime`)
- 컬럼: `snake_case` (`workspace_id`, `created_at`, `last_seen_at`)
- 외래 키: `<table>_id`
- 불리언: `is_<state>` 또는 `<state>_at` (상태 변경에는 타임스탬프 형태 선호)
- 마이그레이션 파일: `NNN_descriptive_name.up.sql` + `.down.sql` — 항상 양방향을 모두 제공
### Go
- 표준 `gofmt` + `go vet`. 예외 없음.
- Handler 파일은 도메인을 반영: `agent.go`, `auth.go`, `runtime.go`
- 테스트: `<file>_test.go`로 같은 위치에 배치
- handler에서의 UUID 파싱은 루트 `CLAUDE.md`의 규칙을 따릅니다 — 경계 입력에는 `parseUUIDOrBadRequest`, 신뢰할 수 있는 왕복에는 `parseUUID`(panic 버전), 절대 error를 확인하지 않고 `util.ParseUUID`를 직접 사용하지 마세요.
### TypeScript
- 네트워크상의 API 응답은 `snake_case`이며, api client가 경계에서 `camelCase`로 변환합니다. TS 코드 내부에서는 **항상 camelCase**.
- 타입: `PascalCase` (`Issue`, `AgentRuntime`); `IPrefix` 금지, `_t` 접미사 금지.
- 열거형: string literal union을 선호하고, 런타임 순회가 필요한 경우에만 `enum`을 사용.
- TanStack Query 키: `<feature>/queries.ts` 안의 팩토리 함수, 예: `issueKeys.detail(id)`.
### 이슈 키
모든 이슈에는 `MUL-123` 같은 사람이 읽을 수 있는 키가 있습니다: 워크스페이스 `issue_prefix`(대문자와 숫자, 보통 3자, 최대 10자) + 일련번호. 워크스페이스 admin은 Settings → General에서 접두사를 변경할 수 있는데, 변경하면 기존의 모든 이슈가 다시 번호 매겨지므로 옛 접두사가 박혀 있는 외부 참조(PR 제목, 브랜치 이름, 문서와 채팅 속 링크)는 더 이상 연결되지 않습니다.
### 코드 내 주석
영어만 사용합니다. 레포는 Go와 TypeScript 모두에 이를 강제합니다. 코드에서 중국어 주석을 발견하면 그것은 버그이니 교체하세요.
### 커밋 메시지
Conventional 형식: `feat(scope)`, `fix(scope)`, `refactor(scope)`, `docs`, `test(scope)`, `chore(scope)`. 의도별로 묶인 원자적 커밋.
---
## 2. i18n 번역 용어집
이것은 모든 번역 PR이 **반드시** 지켜야 하는 용어집입니다. 예전에는 `packages/views/locales/glossary.md`에 있었는데, 그 파일은 이제 이곳을 가리키는 stub입니다.
### 핵심 구분: 엔티티 vs 개념
Multica의 제품 명사는 두 가지 범주로 나뉩니다:
- **엔티티(Entity)** — URL, 데이터베이스 row, API 타입을 가집니다. 중국어 텍스트에서는 **소문자 영문**으로 표기하여 시각적으로 타입 이름처럼 읽히게 하고 "이것은 Multica 시스템 엔티티다"라는 신호를 줍니다.
- **개념(Concept)** — 일반 명사이며, 데이터베이스 엔티티가 아닙니다. **완전히 번역**하여 중국어 사용자가 흐르는 텍스트 속에 들쭉날쭉한 영문을 보지 않도록 합니다.
이 규칙은 `apps/docs/content/docs/*.zh.mdx`와 정렬되어 있습니다 — 이 문서들은 사실상의 중국어 보이스 표준이며 20개 이상의 페이지에서 실전 검증되었습니다.
### 엔티티 — 혼합 규칙 (`issue` / `skill` / `task`)
`issue` / `skill` / `task`는 Multica의 핵심 엔티티입니다. 스키마 컬럼, API 필드, 제품 UI 레이블이 모두 영어입니다. 중국어 텍스트에서는 **혼합 규칙**을 따르며 — 무엇을 사용할지는 단어가 어디에 나타나는지에 따라 달라집니다:
| 맥락 | 표기 | 예시 |
| --- | --- | --- |
| **UI 문자열, 상태 이름, 코드 참조** | 소문자 영문 | "排队中的 task"、"创建子 issue"、"为智能体注入 skill" |
| **문서 제목 / 섹션 헤딩** | Title-case 영문 **또는** 중국어 용어 | "Issue 与 project"、"Skills"、"执行任务" |
| **장문 문서 산문에서 엔티티가 진행 중인 주어일 때** | 중국어 용어, 첫 언급 시 괄호 안에 영문 | "**执行任务**task是智能体每一次工作的单位" |
| **API / DB 필드** | 항상 `task` / `issue` / `skill` | `task_id`, `issue_status`, `skill_uuid` |
중국어 용어 참고:
- `task` ↔ `执行任务` (맥락이 분명해지면 `任务`로 줄여서 사용)
- `issue`에는 정착된 중국어 번역이 없습니다 — 영문 유지; 제목에서는 `Issue`처럼 대문자로 표기 가능
- `skill`에는 정착된 중국어 번역이 없습니다 — 영문 유지; 제목에서는 `Skills`처럼 대문자로 표기 가능
**`issue` / `skill` / `task`가 `project` / `autopilot`처럼 중국어로 강제 번역되지 않는 이유**:
- **`issue` / `task`**: 개발 팀은 영어로 대화합니다. 중국어 후보들("任务" — 너무 모호하고 "工作"과 거의 동의어; "工单" — IT 티켓 어감; "议题" — GitHub 스타일이지만 제품 느낌과 맞지 않음)은 모두 `issue`보다 못하게 읽힙니다. **하지만** 장문 문서 산문에서 소문자 `task`를 50번 반복하면 리듬이 깨지므로 — 산문에서는 `执行任务`를 허용하되, UI 문자열과 상태 이름은 소문자 영문으로 유지합니다.
- **`skill`**: 정착된 중국어 용어가 없는 Multica 고유 개념입니다.
- **`project` → "项目"**: 정착된 주류 중국어 단어. Feishu / Tower / Teambition / PingCode / GitHub Projects — 모든 중국어 제품이 이를 번역합니다. 중국어 맥락에서 `project`를 그대로 두는 제품은 없습니다.
- **`autopilot` → "自动化"**: 중국어에서 "autopilot"은 Tesla의 "自动驾驶"를 연상시키며, 이 기능이 하는 일(일정에 따라 task 실행)과 맞지 않습니다. Notion과 Feishu 모두 "自动化"를 사용하며, 그것이 업계 합의입니다.
### 번역하지 않음 — 브랜드와 약어
| 범주 | 용어 |
| --- | --- |
| 브랜드 | **Multica**, GitHub, Slack, Google, Anthropic, OpenAI, Claude, Codex, Cursor, Linear, Jira |
| 약어 | API, CLI, URL, SDK, OAuth, JWT, SSO, WebSocket, HTTP, JSON, YAML, SQL |
### 완전히 번역 — 개념
| English | Chinese |
| --- | --- |
| Workspace | **工作区** |
| Agent | **智能体** |
| Project | **项目** |
| Autopilot | **自动化** |
| Daemon | **守护进程** |
| Runtime | **运行时** |
| Inbox | **收件箱** |
| Comment | **评论** |
| Reply | **回复** |
| Notifications | **通知** |
| Member | **成员** |
| Label | **标签** |
| Settings | **设置** |
| Onboarding | **上手引导** |
### 완전히 번역 — 일반 UI 단어
| English | Chinese |
| --- | --- |
| Invite / Invitation | 邀请 |
| Search | 搜索 |
| Email | 邮箱 (label) / 邮件 (action) |
| Password | 密码 |
| Sign in / Log in | 登录 |
| Sign up | 注册 |
| Sign out / Log out | 退出登录 |
| Save / Cancel / Delete | 保存 / 取消 / 删除 |
| Confirm / Continue / Back | 确认 / 继续 / 返回 |
| Edit / New / Create / Add | 编辑 / 新建 / 创建 / 添加 |
| Remove / Send / Open / Close | 移除 / 发送 / 打开 / 关闭 |
| Preview / Download / Upload | 预览 / 下载 / 上传 |
| Done / Loading... | 完成 / 加载中... |
| Profile / Account / Appearance | 个人资料 / 账号 / 外观 |
| Theme / Language | 主题 / 语言 |
| Light / Dark / System | 浅色 / 深色 / 跟随系统 |
| Active / Archived | 活跃 (or 启用) / 已归档 |
| Status / Priority | 状态 / 优先级 |
| Assignee / Reporter | 负责人 / 报告人 |
| Description / Title | 描述 / 标题 |
| Date / Time | 日期 / 时间 |
| Today / Yesterday / Tomorrow | 今天 / 昨天 / 明天 |
| Empty / Failed / Success | 空 / 失败 / 成功 |
| Error / Warning | 错误 / 警告 |
### 역할과 상태 열거형 (소문자 영문, 번역하지 않음)
이것들은 스키마 수준의 식별자입니다; 중국어 맥락에서도 소문자 영문으로 표기합니다.
- 역할: `owner` / `admin` / `member`
- 이슈 상태: `backlog` / `todo` / `in_progress` / `in_review` / `done` / `blocked` / `cancelled`
UI에서는 이 값들을 영어로 표시합니다(필요 시 `code-style`로 감쌈):
- "你需要 owner 权限"
- "已切换到 in_progress"
### 단어 조합 규칙
영문 단어(엔티티 / 브랜드 / 약어)와 주변 중국어 사이에는 항상 **단일 공백**을 둡니다:
- "Create new issue" → "新建 issue"
- "Assign to agent" → "分配给智能体"
- "Configure runtime" → "配置运行时"
- "Stop daemon" → "停止守护进程"
### 복수형과 개수
i18next는 `_one` / `_other`를 사용합니다; 중국어에는 문법적 수가 없으므로 `_other`만 채웁니다.
```json
// en/issues.json
{
"issue_count_one": "{{count}} issue",
"issue_count_other": "{{count}} issues"
}
// zh-Hans/issues.json
{
"issue_count_other": "{{count}} 个 issue"
}
```
일반적인 개수 형식:
- `{{count}} issues` → `{{count}} 个 issue`
- `{{count}} agents` → `{{count}} 个智能体`
- `{{count}} workspaces` → `{{count}} 个工作区`
- `{{count}} comments` → `{{count}} 条评论`
- `{{count}} members` → `{{count}} 位成员`
- `{{count}} skills` → `{{count}} 个 skill`
### 보간
`{{var}}`를 사용합니다. 중국어 번역은 자연스러운 문장 흐름을 위해 순서를 재배치할 수 있습니다.
```json
// en
{ "welcome_message": "Welcome back, {{name}}!" }
// zh-Hans
{ "welcome_message": "欢迎回来,{{name}}" }
```
### 번역 키 네이밍
3단계 중첩: `feature.component.action`.
```json
{
"feature_or_component": {
"subcomponent_or_section": {
"action_or_label": "..."
}
}
}
```
예시:
- `issues.toolbar.batch_update_success`
- `issues.detail.comment_form.placeholder`
- `inbox.empty.title`
- `settings.preferences.language.title`
### Web 전용 / Desktop 전용 카피
- 공유 카피: namespace JSON의 최상위
- Web 전용: `web` 섹션
- Desktop 전용: `desktop` 섹션
정식 예시는 `auth.json`을 참고하세요(`web` 섹션에 `prefer_desktop` / `desktop_handoff.*`가 포함됨).
---
## 3. 중국어 보이스와 스타일
### 구두점
- 중국어에서는 전각 구두점 사용: `,。:;!?`
- 따옴표: 영어 원문과 맞추기 위해 곧은 큰따옴표 `"..."`를 사용. `「」`나 둥근 따옴표는 사용하지 마세요.
- 줄임표: 단일 문자 `…`가 아닌 세 개의 점 `...`. 영어 원문과 일치시키세요.
- 중국어-영어 혼용: 영문 단어 양옆에 각각 단일 공백(단어 조합 규칙 참고).
### 스타일 원칙
- **간결하고 직접적으로.** 번역투 회피: "对于 X 来说"、"作为 X"、"我们的".
- **오류 메시지**: 부드럽지만 명확하게. "无法保存修改"가 "保存修改失败了!"보다 낫습니다.
- **버튼**: 동사를 먼저, 2~4자. "取消"、"保存修改"、"立即同步".
- **툴팁**: 완결된 짧은 문장. "复制链接到剪贴板".
- **플레이스홀더**: 예시 형태. "输入 issue 标题...".
### 막힐 때 참고할 곳
용어집이 특정 용어를 다루지 않을 때는 다음을 참고하세요:
1. `apps/docs/content/docs/*.zh.mdx` — 사실상의 중국어 보이스 표준, 일관된 번역 20개 이상 페이지
2. `packages/views/locales/zh-Hans/auth.json`과 `editor.json` — JSON 구조 + selector API 패턴
3. `packages/views/auth/login-page.tsx` — 컴포넌트 수준 selector API 호출 지점
4. `packages/views/settings/components/preferences-tab.tsx` — 언어 전환기 참고
---
## 이 페이지를 업데이트할 때
이곳의 규칙을 변경하면 다음도 함께 수행하세요:
1. 관련 locale JSON / CLAUDE.md / 문서 페이지에 적용
2. PR 설명에 변경 사항을 기록하여 리뷰어가 다운스트림 정리를 살펴보도록 알리기
이 페이지가 계약입니다; 다른 어떤 것도 이를 무시할 수 없습니다.

View File

@@ -0,0 +1,4 @@
{
"title": "Developers",
"pages": ["conventions"]
}

View File

@@ -0,0 +1,224 @@
---
title: 환경 변수
description: 자체 호스팅 Multica 서버를 실행하기 위한 환경 변수 전체 목록입니다.
---
import { Callout } from "fumadocs-ui/components/callout";
자체 호스팅 Multica [서버](/self-host-quickstart)는 시작 시 환경 변수에서 설정을 읽습니다 — 데이터베이스, 로그인, 이메일, 스토리지, 가입 허용 목록이 모두 여기에 있습니다. 이 페이지는 모든 변수를 용도별로 그룹화합니다: 각 섹션은 **설정하지 않으면 어떻게 되는지**, 그리고 **프로덕션에서 반드시 설정해야 하는 변수가 무엇인지**를 명확히 설명합니다. auth 관련 변수를 실제로 어떻게 설정하는지는 [로그인 및 가입 설정](/auth-setup)을 참고하세요.
## 핵심 서버 변수
배포 전에 반드시 고려해야 하는 핵심 변수입니다 — 일부는 서버를 시작할 수 있게 해주는 기본값을 가지고 있지만, 프로덕션에서는 필수 항목을 명시적으로 설정해야 합니다.
| 변수 | 기본값 | 프로덕션 필수? |
|---|---|---|
| `DATABASE_URL` | `postgres://multica:multica@localhost:5432/multica?sslmode=disable` | **예** |
| `PORT` | `8080` | 아니오 (포트를 변경하는 경우 제외) |
| `JWT_SECRET` | `multica-dev-secret-change-in-production` | **예** (기본값은 안전하지 않음) |
| `APP_ENV` | 비어 있음 | **예** (`production`이어야 함) |
| `FRONTEND_ORIGIN` | 비어 있음 | **예** (자체 호스팅은 자체 도메인을 설정해야 함) |
| `MULTICA_DEV_VERIFICATION_CODE` | 비어 있음 | 아니오 (프로덕션에서는 반드시 비워 두어야 함) |
<Callout type="warning">
**프로덕션에서는 `MULTICA_DEV_VERIFICATION_CODE`를 비워 두세요.** 고정된 로컬 테스트 코드는 기본적으로 비활성화되어 있지만, `MULTICA_DEV_VERIFICATION_CODE=888888`로 활성화하면 `APP_ENV`가 production이 아닌 동안에는 코드를 요청할 수 있는 누구나 그 고정 값으로 로그인할 수 있습니다. 이 단축 코드는 `APP_ENV=production`일 때 무시됩니다.
</Callout>
### 데이터베이스 커넥션 풀
| 변수 | 기본값 | 설명 |
|---|---|---|
| `DATABASE_MAX_CONNS` | `25` | pgxpool 최대 연결 수. 데몬은 자주(3초마다) 폴링하며 연결을 사용하므로, 규모가 큰 배포에서는 더 높은 값이 필요할 수 있습니다 |
| `DATABASE_MIN_CONNS` | `5` | 최소 유휴 연결 수 |
**설정하지 않으면** 위 값이 사용됩니다 — 이전에 프로덕션에서 풀 고갈을 일으켰던 pgx 내장 기본값 4/NumCPU가 **아닙니다**.
## 이메일 설정
Multica는 두 가지 전송 백엔드를 지원합니다 — 클라우드 배포용 [Resend](https://resend.com/), 또는 내부 / 온프레미스 네트워크용 SMTP relay. 둘 다 설정된 경우 `SMTP_HOST`가 `RESEND_API_KEY`보다 우선합니다.
### Resend
| 변수 | 기본값 | 설명 |
|---|---|---|
| `RESEND_API_KEY` | 비어 있음 | Resend API key |
| `RESEND_FROM_EMAIL` | `noreply@multica.ai` | 발신 주소 (Resend 계정에서 검증된 도메인이어야 하며, SMTP를 사용할 때도 `From:` 헤더로 재사용됨) |
### SMTP relay
| 변수 | 기본값 | 설명 |
|---|---|---|
| `SMTP_HOST` | 비어 있음 | SMTP relay 호스트명. 이를 설정하면 SMTP 모드가 활성화되고 Resend를 덮어씁니다 |
| `SMTP_PORT` | `25` | SMTP 포트. STARTTLS 제출에는 `587`을 사용하세요; **포트 465(SMTPS / 암묵적 TLS)는 지원되지 않습니다** |
| `SMTP_USERNAME` | 비어 있음 | SMTP 사용자명. 인증 없는 relay의 경우 비워 두세요 |
| `SMTP_PASSWORD` | 비어 있음 | SMTP 비밀번호 |
| `SMTP_TLS_INSECURE` | `false` | TLS 인증서 검증을 건너뛰려면 `true`로 설정 (사설 CA / 자체 서명 인증서만 해당) |
서버가 STARTTLS를 알리면 자동으로 업그레이드됩니다. dial 타임아웃은 10초이고 전체 SMTP 세션에는 30초 데드라인이 있어, 블랙홀이 된 relay가 auth 핸들러를 멈추게 할 수 없습니다.
**둘 다 설정하지 않았을 때의 동작**: 서버는 오류를 내지 않지만, 전송되어야 했던 모든 이메일(인증 코드, 초대 링크)은 **서버의 stdout에만 기록됩니다**. 로컬 개발에는 편리합니다 — 서버 로그에서 코드를 복사해 사용하세요; **프로덕션에서는 이를 설정하는 것을 잊으면 조용한 블랙홀이 되어**, 사용자는 이메일을 전혀 받지 못하고 아무런 오류도 드러나지 않습니다.
## Google OAuth 설정
선택 사항입니다. 이메일 + 인증 코드만 사용하려면 설정하지 않은 채로 두고, 로그인 페이지에 "Sign in with Google"을 추가하려면 설정하세요.
| 변수 | 기본값 | 설명 |
|---|---|---|
| `GOOGLE_CLIENT_ID` | 비어 있음 | Google Cloud OAuth client ID |
| `GOOGLE_CLIENT_SECRET` | 비어 있음 | Google Cloud OAuth secret |
| `GOOGLE_REDIRECT_URI` | `http://localhost:3000/auth/callback` | OAuth 콜백 URL (자체 호스팅: 자신의 프론트엔드 도메인으로 교체) |
**런타임에 적용됨**: 프론트엔드는 런타임에 `/api/config`를 통해 이 설정을 읽으므로, **변경해도 프론트엔드 리빌드나 재배포가 필요 없습니다** — 서버를 재시작하면 적용됩니다.
전체 설정(Google Cloud Console 단계 포함)은 [로그인 및 가입 설정](/auth-setup#google-oauth-configuration)에 있습니다.
## 파일 스토리지 설정
Multica는 사용자가 업로드한 첨부 파일(댓글의 이미지와 파일)을 저장합니다. **S3가 권장됩니다**; S3가 설정되어 있지 않으면 로컬 디스크로 폴백합니다.
### S3 / S3 호환 스토리지
| 변수 | 기본값 | 설명 |
|---|---|---|
| `S3_BUCKET` | 비어 있음 | **버킷 이름만** (예: `my-bucket`). `.s3.<region>.amazonaws.com` 접미사를 포함하지 **마세요** — 서버가 `S3_BUCKET` + `S3_REGION`으로 공개 호스트를 구성합니다. 이를 설정하면 S3 스토리지가 활성화됩니다 |
| `S3_REGION` | `us-west-2` | AWS 리전. 버킷의 실제 리전과 일치해야 합니다 — SDK 서명과 공개 URL 구성 모두에 사용됩니다 |
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | 비어 있음 | 정적 자격 증명. 둘 다 설정하지 않으면 AWS SDK 기본 자격 증명 체인(IAM role / 환경 자격 증명)이 사용됩니다 |
| `AWS_ENDPOINT_URL` | 비어 있음 | 사용자 정의 S3 호환 엔드포인트 (예: [MinIO](https://min.io/)). 이를 설정하면 path-style URL로 전환됩니다 |
**`S3_BUCKET`을 설정하지 않으면**: 서버는 시작 시 `"S3_BUCKET not set, cloud upload disabled"`를 로깅하고, 모든 업로드는 로컬 디스크로 폴백합니다.
**공개 URL**은 다음 우선순위 순서로 구성됩니다:
1. `CLOUDFRONT_DOMAIN`이 설정된 경우 `https://<CLOUDFRONT_DOMAIN>/<key>`.
2. `AWS_ENDPOINT_URL`이 설정된 경우 `<AWS_ENDPOINT_URL>/<S3_BUCKET>/<key>` (path-style).
3. `https://<S3_BUCKET>.s3.<S3_REGION>.amazonaws.com/<key>` (virtual-hosted-style). `S3_BUCKET`에 점이 포함된 경우, AWS가 발급한 와일드카드 TLS 인증서가 점이 포함된 버킷 호스트를 검증하지 못하므로 서버는 `https://s3.<S3_REGION>.amazonaws.com/<S3_BUCKET>/<key>` (path-style)로 폴백합니다.
### 로컬 디스크 (S3가 설정되지 않은 경우)
| 변수 | 기본값 | 설명 |
|---|---|---|
| `LOCAL_UPLOAD_DIR` | `./data/uploads` | 로컬 스토리지 디렉터리 |
| `LOCAL_UPLOAD_BASE_URL` | 비어 있음 (상대 경로 반환) | 공개 base URL — 설정하지 않으면 프론트엔드가 첨부 파일의 전체 URL을 확인할 수 없습니다 |
### CloudFront (선택)
S3 앞에 CloudFront를 두는 경우 세 가지 변수가 적용됩니다: `CLOUDFRONT_DOMAIN`, `CLOUDFRONT_KEY_PAIR_ID`, `CLOUDFRONT_PRIVATE_KEY` (또는 Secrets Manager에서 읽으려면 `CLOUDFRONT_PRIVATE_KEY_SECRET`). CloudFront를 사용하지 않으면 건너뛰세요 — S3 설정과 충돌하지 않습니다.
### Cookie 도메인
| 변수 | 기본값 | 설명 |
|---|---|---|
| `COOKIE_DOMAIN` | 비어 있음 | 세션 cookie의 범위 |
- **비어 있음**: cookie는 방문한 정확한 호스트에서만 유효합니다 (단일 호스트 배포에 적합)
- **`.example.com`으로 설정**: cookie가 하위 도메인 간에 공유됩니다 (그래서 `app.example.com`과 `api.example.com`이 로그인 세션을 공유함)
- 경고: IP 주소일 수 없습니다 (브라우저가 무시함)
## 누가 가입할 수 있는지 제한하기
세 개의 허용 목록 계층이 우선순위에 따라 결합됩니다. **어느 한 계층이라도 비어 있지 않은 값으로 설정되면, 일치하지 않는 이메일은 거부됩니다** — `ALLOW_SIGNUP=true`조차 이를 무시할 수 없습니다.
| 변수 | 기본값 | 설명 |
|---|---|---|
| `ALLOWED_EMAILS` | 비어 있음 | 명시적 이메일 허용 목록 (쉼표로 구분). 비어 있지 않으면 목록에 있는 이메일만 가입할 수 있습니다 |
| `ALLOWED_EMAIL_DOMAINS` | 비어 있음 | 도메인 허용 목록 (쉼표로 구분). 비어 있지 않으면 목록에 있는 도메인만 가입할 수 있습니다 |
| `ALLOW_SIGNUP` | `true` | 가입 마스터 스위치. 가입을 완전히 비활성화하려면 `false`로 설정 |
**직관에 반하는 부분**: `ALLOWED_EMAIL_DOMAINS=company.io` + `ALLOW_SIGNUP=true`는 "company.io 또는 모두를 허용"한다는 뜻이 **아니라** **company.io만 허용**한다는 뜻입니다. 허용 목록 계층은 AND 시맨틱입니다 — 전체 결정 트리는 [로그인 및 가입 설정 → 가입 허용 목록](/auth-setup#restricting-who-can-sign-up)에 있습니다.
**초대 흐름 자체는 가입 허용 목록을 확인하지 않습니다** — 하지만 초대받은 사람은 초대를 수락하기 전에 여전히 **로그인**할 수 있어야 합니다. 이미 Multica 계정이 있다면(예: 다른 워크스페이스에서) 허용 목록의 영향을 받지 않고 바로 수락할 수 있습니다; **한 번도 가입한 적이 없다면**, 로그인의 첫 단계(인증 코드 요청)는 여전히 허용 목록 확인을 거치며, `ALLOW_SIGNUP=false`나 `ALLOWED_EMAILS` / `ALLOWED_EMAIL_DOMAINS`에 의해 거부된 이메일은 **가입을 완료할 수 없고, 따라서 초대를 수락할 수 없습니다**.
## 워크스페이스 생성 잠그기
`ALLOW_SIGNUP=false`는 새 계정을 차단하지만, 이미 로그인한 사용자가 `POST /api/workspaces`를 통해 또 다른 워크스페이스를 생성하는 것은 **차단하지 않습니다**. 모든 이슈, 저장소, 에이전트가 플랫폼 관리자에게 보여야 하는 자체 호스팅 인스턴스에서는, 그 틈을 막기 위해 `DISABLE_WORKSPACE_CREATION=true`로 설정하세요.
| 변수 | 기본값 | 설명 |
|---|---|---|
| `DISABLE_WORKSPACE_CREATION` | `false` | `true`이면 `POST /api/workspaces`에 대한 모든 호출이 `403 workspace creation is disabled for this instance`를 반환합니다. 웹 UI는 `/api/config`를 통해 모든 "워크스페이스 생성" 요소를 숨깁니다. 역할/owner 예외는 없습니다 — 이 게이트는 인스턴스 단위로 전역 적용됩니다 |
권장 부트스트랩 순서:
1. `DISABLE_WORKSPACE_CREATION`을 설정하지 않은 채로(기본값) 인스턴스를 시작합니다.
2. 관리자로 로그인하여 공유 워크스페이스를 생성합니다.
3. `DISABLE_WORKSPACE_CREATION=true`로 설정하고 백엔드를 재시작합니다. 이 시점부터 사용자는 초대를 통해서만 참여할 수 있습니다.
초대받은 사용자가 첫 인증 코드로 가입을 완료할 수 있도록 `ALLOW_SIGNUP=true`를 유지하고 싶다면, `DISABLE_WORKSPACE_CREATION=true`를 `ALLOWED_EMAIL_DOMAINS` / `ALLOWED_EMAILS`와 결합하여 어떤 주소가 가입할 수 있는지 범위를 지정하세요. `ALLOW_SIGNUP=false`로 설정하면 대기 중인 초대 대상자가 계정을 만드는 것조차 추가로 차단됩니다 — 모든 멤버가 이미 Multica 계정을 가지고 있는 인스턴스에서만 유용합니다.
## 속도 제한 (선택적 Redis)
공개 auth 엔드포인트 — `/auth/send-code`, `/auth/verify-code`, `/auth/google` — 앞에는 IP별 고정 윈도우 속도 제한이 있습니다. 리미터는 Redis로 뒷받침됩니다. `REDIS_URL`을 설정하지 않으면 미들웨어는 **no-op**(fail-open)이 되고, 백엔드는 시작 시 `rate limiting disabled: REDIS_URL not configured`를 로깅합니다.
| 변수 | 기본값 | 설명 |
|---|---|---|
| `REDIS_URL` | 비어 있음 | Redis 연결 URL (예: `redis://localhost:6379/0`). 설정하지 않으면 auth 엔드포인트의 속도 제한이 비활성화됩니다. 동일한 Redis는 실시간 허브 fan-out, PAT 캐시, 데몬 토큰 캐시에서도 사용됩니다 — 설정하지 않으면 모두 인메모리 / 직접 DB 모드로 폴백합니다 |
| `RATE_LIMIT_AUTH` | `5` | `/auth/send-code` 및 `/auth/google`에 대한 IP당 분당 최대 요청 수 |
| `RATE_LIMIT_AUTH_VERIFY` | `20` | `/auth/verify-code`에 대한 IP당 분당 최대 요청 수 |
| `RATE_LIMIT_TRUSTED_PROXIES` | 비어 있음 | 리미터가 그 `X-Forwarded-For` 헤더를 신뢰하도록 허용하는, 쉼표로 구분된 CIDR. 비어 있음(기본값)은 **XFF를 절대 신뢰하지 않음**을 의미합니다 — 리미터는 직접 연결의 `RemoteAddr`만 사용합니다 |
요청이 제한을 초과하면 서버는 `429 Too Many Requests`, `Retry-After: 60`, 그리고 본문 `{"error":"too many requests"}`로 응답합니다.
<Callout type="warning">
**리버스 프록시 뒤에서는 `RATE_LIMIT_TRUSTED_PROXIES`를 반드시 설정해야 합니다.** 그렇지 않으면 백엔드 관점에서 모든 실제 사용자가 프록시의 IP를 공유하게 되어, 배포 전체가 하나의 버킷에 들어가고, `/auth/send-code`가 사이트 전체에 대해 분당 5회가 됩니다. 일반적인 값: 같은 호스트의 Caddy / Nginx에는 `127.0.0.1/32,::1/128`; Cloudflare / ALB / CloudFront에는 해당 CDN의 공개된 IP 범위. `RemoteAddr`이 이 CIDR 중 하나에 속하는 IP만 `X-Forwarded-For`를 사용해 클라이언트를 식별할 수 있습니다.
</Callout>
이 별도의 `RATE_LIMIT_TRUSTED_PROXIES`는 오토파일럿 webhook 리미터(`/api/webhooks/autopilots/{token}`)를 제어하는 `MULTICA_TRUSTED_PROXIES`와는 **다릅니다**. 각 리미터는 자체 목록을 파싱하므로, 프록시 뒤에 있는 배포는 둘 다 설정해야 합니다.
## 데몬 튜닝 파라미터
데몬은 사용자의 로컬 기기에서 실행되며, 그 설정도 로컬 환경 변수에서 읽습니다. 일반적으로 사용되는 것들:
| 변수 | 기본값 | 설명 |
|---|---|---|
| `MULTICA_SERVER_URL` | `ws://localhost:8080/ws` | 서버 주소 (자체 호스팅: 자신의 도메인으로 교체) |
| `MULTICA_DAEMON_HEARTBEAT_INTERVAL` | `15s` | 하트비트 간격 |
| `MULTICA_DAEMON_POLL_INTERVAL` | `3s` | 작업 폴링 간격 |
| `MULTICA_DAEMON_MAX_CONCURRENT_TASKS` | `20` | 최대 동시 작업 수 |
| `MULTICA_<PROVIDER>_PATH` | CLI 이름과 일치 | 각 AI 코딩 도구 실행 파일의 경로 (예: `MULTICA_CLAUDE_PATH`) |
| `MULTICA_<PROVIDER>_MODEL` | 비어 있음 | 각 AI 코딩 도구의 기본 모델 |
각 파라미터가 데몬 동작에 어떻게 영향을 미치는지에 대한 전체 설명은 [데몬과 런타임](/daemon-runtimes)을 참고하세요.
## 프론트엔드 액세스 제어
| 변수 | 기본값 | 설명 |
|---|---|---|
| `FRONTEND_ORIGIN` | 비어 있음 | 프론트엔드 주소. 초대 이메일 링크, CORS 허용 목록, cookie 도메인이 모두 이 값에서 파생됩니다. 설정하지 않으면 초대 이메일 링크는 호스팅 도메인 `https://app.multica.ai`로 폴백합니다 — 자체 호스팅은 이를 명시적으로 설정해야 합니다 |
| `CORS_ALLOWED_ORIGINS` | 비어 있음 | 추가로 허용할 CORS origin (쉼표로 구분) |
| `ALLOWED_ORIGINS` | 비어 있음 | WebSocket 전용 origin 허용 목록 (쉼표로 구분); 설정하지 않으면 폴백 순서는 `CORS_ALLOWED_ORIGINS` → `FRONTEND_ORIGIN` → `localhost:3000/5173/5174`입니다 |
<Callout type="warning">
**`FRONTEND_ORIGIN`을 설정하지 않으면 두 가지 조용한 실패가 발생합니다**: (1) 초대 이메일 링크가 `https://app.multica.ai`(호스팅 도메인)를 가리켜, 클릭해도 사용자가 자체 호스팅 인스턴스로 돌아오지 않습니다; (2) WebSocket Origin 검사가 `localhost:3000 / 5173 / 5174`로 폴백하여, 프로덕션 배포의 모든 WebSocket 연결이 거부되고 프론트엔드가 "실시간 업데이트를 받지 못하는" 것처럼 보입니다.
</Callout>
## GitHub 연동
[GitHub PR ↔ 이슈 연동](/github-integration)에는 두 개의 변수가 필요합니다. 설정에서 Connect GitHub를 활성화하고 들어오는 webhook을 수락하려면 둘 다 설정하세요.
| 변수 | 기본값 | 설명 |
|---|---|---|
| `GITHUB_APP_SLUG` | 비어 있음 | GitHub App의 slug (`https://github.com/apps/<slug>`의 끝부분). 설정 → GitHub 설치 버튼 URL을 구성합니다 |
| `GITHUB_WEBHOOK_SECRET` | 비어 있음 | GitHub App에 설정한 Webhook secret. 모든 `pull_request` / `installation` delivery의 HMAC-SHA256 검증에 사용되며, setup 콜백 state token의 HMAC 키로도 사용됩니다 |
**둘 중 하나라도 설정하지 않았을 때의 동작:**
- 설정 → GitHub의 `Connect GitHub`가 **비활성화**되고 admin에게 "not configured" 힌트를 표시합니다.
- `/api/webhooks/github` 엔드포인트는 **`503 github webhooks not configured`**를 반환합니다 — Multica는 모든 서명을 유효한 것으로 취급하기보다, secret 없이는 이벤트 처리를 거부합니다.
**참고:** `GITHUB_WEBHOOK_SECRET`은 설치 흐름 state token의 서명 키로 재사용되므로, 운영자는 secret 하나만 관리하면 됩니다. 이것은 GitHub App의 *Client* secret이 **아닙니다** — Client secret은 OAuth 관련이며 이 연동에서는 사용되지 않습니다. 전체 안내는 [GitHub 연동 → 자체 호스팅 설정](/github-integration#self-host-setup)을 참고하세요.
## 사용량 분석
기본적으로 서버는 Multica의 공식 PostHog 인스턴스로 보고합니다. 옵트아웃하려면 `ANALYTICS_DISABLED=true`로 설정하세요.
| 변수 | 기본값 | 설명 |
|---|---|---|
| `ANALYTICS_DISABLED` | `false` | 백엔드 분석을 완전히 비활성화하려면 `true`로 설정 |
| `POSTHOG_API_KEY` | 내장 기본 키 | 자신의 PostHog 인스턴스를 가리킬 때 설정 |
| `POSTHOG_HOST` | `https://us.i.posthog.com` | PostHog를 자체 호스팅하는 경우 자신의 호스트로 변경 |
## 다음
- [로그인 및 가입 설정](/auth-setup) — 위의 auth 관련 변수를 실제로 어떻게 설정하는지, 그리고 함정이 어디에 있는지
- [GitHub 연동](/github-integration) — `GITHUB_APP_SLUG` / `GITHUB_WEBHOOK_SECRET`을 뒷받침하는 GitHub App을 어떻게 설정하는지
- [문제 해결](/troubleshooting) — 흔한 설정 오류의 증상과 해결책
- [데몬과 런타임](/daemon-runtimes) — `MULTICA_DAEMON_*` 파라미터가 실제로 하는 일

View File

@@ -0,0 +1,183 @@
---
title: GitHub 연동
description: GitHub App을 한 번만 연결하면, 브랜치·제목·본문에 이슈 식별자가 들어간 PR이 해당 이슈에 자동으로 연결됩니다. 그리고 PR을 머지하면 이슈가 완료로 이동합니다.
---
import { Callout } from "fumadocs-ui/components/callout";
**설정 → GitHub**에서 GitHub 계정 또는 조직을 한 번만 연결하세요. 그 후에는 브랜치 이름, 제목, 본문에 이슈 식별자(예: `MUL-123`)가 들어 있는 모든 pull request가 해당 [이슈](/issues)에 **자동으로 연결**되고, 이슈 사이드바의 **Pull requests** 아래에 표시되며, PR이 머지되면 이슈가 **완료**로 이동합니다.
이슈별 설정은 없습니다. 전체 흐름은 식별자로 동작합니다.
## 연동이 하는 일
| 위치 | 동작 |
|---|---|
| **설정 → GitHub** | 워크스페이스 admin에게는 마스터 토글, **Connect GitHub** 버튼, 기능 스위치(PR 사이드바, Co-authored-by, 자동 연결)가 있는 GitHub 탭이 보입니다. 설치 후에는 GitHub 탭으로 다시 돌아옵니다. |
| **이슈 사이드바 → Pull requests** | 이 이슈에 자동 연결된 모든 PR이 제목, 저장소, 상태(`Open` / `Draft` / `Merged` / `Closed`), 작성자와 함께 표시됩니다. 행을 클릭하면 GitHub의 해당 PR로 이동합니다. |
| **Webhook(백그라운드)** | 모든 `pull_request` 이벤트에서 Multica는 PR 행을 upsert하고, PR에서 이슈 식별자를 스캔한 뒤, 연결 행을 (다시) 구성합니다. 멱등성이 있어 동일 delivery를 재전송해도 변화가 없습니다. |
| **머지 시 상태 자동 변경** | PR이 `merged`로 전환되면, 아직 `Done`이나 `Cancelled`가 아닌 모든 연결된 이슈가 `Done`으로 이동합니다. 상태 변경은 source `github_pr_merged`로 타임라인에 기록됩니다. |
미러링되는 것은 PR 자체뿐입니다. 커밋, 열린 PR이 없는 브랜치 ref, CI 체크 상태는 모델링되지 **않습니다**. 이 연동은 의도적으로 좁게 설계되었습니다.
## 식별자 매칭 방식
Webhook은 다음 순서로 세 필드에서 식별자를 추출합니다: **PR head 브랜치**, **PR 제목**, **PR 본문**. 매처는 다음과 같습니다.
- 대소문자를 구분하지 않습니다 — `mul-123`, `MUL-123`, `Mul-123`이 모두 매칭됩니다.
- 경계가 있습니다 — 왼쪽의 `\b`와 오른쪽의 숫자 앵커 덕분에 `v1.2-3` 같은 버전 번호나 이메일 형식 문자열을 잘못 잡지 않습니다.
- 워크스페이스 범위로 제한됩니다 — 해당 워크스페이스 고유의 [이슈 prefix](/workspaces)에만 매칭됩니다. prefix가 `MUL`인 워크스페이스에서는 정수가 다른 이슈와 일치하더라도 `FOO-1`이 무시됩니다.
- 중복이 제거됩니다 — 본문에 `MUL-1, MUL-1`을 나열해도 이슈는 한 번만 연결됩니다.
하나의 PR에서 **여러 이슈**를 참조할 수 있습니다. `Closes MUL-1, MUL-2`는 PR을 두 이슈에 모두 연결하고, 머지하면 두 이슈 모두 `Done`으로 진행됩니다.
## 머지 시 완료 자동 변경 규칙
PR의 `merged` 필드가 `true`로 바뀌면, 연결된 모든 이슈가 평가됩니다.
| 이슈 현재 상태 | 결과 |
|---|---|
| `done` | 변화 없음(이미 종료 상태). |
| `cancelled` | **변화 없음** — 취소됨은 사용자가 작업을 명시적으로 포기했다는 의미이므로, 연동이 이 신호를 덮어쓰지 않습니다. |
| 그 외 모두(`todo`, `in_progress`, `in_review`, `blocked`, `backlog`) | `done`으로 이동. |
PR을 머지하지 **않고** 닫으면 PR 카드의 상태만 `Closed`로 업데이트됩니다. 연결된 이슈는 그대로 유지됩니다 — 머지 없이 닫는 것이 무엇을 의미하는지는 사용자가 결정합니다.
<Callout type="info">
이 동작은 타임라인에서 `system` 액터에게 귀속됩니다. 이슈 구독자는 사람이 상태를 옮겼을 때와 동일하게 상태 변경에 대한 인박스 알림을 받습니다.
</Callout>
## 자동 연결되지 않는 것
- **커밋 메시지의 식별자** — 브랜치 / 제목 / 본문만 스캔됩니다. `MUL-123: fix login`이라는 제목의 커밋은 동일한 문자열이 PR 제목이나 본문에도 나타나지 않는 한 자동 연결되지 않습니다.
- **PR 댓글의 식별자** — PR 자체의 메타데이터만 스캔되며, 이후의 GitHub 댓글은 무시됩니다.
- **App이 설치되지 않은 저장소의 PR** — App이 없으면 Multica는 webhook을 전혀 받지 못합니다.
- **PR을 이슈에 수동으로 연결하기** — 아직 이를 위한 UI는 없습니다. 팀의 규칙상 식별자를 Multica가 읽지 않는 위치에 둔다면, PR 제목이나 본문에 추가하세요.
## 연결 해제
**설정 → GitHub**에는 설치 목록이 없습니다 — 기존 설치는 GitHub에서 직접 관리합니다.
- **GitHub에서** — `https://github.com/settings/installations`(개인) 또는 `https://github.com/organizations/<org>/settings/installations`(조직)에서 Multica GitHub App을 제거합니다. Multica는 `installation.deleted` webhook을 받아 실시간으로 행을 삭제하며, 열려 있는 Settings 탭은 새로고침 없이 업데이트됩니다.
- **Multica 내부에서의 연결 해제는 admin 전용입니다** — GitHub 탭의 연결 해제 컨트롤은 admin이 아닌 사용자에게는 숨겨집니다. 마스터 GitHub 스위치가 꺼져 있어도 계속 사용할 수 있어, admin이 원클릭으로 기능을 비활성화한 후에도 오래된 설치를 해제할 수 있습니다.
연결 해제 후에도 미러링된 PR 행은 데이터베이스에 남아 과거 이슈 사이드바에서 무엇이 연결되어 있었는지 계속 보여주지만, 해당 설치에서 새로 들어오는 webhook 이벤트는 더 이상 수락되지 않습니다.
## 권한 및 가시성
- **연결 / 연결 해제**에는 워크스페이스 **owner 또는 admin**이 필요합니다. member에게는 카드 설명은 보이지만 Connect 버튼은 보이지 않습니다.
- 이슈의 **Pull requests** 사이드바는 해당 이슈를 읽을 수 있는 모든 사람에게 보입니다 — 이슈 상세의 나머지 부분과 동일한 권한입니다.
- GitHub App은 pull request와 메타데이터에 대한 **읽기 전용** 액세스를 요청합니다. Multica는 커밋, 댓글, 상태 체크를 GitHub로 다시 푸시하지 않습니다.
## 자체 호스팅 설정
Multica Cloud에서 Multica를 실행 중이라면 연동이 이미 구성되어 있습니다 — 이 섹션은 건너뛰세요.
자체 호스팅의 경우, GitHub App을 하나 만들고, 서버를 가리키게 한 뒤, 환경 변수 두 개를 설정합니다. 전체 흐름은 아래와 같습니다.
### 1. GitHub App 만들기
다음 중 하나로 이동하세요.
- 개인 계정 → `https://github.com/settings/apps/new`
- 조직 → `https://github.com/organizations/<org>/settings/apps/new`
다음을 입력하세요.
| 필드 | 값 |
|---|---|
| **GitHub App name** | 알아보기 쉬운 이름, 예: `Multica` 또는 `Multica (staging)`. |
| **Homepage URL** | Multica 프론트엔드, 예: `https://multica.example.com`. |
| **Callback URL** | 비워 두세요 — Multica는 OAuth 사용자 신원을 사용하지 않습니다. |
| **Setup URL** | `https://<api-host>/api/github/setup`. **"Redirect on update"를 체크하세요.** |
| **Webhook → Active** | 활성화. |
| **Webhook URL** | `https://<api-host>/api/webhooks/github`. |
| **Webhook secret** | 긴 무작위 문자열을 생성하세요(예: `openssl rand -hex 32`). 2단계에서 동일한 값을 Multica의 env에 붙여넣게 됩니다. |
| **Permissions → Repository → Pull requests** | **Read-only**. |
| **Permissions → Repository → Metadata** | Read-only(필수). |
| **Subscribe to events** | **Pull request**를 체크하세요. |
| **Where can this GitHub App be installed?** | 선택 사항. 단일 조직 설정에는 `Only on this account`로 충분합니다. |
**Create GitHub App** 후, App 상세 페이지에서 두 가지를 기록해 두세요.
- 상단의 **public link** — 그 꼬리가 slug입니다. `https://github.com/apps/multica-acme` → slug = `multica-acme`.
- 방금 생성한 **webhook secret**(나중에 GitHub에서 다시 읽을 수 없으니 — 지금 저장하세요).
<Callout type="warning">
**Webhook secret ≠ Client secret.** App 설정 페이지에는 두 필드가 함께 쌓여 있습니다. **Webhook secret**은 `pull_request` payload에 서명하는 값으로, Multica가 필요로 하는 것입니다. **Client secret**은 OAuth용이며 이 연동에서는 사용되지 않습니다. 이 둘을 혼동하면 모든 webhook delivery에서 혼란스러운 `401 invalid signature`가 발생합니다.
</Callout>
### 2. 환경 변수 설정
API 서버에서:
```dotenv
GITHUB_APP_SLUG=multica-acme
GITHUB_WEBHOOK_SECRET=<the webhook secret you generated>
```
두 변수 모두 필수입니다. 둘 중 하나라도 누락되면:
- Settings의 `Connect GitHub`이 **비활성화**되고 "not configured" 힌트가 표시됩니다.
- `/api/webhooks/github` 엔드포인트가 **`503 github webhooks not configured`**를 반환합니다 — Multica는 secret 없이 이벤트를 처리하기를 거부하며, 모든 서명을 조용히 유효한 것으로 취급하지 않습니다.
`FRONTEND_ORIGIN`도 설정되어 있어야 합니다(어떤 프로덕션 자체 호스팅이든 이미 설정되어 있습니다). 설치 후 setup 콜백이 사용자를 `<FRONTEND_ORIGIN>/settings?tab=github`으로 다시 돌려보냅니다.
env 변수를 설정한 후 API를 재시작하세요.
### 3. 마이그레이션 실행
이 연동은 테이블을 마이그레이션 `079_github_integration`으로 제공합니다. 기존 배포를 업그레이드하는 경우:
```bash
make migrate-up
```
세 개의 테이블이 생성됩니다: `github_installation`, `github_pull_request`, `issue_pull_request`. 이들은 워크스페이스와 함께 cascade-delete되므로, 워크스페이스를 제거하면 자동으로 정리됩니다.
### 4. UI에서 연결
Multica에서:
1. owner 또는 admin 권한으로 **설정 → GitHub**를 엽니다.
2. **Connect GitHub**를 클릭합니다. GitHub가 새 탭에서 열립니다.
3. 액세스를 부여할 저장소를 선택하고 **Install**합니다.
4. GitHub가 `<api-host>/api/github/setup`으로 리디렉션하여 설치를 기록한 뒤, `<FRONTEND_ORIGIN>/settings?tab=github&github_connected=1`로 돌려보냅니다.
그 후, 브랜치 / 제목 / 본문에 이슈 식별자가 들어 있는 PR을 열어 보세요 — 몇 초 내에 해당 이슈의 상세 페이지에 Pull requests 블록이 나타납니다.
### 5. curl 프로브로 검증
설치 후 GitHub의 **Recent Deliveries** 페이지에서 `401 invalid signature`가 보고된다면, 양쪽의 secret이 다른 것입니다. 어느 쪽이 잘못되었는지 가장 빠르게 찾는 방법은 GitHub를 우회하는 것입니다.
```bash
SECRET="<the value you put in GITHUB_WEBHOOK_SECRET>"
BODY='{"zen":"test"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')
curl -i -X POST https://<api-host>/api/webhooks/github \
-H "X-Hub-Signature-256: sha256=$SIG" \
-H "X-GitHub-Event: ping" \
-H "Content-Type: application/json" \
-d "$BODY"
```
| HTTP 상태 | 의미 | 해결 방법 |
|---|---|---|
| `200` `{"ok":"pong"}` | 서버가 로드한 secret이 `$SECRET`과 일치합니다. 불일치는 GitHub 쪽에 있습니다. | App → Webhook secret 편집 → **동일한 값을 붙여넣기** → **Save changes**(저장하지 않고 필드 밖을 클릭하면 이전 secret이 유지됩니다). 재전송하세요. |
| `401 invalid signature` | 서버가 로드한 secret이 생각하는 값이 **아닙니다**. | env 변수가 실행 중인 프로세스에 적용되었는지 확인하세요(예: `kubectl exec` → `echo -n "$GITHUB_WEBHOOK_SECRET" \| wc -c`). 재배포하세요. |
| `503 github webhooks not configured` | 프로세스에서 `GITHUB_WEBHOOK_SECRET`이 비어 있습니다. | env 변수를 설정하고 API를 재시작하세요. |
## 제한 사항
현재 알아 둬야 할 몇 가지 거친 부분이 있습니다.
- **아직 수동 연결 UI가 없습니다** — PR을 연결하는 유일한 방법은 브랜치, 제목, 본문에 식별자를 두는 것입니다.
- **CI / 체크 상태가 없습니다** — PR 자체만 미러링됩니다. 빌드 상태, 리뷰 댓글, 리뷰어는 Multica에 표시되지 않습니다.
- 머지 → 완료 규칙에 대한 **워크스페이스 수준 설정이 없습니다** — 고정된 기본값입니다(`cancelled`가 아닌 한 `merged → done`). 워크스페이스에서 커스터마이즈할 수 있는 매핑은 향후 추가될 예정입니다.
- **하나의 이슈에 여러 PR이 연결된 경우 머지가 보수적입니다** — 두 PR이 모두 `MUL-123`을 참조하고 첫 번째가 머지되면, 이슈는 즉시 `Done`으로 이동합니다. 진행하기 전에 연결된 모든 PR이 해결되기를 기다리는 후속 변경이 진행 중입니다.
## 다음
- [이슈](/issues) — PR에서 참조하는 이슈 식별자(`MUL-123`)
- [워크스페이스](/workspaces) — 워크스페이스별 이슈 prefix를 설정하는 곳
- [환경 변수](/environment-variables) — 위의 GitHub 변수를 포함한 전체 env 참조

View File

@@ -0,0 +1,54 @@
---
title: Multica의 작동 방식
description: 세 가지 핵심 구성 요소(서버 / 데몬 / AI 코딩 도구)가 어떻게 협력하여 에이전트의 작업을 실행하는지 설명합니다.
---
import { ArchitectureDiagram } from "@/components/architecture-diagram";
Multica는 **분산형** 플랫폼입니다. 여러분이 보는 웹 인터페이스는 겉으로 드러난 부분일 뿐이고, 실제 작업은 세 가지 구성 요소가 처리합니다. **Multica 서버**는 데이터를 소유합니다([워크스페이스](/workspaces), [이슈](/issues), [멤버](/members-roles), [작업](/tasks) 대기열 등). **[데몬](/daemon-runtimes)**은 여러분 자신의 기기에서 실행되며 작업을 가져와 AI 코딩 도구를 구동합니다. 그리고 **[AI 코딩 도구](/providers)**(Claude Code, Codex, 그 밖의 로컬 CLI)는 실제로 코드를 작성하는 구성 요소입니다. 이것이 Multica와 Linear 또는 Jira의 가장 큰 차이입니다. **[에이전트](/agents)는 우리 서버가 아니라 여러분의 기기에서 실행됩니다.**
## 세 가지 핵심 구성 요소
<ArchitectureDiagram />
- **Multica 서버** — 여러분이 보는 워크스페이스, 이슈 목록, 댓글 스레드는 모두 이곳의 데이터베이스에 저장됩니다. 또한 여러분과 동료 사이의 실시간 업데이트를 푸시하는 WebSocket 허브이기도 합니다. 에이전트 작업은 **실행하지 않습니다.**
- **데몬** — Multica CLI의 일부로, 여러분 자신의 기기에서 실행됩니다. 시작 시 로컬에 설치된 AI 코딩 도구를 감지하고, 서버에 등록한 다음, 3초마다 작업을 폴링하고 15초마다 하트비트를 전송하기 시작합니다.
- **AI 코딩 도구** — 다음 열두 가지 중 하나(또는 여러 개를 병렬로): [Antigravity](/providers#antigravity), [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi). 데몬이 작업을 가져온 뒤에는 이러한 도구를 사용해 실제 작업을 수행합니다.
도구 체인이 로컬에 유지되므로 **여러분의 API 키, 코드 디렉터리, 인증된 도구**는 오직 여러분의 기기에서만 사용됩니다. Multica 서버는 그중 어떤 것도 보지 못합니다. 이는 자체 호스팅을 하든 Cloud를 사용하든 동일하게 적용됩니다.
## 작업의 수명 주기
가장 흔한 시나리오인, 이슈를 에이전트에게 할당하는 경우를 살펴보겠습니다.
1. 여러분이 웹 UI에서 할당을 클릭합니다. 브라우저가 Multica 서버로 HTTP 요청을 보냅니다.
2. 서버가 해당 이슈의 담당자를 에이전트로 설정하고, 동시에 작업 대기열에 상태 `queued`인 실행 작업을 생성합니다.
3. 여러분의 기기에 있는 데몬이 다음 폴링(3초 이내) 때 작업을 가져옵니다. 작업 상태가 `dispatched`로 바뀝니다.
4. 데몬이 로컬에 격리된 작업 디렉터리를 생성하고 해당 AI 코딩 도구를 호출합니다. 작업 상태가 `running`으로 바뀝니다.
5. AI가 로컬에서 코드를 작성하고, 테스트를 실행하고, 댓글을 서버로 다시 게시합니다.
6. 실행이 종료됩니다. 데몬이 결과(성공 / 실패)를 서버에 보고하고, 작업 상태가 `completed` 또는 `failed`로 바뀝니다. 여러분은 웹 UI에서 진행 상황 업데이트를 실시간으로(WebSocket을 통해) 확인합니다.
자세한 동작 원리는 [데몬과 런타임](/daemon-runtimes) 및 [작업](/tasks)을 참조하세요.
## 에이전트를 작동시키는 네 가지 방법
"이슈 할당"만 있는 것은 아닙니다. Multica에는 협업 스타일별로 하나씩, 4가지 트리거가 있습니다.
| 방법 | 일반적인 시나리오 | 문서 |
|---|---|---|
| **이슈 할당** | 가장 흔한 방법. 이슈를 에이전트에게 할당하면 스스로 작업을 시작합니다 | [이슈 할당하기](/assigning-issues) |
| **댓글에서 에이전트 @멘션** | "이거 한번 봐 줘" — 담당자나 상태를 바꾸지 않고 댓글 하나로 실행을 시작합니다 | [에이전트 멘션하기](/mentioning-agents) |
| **다이렉트 채팅** | 이슈에 묶이지 않은 독립적인 대화 — 질문하거나, 이슈 초안을 작성하게 합니다 | [채팅](/chat) |
| **오토파일럿(예약)** | 상시 지시 — "매주 월요일 아침에 스탠드업 요약을 해 줘" 같은 것 | [오토파일럿](/autopilots) |
## 런타임: 어디서 실행되고, 도구는 몇 개인가
**런타임**은 "데몬 × 하나의 AI 코딩 도구"의 조합입니다. 한 기기의 데몬에 Claude Code와 Codex가 모두 설치되어 있고 두 개의 워크스페이스에 참여해 있다면, Multica는 4개의 독립적인 런타임(워크스페이스 2개 × 도구 2개)을 등록합니다.
현재는 **로컬 데몬** 런타임 모델만 지원됩니다. 클라우드 런타임(여러분 자신의 기기를 켜 둘 필요가 없는 방식)은 **곧 제공될 예정**이며, 현재는 대기자 명단 등록만 받고 있습니다. [다운로드](https://multica.ai/download) 페이지에서 신청하세요.
## 다음 단계
- [Cloud Quickstart](/cloud-quickstart) — 5분 만에 Multica Cloud에 연결하기
- [Self-Host Quickstart](/self-host-quickstart) — 자체 백엔드 실행하기
- [데몬과 런타임](/daemon-runtimes) — 아키텍처가 의존하는 구성 요소에 대한 심층 분석

View File

@@ -0,0 +1,65 @@
---
title: 인박스와 구독
description: Multica가 언제 알림을 보내는지, 그리고 관심 없는 이슈를 음소거하는 방법.
---
import { Callout } from "fumadocs-ui/components/callout";
인박스는 Multica가 여러분을 **방해**하는 곳입니다. 여러분에게 할당된 [이슈](/issues), [`@` 멘션](/comments), 그리고 여러분이 구독한 이슈의 활동이 모두 여기에 도착합니다.
여러분은 **구독**과 **구독 취소**를 통해 어떤 이슈 활동이 여러분에게 도달할지 제어합니다.
## 인박스에 표시되는 것
다음 이벤트가 여러분의 인박스로 알림을 전달합니다.
- **이슈 할당 / 할당 해제 / 재할당** — 여러분이 새 담당자(또는 이전 담당자)가 되면 알림을 받습니다
- **여러분이 구독한 이슈의 상태, 우선순위, 마감일 변경**
- **여러분이 구독한 이슈의 새 댓글**
- **여러분이 댓글에서 `@`로 멘션됨** — 구독 여부와 관계없이 전달됩니다
- **누군가 여러분의 이슈나 댓글에 반응함**
- **여러분이 할당한 에이전트 [작업](/tasks)이 실패함**
## `@all`은 워크스페이스 전체에 알립니다
`@all`은 특수한 대상입니다. 워크스페이스의 **모든 멤버**에게 알림을 푸시합니다.
<Callout type="warning">
**`@all`은 아껴서 사용하세요.** 50명 규모의 워크스페이스에서 `@all` 댓글 하나는 즉시 50개의 인박스 알림을 생성합니다. 일상적인 논의가 아니라 중대한 사안(프로덕션 장애, 마일스톤 공지)에만 사용하세요.
</Callout>
## 에이전트는 알림을 받지 않습니다
에이전트는 **절대** 인박스 알림을 받지 않습니다. 담당자나 생성자일 때도, 댓글에서 `@`로 멘션될 때도 받지 않습니다.
이것은 버그가 아닙니다. 에이전트는 인박스를 읽지 않습니다. 에이전트는 [**즉시 트리거**](/assigning-issues) 방식으로 동작합니다. 이슈를 할당하거나 댓글에서 에이전트를 `@`로 멘션하면 곧바로 해당 에이전트를 위한 작업이 시작됩니다. 인박스는 사람을 위한 알림 메커니즘이며, 에이전트에게는 아무런 의미가 없습니다.
## 구독 규칙
다음 네 가지 상황에서 여러분은 이슈에 **자동 구독**됩니다.
- 여러분이 이슈를 **생성**한 경우
- 여러분이 이슈에 **할당**된 경우
- 여러분이 이슈에 **댓글**을 단 경우
- 여러분이 이슈 또는 그 댓글에서 **`@`로 멘션**된 경우
자동 구독은 한 번만 일어납니다. 생성자이면서 동시에 멘션 대상이더라도 두 번 구독되지는 않습니다.
<Callout type="warning">
**재할당은 자동으로 구독을 취소하지 않습니다.** 여러분이 예전에 담당자였다가 교체되었더라도, **그 이슈의 업데이트를 계속 받게 됩니다.** 자동 구독이 데이터베이스에 그대로 남아 있기 때문입니다.
더 이상 알림을 받지 않으려면 이슈를 열어 직접 구독을 취소하세요.
</Callout>
또한 어떤 이슈든(관련 없는 이슈라도) **직접 구독**하거나, 어떤 자동 구독이든 **직접 구독을 취소**할 수 있습니다. UI에서는 이슈 페이지의 오른쪽 패널을 사용하고, CLI에서는 `multica issue subscriber add/remove`를 사용하세요.
## 하위 이슈의 상태 변경은 상위 이슈로 전파됩니다
하위 이슈의 **상태**가 변경되면, 상위 이슈의 구독자도 알림을 받습니다. 그들이 하위 이슈를 구독하지 않았더라도 마찬가지입니다.
이것은 **상태에만** 적용됩니다. 하위 이슈의 댓글, 우선순위, 마감일 변경은 상위 이슈로 전파되지 **않습니다**.
## 다음
- [댓글과 멘션](/comments) — `@` 멘션의 작동 방식과 주의할 점
- [에이전트에게 이슈 할당하기](/assigning-issues) — 에이전트가 트리거되는 방식(그리고 에이전트가 인박스를 읽지 않는 이유)

View File

@@ -0,0 +1,50 @@
---
title: 환영합니다
description: 인간과 AI 에이전트가 같은 워크스페이스에서 함께 일하는 작업 협업 플랫폼.
---
import { Callout } from "fumadocs-ui/components/callout";
Multica는 인간과 AI [에이전트](/agents)가 같은 [워크스페이스](/workspaces)에서 함께 일하는 작업 협업 플랫폼입니다. 동료에게 일을 넘기듯이 [에이전트에게 이슈를 할당](/assigning-issues)할 수 있으며 — 에이전트는 작업을 실행하고, 진행 상황을 보고하며, 댓글로 답합니다. 또한 [채팅 창을 열어 직접 대화](/chat)하면서 이슈 초안 작성, 질문 답변, 일회성 요청 처리를 맡길 수도 있습니다.
이 페이지에서는 에이전트가 어디에서 실행되는지, 그리고 Multica를 사용하기 시작하는 여러 방법을 설명합니다.
## 에이전트가 실행되는 곳
에이전트는 Multica 서버에서 작업을 실행하지 **않습니다**. 현재 Multica는 하나의 런타임 모델을 지원합니다:
- **로컬 [데몬](/daemon-runtimes)** — 자신의 기기에서 `multica daemon`을 실행하면, 데몬이 로컬에 설치된 [AI 코딩 도구](/providers)를 구동합니다. 현재 열두 가지가 기본 내장되어 있습니다: [Antigravity](/providers#antigravity), [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi). API 키, 툴체인, 코드 디렉터리는 모두 자신의 기기에 머뭅니다.
<Callout type="info">
**클라우드 런타임이 곧 제공됩니다.** 현재는 대기 명단으로만 운영됩니다. 출시되면 로컬 데몬이 필요 없어지며 — 에이전트 작업이 Multica Cloud에서 직접 실행됩니다. [다운로드](https://multica.ai/download) 페이지에서 등록하면 알림을 받을 수 있습니다.
</Callout>
## Multica를 사용하는 세 가지 방법
처음 두 카드는 **백엔드 선택지** — Multica 서버가 어디에서 실행되는지를 정합니다. 세 번째는 **클라이언트 선택지** — 어떤 인터페이스를 사용할지를 정합니다. 데스크톱 앱은 두 백엔드 중 어느 쪽과도 함께 사용할 수 있습니다.
<NumberedCards>
<NumberedCard number="01" title="Multica Cloud" href="/cloud-quickstart" tag="대기 명단">
관리형 백엔드. CLI를 설치하고 로컬에서 데몬을 실행한 뒤, Multica가 호스팅하는 서버에 연결합니다. 약 5분이면 됩니다.
</NumberedCard>
<NumberedCard number="02" title="자체 호스팅" href="/self-host-quickstart" tag="Docker · Helm">
Docker Compose로 자신의 서버에서 전체 백엔드를 실행합니다. 데이터베이스, 서버, 스토리지가 모두 자신의 인프라에 위치합니다.
</NumberedCard>
<NumberedCard number="03" title="데스크톱 앱" href="/desktop-app" tag="추천">
네이티브 멀티탭 창. CLI가 내장되어 있고 실행 시 데몬을 자동으로 시작합니다 — 설치 후 실행할 명령이 전혀 없습니다. Multica Cloud 또는 자체 호스팅 백엔드에 연결합니다.
</NumberedCard>
</NumberedCards>
## 다음 단계
<NumberedSteps>
<Step number="01" title="런타임 모델부터 이해하기">
[Multica는 어떻게 작동하나요](/how-multica-works) — 30초면 읽을 수 있으며, "서버는 에이전트를 실행하지 않고 에이전트는 사용자의 기기에서 실행된다"는 점을 확실히 짚어줍니다.
</Step>
<Step number="02" title="시작할 방법 고르기">
위의 세 가지 중 하나를 선택하세요 — 대부분은 [데스크톱 앱](/desktop-app)으로 시작합니다. CLI 설정이 필요 없고 5분이면 실행됩니다.
</Step>
<Step number="03" title="첫 이슈 할당하기">
[이슈](/issues)를 만들고 담당자로 동료 대신 에이전트를 선택하세요. 에이전트가 결과를 가져올 때까지 기다리면 됩니다.
</Step>
</NumberedSteps>

View File

@@ -0,0 +1,180 @@
---
title: 에이전트 런타임 설치하기
description: Multica는 사용자 기기에 설치된 AI 코딩 도구를 구동합니다. 이 페이지에서는 데몬이 도구를 감지할 수 있도록 지원되는 12종의 도구를 각각 설치하는 방법을 설명합니다.
---
import { Callout } from "fumadocs-ui/components/callout";
Multica에서 **런타임**이란 사용자 기기의 데몬과, 데몬이 `PATH`에서 찾아낸 AI 코딩 도구 하나가 짝을 이룬 것입니다. 온보딩의 "런타임 연결" 단계에서 **지원되는 도구를 감지하지 못했습니다**라고 표시된다면, 데몬이 `PATH`를 스캔했지만 구동 방법을 아는 12종의 도구 중 어느 것도 찾지 못했다는 뜻입니다. 아래 도구 중 하나(또는 여러 개)를 설치한 다음 해당 단계로 돌아와 다시 스캔하세요. 몇 초 안에 런타임이 나타납니다.
이 페이지는 다음 문서의 설치 측면 동반 문서입니다.
- [데몬과 런타임](/daemon-runtimes) — 감지가 작동하는 방식
- [AI 코딩 도구 매트릭스](/providers) — 각 도구가 할 수 있는 것과 할 수 없는 것(세션 재개, MCP, 모델 선택)
<Callout type="info">
Multica 서버는 사용자의 API 키나 도구 자체를 결코 보지 못합니다. 아래의 모든 것 — 설치, 인증, 모델 접근 — 은 사용자의 로컬 기기에 존재합니다. 무언가 실패한다면 거의 항상 로컬 문제입니다.
</Callout>
## 시작하기 전에
아래 **모든** 도구에 두 가지 사전 조건이 적용됩니다.
1. **Multica 데몬이 실행 중이어야 합니다.** [Multica CLI](/cli)를 설치한 후 `multica daemon start`를 실행하거나, 데몬을 자동으로 시작하는 [Multica 데스크톱 앱](/desktop-app)을 사용하세요. 데몬이 실행 중이지 않으면 도구를 감지할 주체가 없습니다.
2. **도구의 바이너리가 `PATH`에서 접근 가능해야 합니다.** 데몬은 각 도구를 이름으로 호출하여 실행합니다(각 섹션의 **데몬이 찾는 이름** 열 참고). 터미널에서 `which <name>`으로 찾을 수 없다면 데몬도 찾지 못합니다. 설치한 후에는 새 터미널을 열거나(또는 데몬을 재시작하여) 새 `PATH` 항목이 반영되도록 하세요.
도구를 설치한 후에는 데몬을 재시작하세요.
```bash
multica daemon restart
```
또는 데스크톱 앱에서는 앱을 다시 실행하기만 하면 됩니다. 데몬은 시작될 때마다 `PATH`를 다시 스캔합니다.
## 지원되는 12종의 도구
대략 많이 쓰이는 순서대로 나열했습니다. 이미 자격 증명을 갖고 있는 것을 골라 사용하세요. 12종을 모두 설치할 필요는 없습니다.
### Claude Code (Anthropic)
가장 완전한 연동입니다. 세션 재개가 작동하고, MCP가 작동하며, **11종 중 에이전트의 `mcp_config` 필드를 실제로 읽어 들이는 유일한 도구**입니다(자세한 내용은 [매트릭스](/providers#mcp-configuration-only-claude-code-actually-reads-it) 참고).
| | |
|---|---|
| 데몬이 찾는 이름 | `claude` |
| 설치 | [claude.com/claude-code](https://www.claude.com/claude-code)의 공식 가이드를 따르세요. 일반적인 방법은 npm 패키지 `@anthropic-ai/claude-code`입니다(Node.js 18+ 필요). |
| 인증 | `claude`를 한 번 실행하고 CLI 내 로그인 절차를 따르거나, `ANTHROPIC_API_KEY`를 설정하세요. |
| 비고 | 새 사용자에게 가장 먼저 권장하는 선택지입니다. |
### Codex (OpenAI)
더 세분화된 승인 게이트를 갖춘 JSON-RPC 2.0 전송 방식입니다. **세션 재개 코드는 존재하지만 현재 도달할 수 없습니다** — 재개가 필요하다면 Claude Code 또는 ACP 계열 중 하나를 선택하세요.
| | |
|---|---|
| 데몬이 찾는 이름 | `codex` |
| 설치 | [github.com/openai/codex](https://github.com/openai/codex)의 공식 가이드를 따르세요. 일반적인 방법은 npm 패키지 `@openai/codex`입니다. |
| 인증 | `codex login`(브라우저 기반) 또는 `OPENAI_API_KEY`. |
### Cursor (Anysphere)
Cursor 에디터에 대응하는 CLI입니다. **세션 재개가 작동하지 않습니다** — Cursor의 CLI가 세션 id를 반환하지 않으므로 재개 시 전달하는 값은 항상 유효하지 않습니다.
| | |
|---|---|
| 데몬이 찾는 이름 | `cursor-agent` |
| 설치 | [Cursor 에디터](https://cursor.com/)를 설치한 다음 [docs.cursor.com](https://docs.cursor.com/)의 문서에 따라 CLI를 설치하세요. 바이너리 이름은 `cursor`가 아니라 `cursor-agent`입니다. |
| 인증 | Cursor 에디터를 통해 로그인하면 CLI가 해당 세션을 재사용합니다. |
### GitHub Copilot
모델 라우팅은 사용자의 GitHub 계정 권한(entitlement)을 통해 이루어집니다 — 도구가 직접 모델을 고르지 않고, 어떤 모델을 받을지는 GitHub가 결정합니다.
| | |
|---|---|
| 데몬이 찾는 이름 | `copilot` |
| 설치 | GitHub의 CLI 문서 [github.com/github/copilot-cli](https://github.com/github/copilot-cli)를 참고하세요. |
| 인증 | CLI를 통한 브라우저 기반 GitHub 로그인. |
| 비고 | 로그인한 계정에 활성화된 GitHub Copilot 구독이 필요합니다. |
### Gemini (Google)
Gemini 2.5 및 3 시리즈를 지원합니다. 세션 재개와 MCP는 없습니다 — 단발성 작업에 적합합니다.
| | |
|---|---|
| 데몬이 찾는 이름 | `gemini` |
| 설치 | [github.com/google-gemini/gemini-cli](https://github.com/google-gemini/gemini-cli)의 공식 가이드를 따르세요. 일반적인 방법은 npm 패키지 `@google/gemini-cli`입니다. |
| 인증 | `gemini`를 실행하면 Google 계정 로그인을 요청하거나, `GEMINI_API_KEY`를 설정하세요. |
### OpenCode (SST)
오픈 소스 CLI 에이전트입니다. 자체 설정 파일에서 사용 가능한 모델을 동적으로 발견합니다 — 자신만의 모델 카탈로그를 직접 가져오려는 사용자에게 잘 맞습니다.
| | |
|---|---|
| 데몬이 찾는 이름 | `opencode` |
| 설치 | [opencode.ai](https://opencode.ai/)의 공식 가이드 또는 GitHub 저장소 [github.com/sst/opencode](https://github.com/sst/opencode)를 따르세요. 일반적인 방법은 설치 스크립트 또는 npm 패키지입니다. |
| 인증 | OpenCode의 문서에 따라 모델 제공자(Anthropic, OpenAI 등)를 구성하세요. |
### Kiro CLI (Amazon)
ACP-over-stdio 전송 방식입니다. 세션 재개는 ACP `session/load`를 통해 작동하며, 스킬은 `.kiro/skills/`로 복사됩니다.
| | |
|---|---|
| 데몬이 찾는 이름 | `kiro-cli` |
| 설치 | [kiro.dev](https://kiro.dev/)의 Kiro 문서를 참고하세요. 바이너리 이름은 `kiro`가 아니라 `kiro-cli`입니다. |
| 인증 | AWS 계정 기반이며, Kiro 자체 온보딩을 따르세요. |
### Kimi (Moonshot)
ACP 프로토콜 에이전트로, 주로 중국 시장을 겨냥합니다. 스킬은 `.kimi/skills/` 아래에 위치합니다(네이티브 발견).
| | |
|---|---|
| 데몬이 찾는 이름 | `kimi` |
| 설치 | [github.com/MoonshotAI/kimi-cli](https://github.com/MoonshotAI/kimi-cli)의 공식 가이드를 따르세요. |
| 인증 | Moonshot API 키이며, 공급사 문서에 따라 구성합니다. |
### Hermes (Nous Research)
ACP 프로토콜 에이전트입니다(Kimi와 전송 방식을 공유). 세션 재개가 작동합니다. 스킬 주입 경로는 일반적인 `.agent_context/skills/`로 폴백됩니다 — 의존하기 전에 스킬이 제대로 로드되는지 확인하세요.
| | |
|---|---|
| 데몬이 찾는 이름 | `hermes` |
| 설치 | 최신 CLI 배포본은 Nous Research의 저장소 [github.com/NousResearch](https://github.com/NousResearch)를 참고하세요. |
| 인증 | 공급사 문서에 따릅니다. |
### OpenClaw
오픈 소스 CLI 에이전트 오케스트레이터입니다. **모델은 에이전트 계층에 바인딩됩니다**(`openclaw agents add --model`) — 작업별로 재정의할 수 없으며, Multica에서 `--model`이나 `--system-prompt`를 전달할 수 없습니다.
| | |
|---|---|
| 데몬이 찾는 이름 | `openclaw` |
| 설치 | 프로젝트 [github.com/openclaw-org/openclaw](https://github.com/openclaw-org/openclaw)를 참고하세요(커뮤니티 유지 관리). |
| 인증 | OpenClaw의 문서에 따라 기반 모델 제공자를 구성하세요. |
### Pi (Inflection AI)
미니멀합니다. **세션 재개 방식이 특이합니다** — 재개 id가 문자열 id가 아니라 디스크에 있는 세션 파일의 경로입니다.
| | |
|---|---|
| 데몬이 찾는 이름 | `pi` |
| 설치 | Inflection의 CLI 문서 [pi.ai](https://pi.ai/)를 참고하세요. |
| 인증 | 공급사 문서에 따릅니다. |
### Antigravity (Google)
Google의 Antigravity CLI(`agy`)입니다. Google의 Antigravity 서비스와 짝을 이루며 Gemini 기반 모델을 실행합니다. 세션 재개는 `--conversation <id>`를 통해 작동하며, 데몬이 CLI 로그 파일에서 이를 캡처합니다. 모델 선택은 Antigravity CLI 자체 내부에서 관리됩니다 — Multica는 이 제공자에 대해 에이전트별 모델 선택기를 비활성화합니다. 스킬은 `.agents/skills/`에 기록됩니다(CLI가 Gemini CLI의 워크스페이스 스킬 레이아웃을 상속함 — [Antigravity 문서](https://antigravity.google/docs/gcli-migration) 참고).
| | |
|---|---|
| 데몬이 찾는 이름 | `agy` |
| 설치 | [antigravity.google/docs/cli-overview](https://antigravity.google/docs/cli-overview)의 공식 가이드를 따르세요. CLI는 미리 빌드되어 제공됩니다 — `agy install`을 한 번 실행하여 PATH와 셸 별칭을 설정하세요. |
| 인증 | `agy`를 대화형으로 한 번 실행하여 Google 계정 로그인을 완료하거나, Antigravity 데스크톱 앱을 통해 로그인하세요 — CLI는 GUI가 기록한 keyring 항목을 재사용합니다. |
| 비고 | CLI는 구조화된 이벤트 스트림이 아니라 stdout에 일반 어시스턴트 텍스트를 출력합니다. 중간의 "I will run X" 줄과 최종 응답 모두 텍스트로 Multica에 전달됩니다. |
## 설치한 후에
1. **바이너리가 `PATH`에 있는지 확인하세요.** 새 터미널을 열고 `which <name>`(예: `which claude`, `which cursor-agent`, `which kiro-cli`, `which agy`)을 실행하세요. 경로가 출력되면 데몬이 찾을 수 있습니다. 아무것도 출력되지 않으면 먼저 셸의 `PATH`를 수정하세요(전형적인 원인은 다시 로드되지 않은 셸별 rc 파일입니다).
2. **데몬을 재시작하세요.** `multica daemon restart`를 실행하거나 데스크톱 앱을 다시 실행하세요. 데몬은 시작 시에만 `PATH`를 스캔합니다.
3. **런타임 페이지를 확인하세요.** Multica UI의 **런타임** 페이지에 이제 `(워크스페이스 × 도구)` 조합별로 한 행씩 나열되어야 합니다. 행에 "offline"이라고 표시되면 [데몬과 런타임 → 런타임이 오프라인으로 표시될 때](/daemon-runtimes#when-a-runtime-is-marked-offline)를 참고하세요.
4. **온보딩으로 돌아가세요.** "런타임 연결" 단계는 폴링을 수행하며 몇 초 안에 새 런타임을 인식합니다 — 새로 고칠 필요가 없습니다.
## 문제 해결
- **`which`는 바이너리를 찾는데 데몬은 찾지 못합니다.** 데몬이 예전 `PATH`로 시작되었습니다. 재시작하세요.
- **바이너리는 존재하지만 실행에 실패합니다.** 터미널에서 도구 자체의 `--version`이나 `--help`를 한 번 실행하세요 — 여기서 발생하는 대부분의 실패는 인증 누락, 만료된 토큰, 또는 Node.js / 런타임 불일치입니다.
- **런타임 페이지에 행은 표시되지만 작업이 즉시 실패합니다.** 작업을 트리거하면서 `multica daemon logs -f`를 확인하세요. 데몬은 도구 자체의 오류 출력을 그대로 보여줍니다.
더 광범위한 증상은 [문제 해결 가이드](/troubleshooting)를 참고하세요.
## 다음
- [데몬과 런타임](/daemon-runtimes) — 감지, 하트비트, 오프라인 처리가 작동하는 방식
- [AI 코딩 도구 매트릭스](/providers) — 도구가 연결된 후의 기능 차이
- [에이전트 생성 및 구성](/agents-create) — 에이전트에 사용할 도구를 선택하고 작업 실행 시작하기

View File

@@ -0,0 +1,79 @@
---
title: 이슈와 프로젝트
description: 사람 또는 에이전트에게 할당할 수 있는 Multica의 핵심 작업 단위.
---
import { Callout } from "fumadocs-ui/components/callout";
이슈는 Multica에서 독립적인 작업 단위입니다 — 버그, 새 기능, 처리해야 할 어떤 일이든 될 수 있습니다. 모든 이슈에는 **제목**, **설명**(Markdown 지원), **상태**, **우선순위**, **담당자**가 있으며, 선택적으로 **프로젝트**에 속할 수 있습니다. Linear나 Jira를 사용해 보셨다면 동일한 형태입니다.
**Multica의 가장 큰 특징은 이슈의 담당자가 사람일 수도, [에이전트](/agents)일 수도 있다는 점입니다** — 여기서부터 시작하겠습니다.
## 에이전트에게 이슈 할당하기
이슈를 에이전트에게 [할당](/assigning-issues)하면 그 작업을 에이전트에게 넘기는 것입니다. 에이전트는 **자동으로 시작합니다** — 몇 초 안에 실행을 시작하고, 댓글로 진행 상황을 보고하며, 완료되면 상태를 done으로 전환합니다. 동료에게 일을 넘기는 것과의 유일한 차이는 에이전트는 오프라인이 되지 않고, 알림이 필요 없으며, 연중무휴 24시간 사용할 수 있다는 점입니다.
<Callout type="info">
에이전트의 정체성, 구성, 실행 위치에 대해서는 [에이전트](/agents)를 참고하세요.
</Callout>
비공개 에이전트는 워크스페이스 owner와 admin만 이슈에 할당할 수 있습니다. 역할 권한에 대해서는 [멤버와 역할](/members-roles)을 참고하세요.
## 상태
Multica에는 일곱 가지 상태가 있습니다. **어떤 상태든 다른 어떤 상태로도 바로 이동할 수 있습니다** — Multica는 워크플로를 강제하지 않으며, `backlog`에서 곧바로 `done`으로 건너뛰어도 막지 않습니다.
| 상태 | 의미 |
|---|---|
| `backlog` | 아직 일정에 없음 |
| `todo` | 일정이 잡혔고, 시작할 준비됨 |
| `in_progress` | 작업 중 |
| `in_review` | 리뷰 대기 중 |
| `done` | 완료됨 |
| `blocked` | 외부 요인으로 막힘 |
| `cancelled` | 취소됨 |
이슈가 에이전트에게 할당되면, 에이전트는 자동으로 상태를 `backlog` / `todo`에서 `in_progress`로 옮기고, 완료 시 `done`으로 옮깁니다. 언제든지 직접 변경할 수도 있습니다.
## 우선순위
우선순위에는 다섯 단계가 있으며, 기본 이슈 목록의 정렬에 사용됩니다:
| 우선순위 | 용도 |
|---|---|
| `No priority` | 아직 결정되지 않음 (기본값) |
| `Urgent` | 긴급 |
| `High` | 높음 |
| `Medium` | 중간 |
| `Low` | 낮음 |
## 이슈 번호
모든 이슈에는 워크스페이스 내에서 고유한 번호가 `<prefix>-<digits>` 형식으로 부여됩니다 — 예를 들어 `MUL-123`처럼요. 번호는 생성 시점에 시스템이 부여하며 **절대 변하지 않습니다**. [워크스페이스 → 이슈 번호](/workspaces#issue-numbers)를 참고하세요.
## 댓글
이슈 아래의 댓글 스레드는 협업이 일어나는 곳입니다 — 댓글에 답글을 달고, 사람이나 에이전트를 `@`로 멘션하고, 반응을 추가할 수 있습니다.
댓글에서 에이전트를 `@`로 멘션하면 **자동으로 트리거됩니다** — 이것은 "할당"과 더불어 에이전트를 시작하는 두 번째 방법입니다. [댓글과 멘션](/comments)과 [댓글에서 에이전트 멘션하기](/mentioning-agents)를 참고하세요.
## 이슈 삭제하기
<Callout type="warning">
이슈를 삭제하면 그 아래의 모든 댓글, 반응, 첨부 파일과 대기 중인 에이전트 작업이 **즉시** 삭제됩니다(실행 중인 작업은 취소됩니다). **되돌릴 수 없습니다.**
이슈를 단지 보이지 않게 하고 싶을 뿐이라면, **상태를 `cancelled`로 변경하는 것이 삭제보다 안전합니다** — 데이터가 남아 있어 나중에 다시 가져올 수 있습니다.
</Callout>
## 프로젝트
프로젝트는 여러 이슈를 하나로 묶는 컨테이너입니다. 이슈는 최대 하나의 프로젝트에 속하거나, 아예 어떤 프로젝트에도 속하지 않을 수 있습니다.
프로젝트에는 자체 **리더**가 있습니다 — **이슈의 담당자와 마찬가지로, 리더도 사람일 수도, 에이전트일 수도 있습니다**.
프로젝트를 삭제해도 **그 안의 이슈는 삭제되지 않습니다**: 해당 이슈들은 단지 프로젝트에서 분리되어 워크스페이스에 그대로 남습니다.
## 다음 단계
- [댓글과 멘션](/comments) — 이슈 아래에서 협업하기
- [에이전트](/agents) — "에이전트에게 할당"이 실제로 어떻게 작동하는지 이해하기

View File

@@ -0,0 +1,60 @@
---
title: 멤버와 역할
description: 워크스페이스의 세 가지 역할 — owner, admin, member — 가 각각 무엇을 할 수 있는지, 그리고 사람을 어떻게 초대하는지 설명합니다.
---
import { Callout } from "fumadocs-ui/components/callout";
[워크스페이스](/workspaces)에 속한 모든 사람은 역할을 가지며, 역할에 따라 무엇을 할 수 있는지가 결정됩니다. Multica에는 세 가지 역할이 있습니다. **owner**(워크스페이스의 소유자), **admin**, **member**입니다. [이슈](/issues) 생성, [댓글](/comments) 작성, [에이전트](/agents) 사용 같은 대부분의 일상 작업은 세 역할 모두 사용할 수 있습니다. **차이는 팀 관리 영역에 집중되어 있습니다.**
## 권한 한눈에 보기
아래 표는 팀 관리 작업에서 나타나는 가장 중요한 차이를 정리한 것입니다.
| 작업 | owner | admin | member |
|---|---|---|---|
| 새 admin 또는 member 초대 | ✓ | ✓ | ✗ |
| **새 owner 초대** | ✓ | ✗ | ✗ |
| admin 또는 member 강등 / 제거 | ✓ | ✓ | ✗ |
| **다른 owner 강등 / 제거** | ✓ | ✗ | ✗ |
| 워크스페이스 삭제 | ✓ | ✗ | ✗ |
**member는 누구도 초대할 수 없습니다** — 초대는 admin 등급의 권한입니다. **owner만 다른 사람을 owner로 승격할 수 있습니다** — admin은 member나 다른 admin을 승격하거나 강등할 수 있지만, 새 owner를 만들 수는 없습니다. 마찬가지로 admin은 member나 다른 admin을 제거할 수 있지만 **기존 owner는 건드릴 수 없습니다**. 핵심은 최고 등급을 이미 보유한 사람만이 그 등급을 부여할 수 있도록 하는 것입니다 — 권한은 위쪽으로 새어 나가지 않습니다.
<Callout type="info">
에이전트 가시성에는 "workspace"와 "private" 두 가지가 있습니다. private 에이전트는 owner와 admin만 이슈에 할당할 수 있습니다 — 이는 특정 사람들만 사용하도록 만든 구성을 보호하기 위함입니다. [에이전트](/agents)를 참고하세요.
</Callout>
## 새 멤버 초대하기
Multica는 이메일로 새 멤버를 초대합니다.
1. 워크스페이스 설정 페이지에서 **멤버 초대**를 클릭하고, 이메일을 입력한 뒤 역할을 선택합니다.
2. Multica가 고유 링크가 담긴 초대 이메일을 보냅니다.
3. 수신자가 링크를 클릭하고 로그인(또는 가입)한 뒤 **초대를 수락**하면 워크스페이스에 합류합니다.
초대받는 이메일은 **미리 Multica에 등록되어 있을 필요가 없습니다** — 계정이 없으면 초대를 수락할 때 자동으로 생성됩니다.
초대 이메일 전송이 실패하더라도(잘못된 주소, 메일 서비스 장애 등) 초대 기록은 그대로 유지됩니다. 워크스페이스 설정에서 이메일을 다시 보내거나, 초대 링크를 다른 경로로 공유할 수 있습니다.
초대는 **7일간 유효합니다**. 그 이후에 링크를 클릭하면 "만료됨" 메시지가 표시되며, 초대한 사람이 새로 보내야 합니다.
## 항상 최소 한 명의 owner 유지
모든 워크스페이스에는 **항상 최소 한 명의 owner가 있어야 합니다**. 이 제약은 두 가지 작업을 자동으로 차단합니다.
- 마지막 owner는 자신을 강등할 수 없습니다.
- 다른 owner나 admin은 마지막 owner를 제거할 수 없습니다.
<Callout type="warning">
당신이 마지막 owner이고 팀을 떠나려 한다면, **먼저 owner 역할을 다른 멤버에게 양도한 뒤** 워크스페이스를 떠나거나 넘기세요. 그렇지 않으면 작업이 거부됩니다.
</Callout>
## 멤버 제거하기
owner와 admin은 워크스페이스에서 다른 멤버를 제거할 수 있습니다. 제거된 멤버는 즉시 접근 권한을 잃습니다. 그 멤버가 만든 이슈, 댓글 등의 콘텐츠는 워크스페이스에 그대로 유지됩니다.
## 다음
- [이슈와 프로젝트](/issues) — 멤버가 작업하는 대상
- [댓글과 멘션](/comments) — 이슈 아래에서 협업하기

View File

@@ -0,0 +1,63 @@
---
title: "댓글에서 에이전트 @-멘션하기"
description: 댓글에서 @로 에이전트를 멘션해 살펴보게 하세요 — 담당자 변경도, 상태 변경도 없이, 할당보다 가볍습니다.
---
import { Callout } from "fumadocs-ui/components/callout";
[댓글](/comments)에서 [에이전트](/agents)를 `@`-멘션하는 것은 더 가벼운 트리거입니다 — **담당자 변경도, 상태 변경도 없이**, 그저 에이전트가 현재 [이슈](/issues)를 살펴보도록 살짝 알리는 것입니다. [**할당**](/assigning-issues)(에이전트를 담당자로 만들고 이슈를 넘기는 것)과 비교하면, @-멘션은 "이 부분 좀 봐줘", "다른 관점으로 분석해줘", "잠깐 끌어들여서 같이 논의하자" 같은 상황에 잘 맞습니다.
## 댓글에서 에이전트 멘션하기
멤버를 멘션하는 것과 동일합니다 — `@`를 입력해 피커를 열고 에이전트를 선택하세요. 댓글이 게시되면 Multica는 멘션된 각 에이전트에게 **그 댓글**을 트리거 컨텍스트로 삼아 즉시 `task`를 대기열에 넣습니다. 에이전트가 작업을 받으면 다음을 읽을 수 있습니다.
- 이슈 전체(설명 + 모든 과거 댓글)
- 트리거 댓글 자체 — 이번 실행의 시작점으로
`@mention` Markdown 문법, 피커, 그리고 `@all` 의미는 [**댓글**](/comments)에서 다룹니다.
<Callout type="info">
**댓글에서 [스쿼드](/squads)도 `@`-멘션할 수 있습니다.** 동일한 피커가 멤버 및 에이전트와 함께 스쿼드도 보여줍니다. 스쿼드를 선택하면 `[@SquadName](mention://squad/<uuid>)`이 삽입되고, 스쿼드의 **리더 에이전트**가 응답을 조율하도록 트리거됩니다 — 담당자와 상태는 그대로 유지됩니다.
</Callout>
## 할당과 어떻게 다른가
둘 다 에이전트를 일하게 만들지만, 동작 방식은 완전히 다릅니다.
| 항목 | 할당 | @-멘션 |
|---|---|---|
| `assignee` 변경 | ✓ | ✗ |
| `status` 변경 | ✗ | ✗ |
| `task` 대기열 추가 | 즉시(백로그가 아닌 경우) | 즉시 |
| 트리거 댓글 ID | 선택 사항 | 항상 현재 댓글을 포함 |
| 한 번의 동작당 대상 에이전트 | 1명(담당자 한 명) | 다수(댓글 하나에서 여러 명을 @ 가능) |
| 우선순위 | 이슈에서 상속 | 이슈에서 상속 |
판단 기준은 간단합니다. **에이전트가 "지금부터 이 이슈를 책임지길" 원하면 할당을, "현재 컨텍스트를 한번 살펴보길" 원하면 @-멘션을 사용하세요.**
## 여러 에이전트를 @ 하면 어떻게 되는가
댓글 하나에서 여러 에이전트를 @-멘션하면, 각 에이전트는 자신의 런타임에서 독립적인 `task`를 대기열에 받습니다 — **서로를 막지 않고 병렬로 실행됩니다**.
같은 이슈에서 어떤 에이전트가 이미 `queued` 또는 `dispatched` 상태의 `task`를 가지고 있다면(예: 방금 멘션되었고 아직 시작하지 않은 경우), 이번 멘션은 **중복 제거**되어 중복 `task`가 대기열에 추가되지 않습니다. 중복 제거는 **단일 댓글 단위로** 적용됩니다 — 몇 초 간격으로 게시된 서로 다른 두 댓글이 모두 같은 에이전트를 @ 하면, 둘 다 `task`를 대기열에 넣습니다.
<Callout type="warning">
**댓글을 편집해 @를 추가해도 다시 트리거되지 않습니다.** 게시한 뒤에야 `@agent`를 추가해야겠다고 떠올렸다면, 편집으로 넣은 `@`는 표시되는 내용만 바꿀 뿐 — 그 에이전트에게 새 `task`를 **전달하지 않습니다**. 트리거하려면 새 댓글을 게시하거나 이슈를 그 에이전트에게 할당하세요.
</Callout>
## `@all`은 어떤 에이전트도 트리거하지 않는다
`@all`로 전체를 호출할 때, **워크스페이스 멤버만 인박스에 들어가며 — 에이전트는 `@all` 확장에 포함되지 않습니다.** 이는 의도된 설계입니다. 에이전트는 인박스 알림을 받지 않으므로 `@all`은 에이전트에게 아무 의미가 없습니다. 에이전트를 일하게 하려면 이름으로 직접 멘션하세요.
## 에이전트가 자기 자신을 @-멘션해도 루프에 빠지지 않는다
에이전트는 실행 중에 댓글을 게시할 수 있으며, 그 댓글에는 `@mention`이 포함될 수 있습니다. Multica에는 하드코딩된 가드가 있습니다. **댓글 작성자가 `@` 멘션의 대상 에이전트와 동일하다면, 그 멘션은 건너뜁니다** — "에이전트 A가 에이전트 A를 @ → 새 task → 다시 에이전트 A를 @" 같은 무한 루프는 발생하지 않습니다.
이 가드는 **직접적인 자기 참조만 차단합니다.** 에이전트 A가 에이전트 B를 @-멘션하는 것은 정상적으로 동작하며, 그 후 B가 응답에서 A를 @-멘션하면 A가 다시 트리거됩니다 — 다시 말해 **간접 재귀는 차단되지 않습니다**. 에이전트 지침을 작성할 때 여러 에이전트가 서로를 @-멘션해 순환을 이루지 않도록 주의하세요.
## 다음
- [**스쿼드**](/squads) — 스쿼드를 `@`-멘션하면 리더가 질문을 적절한 멤버에게 라우팅합니다
- [**채팅**](/chat) — 이슈 밖에서의 일대일 대화
- [**오토파일럿**](/autopilots) — 에이전트가 일정에 따라 자동으로 작업을 시작하게 하세요
- [**댓글**](/comments) — `@mention` 문법, 피커, 그리고 `@all` 의미

View File

@@ -0,0 +1,46 @@
{
"title": "Documentation",
"pages": [
"index",
"how-multica-works",
"cloud-quickstart",
"self-host-quickstart",
"---워크스페이스 & 팀---",
"workspaces",
"members-roles",
"issues",
"projects",
"comments",
"project-resources",
"---에이전트---",
"agents",
"agents-create",
"skills",
"squads",
"---에이전트 실행 방식---",
"daemon-runtimes",
"install-agent-runtime",
"tasks",
"providers",
"---에이전트와 협업---",
"assigning-issues",
"mentioning-agents",
"chat",
"autopilots",
"---인박스---",
"inbox",
"---연동---",
"github-integration",
"---자체 호스팅 & 운영---",
"environment-variables",
"auth-setup",
"troubleshooting",
"---참고---",
"cli",
"auth-tokens",
"desktop-app",
"mobile-app",
"---개발자---",
"developers"
]
}

View File

@@ -0,0 +1,82 @@
---
title: 모바일 앱 (iOS)
description: 아직 App Store에 없는 오픈소스 Multica iOS 앱을 직접 본인 iPhone에 빌드하는 방법.
---
import { Callout } from "fumadocs-ui/components/callout";
Multica의 iOS 클라이언트는 오픈소스이며, web, desktop, 백엔드와 함께 [메인 저장소](https://github.com/multica-ai/multica)에 들어 있습니다. 아직 App Store에는 없으며, 그 상황이 바뀌기 전까지는 iPhone에서 사용하려는 사람이 직접 소스에서 빌드합니다. 빌드는 처음에는 약 10~20분, 그 이후에는 약 2분이 걸리며, [multica.ai](https://multica.ai)와 동일한 백엔드와 통신하므로 기존 계정이 그대로 동작합니다.
<Callout type="info">
이 페이지는 **개인 사용자**를 위한 것입니다. 앱 개발자는 저장소의 [`apps/mobile/README.md`](https://github.com/multica-ai/multica/blob/main/apps/mobile/README.md)를 읽어야 합니다 — dev / staging 변형과 전체 스크립트 목록을 다룹니다.
</Callout>
## 필요한 것
- Xcode가 설치된 **Mac** (App Store에서 무료로 받을 수 있습니다).
- Xcode → Settings → Accounts에 추가한 무료 **Apple ID**. 유료 Apple Developer Program 계정은 선택 사항이며, 7일 서명 기간을 1년으로 늘려줄 뿐입니다 — 아래 [7일 제한](#7-day-signing-limit)을 참고하세요.
- USB 케이블로 연결되어 있고 [Developer Mode가 활성화된](https://docs.expo.dev/guides/ios-developer-mode/) **iPhone** (설정 → 개인 정보 보호 및 보안 → 개발자 모드).
- 체크아웃한 Multica 소스 코드:
```bash
git clone https://github.com/multica-ai/multica.git
cd multica
pnpm install
```
이 목록에서 빠진 것이 있다면, Expo의 [Set up your environment](https://docs.expo.dev/get-started/set-up-your-environment/)를 따라 진행하세요 (**Development build → iOS Device**를 선택). 저장소 체크아웃을 제외한 모든 것에 대한 공식 설정 가이드입니다.
## 빌드하기
명령어 하나:
```bash
pnpm ios:mobile:device:prod:release
```
Xcode는 Apple ID가 자동으로 소유하는 "Personal Team"으로 빌드에 서명합니다 — 이 team은 어떤 Apple ID로든 처음 Xcode에 로그인할 때 조용히 생성되므로, 무언가를 설정한 기억이 없더라도 이미 존재합니다. 이것은 **Release 빌드**입니다: Metro 의존성이 없고, 스플래시 화면 → 앱으로 이어지며, App Store에서 설치한 것과 똑같습니다.
첫 빌드는 CocoaPods를 다운로드하고 React Native를 소스에서 컴파일합니다 — 10~20분 정도 예상하세요. 이후 빌드는 Xcode의 캐시를 재사용합니다.
일반적인 경로는 이것으로 끝입니다. 서명이 실패하면 [문제 해결](#troubleshooting)로 이동하세요.
## 7일 서명 제한
무료 Apple ID는 빌드를 **7일** 동안 서명합니다. 그 이후에는 앱이 iPhone에서 실행을 거부하고 "untrusted developer" 오류를 표시합니다. Mac에 다시 연결하고 동일한 명령어를 다시 실행하여 재서명하세요 — 데이터는 앱이 아니라 백엔드에 있으므로 그대로 유지됩니다.
이를 연장하는 유일한 방법은 **Apple Developer Program 계정**입니다 ([developer.apple.com](https://developer.apple.com)에서 연 $99). 그러면 서명이 갱신 사이 1년 동안 유효하며, TestFlight를 통해 다른 기기에 배포할 수도 있습니다.
## 업데이트
아직 자동 업데이트는 없습니다. Multica 코드베이스가 앞으로 나아가면, pull한 후 다시 빌드하세요:
```bash
git pull
pnpm install
pnpm ios:mobile:device:prod:release
```
Xcode가 네이티브 컴파일을 캐시하므로 이후 빌드는 빠릅니다.
## 아직 App Store에 없는 이유
iOS 앱은 여전히 빠르게 움직이고 있습니다 — 팀은 지금 App Store 심사 주기보다 출시 후 반복 개선을 선호합니다. 정식 App Store 출시 전에 TestFlight 베타가 가장 유력한 다음 단계입니다. 그때까지는 위의 직접 빌드 방식이 iOS에서 Multica를 사용하는 유일한 방법입니다.
TestFlight가 열릴 때 알림을 받고 싶다면 [GitHub 저장소](https://github.com/multica-ai/multica)를 watch하세요.
## 문제 해결
**"No matching provisioning profiles found"** — Xcode가 기본 번들 id `ai.multica.mobile`를 Apple ID로 서명하기를 거부합니다. 드물지만, 누군가 Apple 개발자 포털에서 해당 접두사를 등록했다면 발생합니다. 본인이 제어하는 아무 역방향 도메인(`com.yourname.multica`이면 충분합니다)을 골라 export한 후 다시 실행하세요:
```bash
export EXPO_BUNDLE_IDENTIFIER_PROD=com.yourname.multica
pnpm ios:mobile:device:prod:release
```
id 자체는 어떤 의미가 있을 필요가 없습니다 — Apple은 단지 다른 team이 점유하지 않았다는 것만 요구합니다.
**"Could not launch &lt;app&gt;" / "Untrusted Developer"** — 7일 제한에 도달했거나(빌드를 다시 실행하세요), iPhone에서 개발자 프로필을 수동으로 신뢰해야 합니다: 설정 → 일반 → VPN 및 기기 관리 → Apple ID를 탭 → 신뢰.
**`Pod install`에서 빌드가 멈추거나 끝없이 컴파일됨** — 첫 빌드는 CocoaPods가 의존성을 다운로드하고 Xcode가 React Native를 소스에서 컴파일하기 때문에 실제로 10~20분이 걸립니다. 이후 빌드는 훨씬 빠릅니다.
**앱이 백엔드에 연결할 수 없음** — `apps/mobile/.env.production`이 수정되지 않았는지 확인하세요(기본값은 `EXPO_PUBLIC_API_URL=https://api.multica.ai`입니다). 변경했다면 `git checkout apps/mobile/.env.production`으로 복원하세요.

View File

@@ -0,0 +1,262 @@
---
title: 프로젝트 리소스
description: 타입이 지정된 포인터(Git 저장소, 로컬 디렉터리, 그리고 추후 더 많은 종류)를 프로젝트에 연결해, 에이전트가 범위가 한정된 컨텍스트로 가져갈 수 있게 합니다.
---
**프로젝트 리소스(Project Resource)** 는 타입이 지정된 포인터입니다 — Git 저장소 URL, 본인 기기의 경로, 내일이면 Notion 페이지까지 — 이것을 [프로젝트](/workspaces)에 연결합니다. [에이전트](/agents)가 해당 프로젝트 안의 이슈에 대해 실행될 때, 데몬은 프로젝트의 리소스 목록을 에이전트의 작업 디렉터리와 [메타 스킬](/skills) 프롬프트에 자동으로 기록합니다.
그 결과: 에이전트는 어떤 저장소를 체크아웃해야 하는지(또는 어떤 로컬 디렉터리에서 작업해야 하는지), 그리고 이 프로젝트의 "주요 참고 자료"가 무엇인지를, 아무도 컨텍스트를 이슈 본문에 복사해 붙여 넣지 않아도 알게 됩니다.
## 멘탈 모델
프로젝트는 더 이상 단순한 라벨이 아닙니다. 작은 **리소스 컨테이너**입니다:
- 프로젝트는 0..N개의 **리소스**를 가집니다.
- 리소스는 `resource_type`(예: `github_repo`, `local_directory`)과 `resource_ref`(`resource_type`에 따라 타입이 정해지는 JSON 페이로드)를 가집니다.
- 새 리소스 타입을 추가하려면 문자열 하나 + 핸들러 하나만 추가하면 됩니다. **스키마 마이그레이션도, 프런트엔드 재작성도 필요 없습니다.**
이 형태는 의도적인 것입니다 — Multica가 에이전트 제공자에 이미 사용하는 것과 동일한 패턴입니다: `type` 판별자 하나와 타입이 지정된 페이로드. 스키마를 안정적으로 유지하므로, 추후 "Notion 페이지", "Google Doc", "업로드된 파일", "외부 URL"을 추가하는 것은 작고 점진적인 변경이 됩니다.
오늘날 두 가지 리소스 타입이 제공됩니다: [`github_repo`](#resource-type-github_repo)(작업마다 격리된 워크트리로 클론)와 [`local_directory`](#resource-type-local_directory)(특정 데몬 기기의 폴더 안에서 직접 실행).
## 리소스 타입: `github_repo`
기본 리소스 타입입니다 — 작업마다 격리된 워크트리로 체크아웃됩니다:
```json
{
"resource_type": "github_repo",
"resource_ref": {
"url": "https://github.com/owner/repo",
"default_branch_hint": "main"
}
}
```
`default_branch_hint`는 선택 사항입니다 — 지정하면 데몬이 이를 메타 스킬에 노출하므로, 에이전트가 어떤 브랜치를 기준으로 작업할지 알게 됩니다.
## 리소스 타입: `local_directory`
작업마다 다시 클론하기 곤란한 저장소 — 수 기가바이트짜리 게임 체크아웃, 대형 monorepo, 또는 작업마다 워크트리를 만드는 모델이 번거로운 모든 프로젝트 — 의 경우, 프로젝트는 대신 **특정 [데몬](/daemon-runtimes) 기기에 있는 기존 디렉터리**를 가리킬 수 있습니다. 에이전트는 클론도, 복사도, 워크트리도 없이 **그 폴더 안에서 직접** 실행됩니다.
```json
{
"resource_type": "local_directory",
"resource_ref": {
"local_path": "/Users/me/code/big-game",
"daemon_id": "0001234e-…",
"label": "main checkout"
}
}
```
`github_repo`와 비교한 트레이드오프는 의도적입니다: 바인딩된 데몬만 해당 디렉터리에 대한 작업을 가져갈 수 있고, 같은 디렉터리에 대한 작업은 병렬이 아니라 **직렬로** 실행됩니다. 그 대가로 기존 체크아웃, 기존 브랜치, 기존 더티 상태를 그대로 유지합니다 — Multica는 절대 다시 클론하지 않습니다.
### `github_repo` 대신 `local_directory`를 선택할 때
| 고려 사항 | `github_repo`(워크트리) | `local_directory` |
| --- | --- | --- |
| 작업당 체크아웃 비용 | 새 클론 + 워크트리 | 없음 — 에이전트가 제자리에서 실행 |
| 같은 저장소의 동시성 | 여러 작업 병렬 | 디렉터리당 한 번에 하나 |
| 브랜치 / 더티 상태 | 작업마다 기본 브랜치에서 새 브랜치를 받음 | 디렉터리가 현재 가진 그대로 |
| 실행 가능한 위치 | 모든 데몬 | 정확히 하나의 데몬(바인딩된 것) |
| 디스크 사용량 | 작업당 워크트리 하나 | 오버헤드 0 — 기존 폴더 사용 |
다음 중 **하나라도** 해당하면 `local_directory`를 선택하세요:
1. **다시 클론하는 비용이 지나치게 큰 경우** — 수 기가바이트짜리 게임 체크아웃, 무거운 LFS 자산을 가진 monorepo, 또는 작업당 `git clone`이 실제 작업을 압도하는 모든 경우. 동시성을 클론 없는 실행과 맞바꾸는 것입니다.
2. **변경이 세밀하고, 변경되는 즉시 로컬에서 리뷰하고 싶은 경우** — 단일 컴포넌트를 반복적으로 다듬고 있고, 몇 분마다 에이전트의 편집과 본인의 에디터를 오가고 싶으며, `~/multica_workspaces/`에서 파헤쳐야 하는 작업당 워크트리보다 기존 체크아웃을 진실의 원천으로 삼고 싶은 경우입니다.
두 경우 모두에서 받아들이는 트레이드오프는 동일합니다: **이 버전은 파일 단위 쓰기 잠금을 제공하지 않습니다.** 디렉터리당 직렬 게이트(같은 폴더에서 한 번에 하나의 작업)가 서로 다른 두 이슈의 에이전트가 동시에 같은 파일을 건드리는 것을 막는 유일한 보호 장치입니다. 두 이슈의 에이전트를 같은 `local_directory`로 향하게 하면, 그 작업들은 병렬화되지 않고 대기열에 들어갑니다 — 이는 의도된 동작입니다. 같은 코드베이스에서 진짜 병렬성이 필요하다면 `github_repo`를 계속 사용하세요.
### 로컬 디렉터리 연결하기
폴더 선택기는 **Desktop 앱**에만 있습니다 — 웹 앱은 OS 경로를 읽을 방법이 없으므로 "로컬 디렉터리 추가" 버튼이 거기서는 숨겨져 있습니다. Desktop에서는:
1. 프로젝트를 엽니다 → **Resources** 패널.
2. **로컬 디렉터리 추가**를 클릭합니다. 네이티브 폴더 선택기가 열립니다.
3. 폴더를 선택합니다. 그 경로는 **이 Desktop 설치가 현재 등록한 데몬**에 바인딩됩니다 — 리소스 레코드에는 경로와 해당 데몬의 ID가 함께 저장됩니다.
Desktop에서는 이 기기의 데몬이 오프라인이거나, 프로젝트에 이미 이 데몬에 바인딩된 `local_directory`가 있을 때, 버튼은 계속 보이되 **힌트와 함께 비활성화**됩니다 — 그래서 *왜* 사용할 수 없는지 알 수 있습니다. (웹 앱에서는 애초에 폴더 선택기가 전혀 없으므로 버튼이 완전히 숨겨집니다.) 다른 기기의 디렉터리를 바인딩하려면, 그 기기에 Desktop을 설치하고 거기서 리소스를 추가하세요.
CLI에서도 가능합니다(데몬 ID를 직접 제공하기만 하면 웹 전용 환경에서도 동작합니다):
```bash
multica project resource add <project-id> \
--type local_directory \
--local-path /Users/me/code/big-game \
--daemon-id <daemon-uuid> \
--ref-label "main checkout" # optional
multica project resource update <project-id> <resource-id> \
--local-path /Users/me/code/big-game-new
```
`--daemon-id`는 `multica daemon list`에서 얻을 수 있습니다. CLI는 페이로드를 직접 전달하고 싶을 때를 위한 범용 `--ref '<json>'` 탈출구도 받습니다.
### 경로 규칙
연결하는 경로는 연결 시점 검증과 작업별 검증을 모두 통과해야 합니다. 둘 다 리소스를 소유한 데몬이 강제합니다 — 서버는 JSON만 저장합니다. 어떤 규칙이라도 위반하는 경로는 타입이 지정된 오류와 함께 작업을 실패시키고, 디렉터리는 손대지 않은 채로 남겨 둡니다:
- 반드시 **절대 경로**여야 합니다.
- 반드시 **존재**해야 하고 **디렉터리**여야 합니다(파일, 파일을 가리키는 symlink, 또는 디바이스 노드가 아니어야 합니다).
- 데몬 프로세스가 **읽기와 쓰기**가 가능해야 합니다.
- 시스템 루트나 사용자 프로필 전체일 수 없습니다 — `/`, `/Users`, `/home`, `/root`, `/etc`, `/tmp`, `/var`, `/usr`, `/opt`, `/Users/Shared`, 본인의 `$HOME`, 임의의 Windows 드라이브 루트(`C:\`, `D:\`, …), 또는 `C:\Users` / `C:\ProgramData` / `C:\Program Files` / `C:\Program Files (x86)` / `C:\Windows`.
- 위 중 어느 것으로든 해석되는 symlink는 거부되며, OS가 별칭 처리하는 경로의 정규형(canonical form)도 마찬가지입니다(예: macOS에서 `/private/tmp`를 입력하는 것은 `/tmp`와 동일하게 거부됩니다).
블랙리스트는 의도적으로 공격적입니다 — 홈 디렉터리를 선택하면 Multica의 런타임 파일이 계정의 루트에 놓이게 되는데, 이는 절대 원하는 결과가 아닙니다. 대신 하위 폴더(보통은 실제 프로젝트 체크아웃)를 선택하세요.
### (프로젝트, 데몬)당 하나
프로젝트는 **데몬당 최대 하나의 `local_directory`**만 가질 수 있습니다. 같은 데몬에 두 번째를 추가하려 하면 API가 `409`를 반환합니다. Desktop 버튼은 한도에 이미 도달하면 스스로 숨겨지고, 이유를 설명하는 툴팁을 표시합니다.
서로 다른 데몬은 독립적입니다 — 공유 프로젝트는 팀원의 기기마다 하나씩 `local_directory`를 가질 수 있으며, 각각은 같은 프로젝트를 서로 다른 호스트의 서로 다른 폴더에 바인딩합니다. 데몬이 작업을 가져갈 때는 자신의 ID와 일치하는 행을 고르고 나머지는 무시합니다.
### 리소스 타입 혼용, 그리고 여러 개의 `local_directory` 리소스
실제로 등장하는 두 가지 교차 리소스 구성이 있습니다:
- **같은 프로젝트에 `github_repo` + `local_directory`.** 일치하는 `local_directory` 바인딩을 가진 데몬에서는 로컬 디렉터리가 **우선**합니다: 에이전트는 당신의 폴더에서 실행되며, 데몬은 그 작업을 위해 `github_repo` 워크트리를 생성하거나 사용하지 않습니다. (워크스페이스별 저장소 캐시는 평소처럼 동기화될 수 있는데, 이는 이 작업의 작업 트리와는 무관한 백그라운드 동작입니다.) `github_repo` URL은 참고용으로 `.multica/project/resources.json`과 에이전트의 `## Repositories` 섹션에 여전히 나타나지만, 에이전트가 편집하는 작업 트리는 워크트리가 아니라 당신의 로컬 디렉터리입니다. 이 프로젝트에 대한 `local_directory` 행이 **없는** 데몬(다른 기기이거나, 그 팀원이 하나를 연결하기 전)에서는, 작업이 평소의 `github_repo` 워크트리 흐름으로 폴백합니다. 사실상 로컬 디렉터리는 워크트리 경로에 대한 데몬별 재정의입니다.
- **같은 프로젝트에 두 개의 `local_directory` 리소스.** 각 `local_directory`는 정확히 하나의 데몬에 바인딩되므로, 이는 서로 다른 두 기기 사이에서만 발생합니다(API는 연결 시점에 같은 데몬에 두 개를 거부합니다, 위 참조). 작업은 어느 데몬이 로컬 디렉터리를 가졌는지가 아니라 에이전트의 런타임 할당에 따라 라우팅됩니다: 작업은 수신 에이전트의 런타임을 소유한 데몬에 도착하고, 그 데몬은 자신의 ID와 일치하는 `local_directory` 행을 고르고 나머지는 무시합니다. 로드 밸런싱은 없습니다 — 특정 기기가 작업을 실행하게 하려면, 그 기기의 런타임에 바인딩된 에이전트를 디스패치하세요.
다른 곳에 하나가 바인딩된 프로젝트에 대해 `local_directory` 행이 없는 데몬은 **차단되지 않습니다** — 그 작업들은 단순히 프로젝트의 다른 리소스(보통 `github_repo` 폴백)를 통해 진행됩니다. `local_directory`는 바인딩된 데몬에 대해서만 의미가 있습니다.
### 로컬 디렉터리에 대해 작업 실행하기
프로젝트가 수신 데몬에 바인딩된 `local_directory`를 가진 이슈에서 작업이 디스패치되면, 데몬은:
1. 경로를 다시 검증합니다(위의 규칙).
2. symlink로 해석된 실제 경로를 키로 하여 디렉터리당 잠금을 획득합니다 — 그래서 같은 폴더로 향하는 두 경로(하나는 symlink 경유, 하나는 직접)도 여전히 직렬화됩니다.
3. 에이전트의 `CLAUDE.md` / `AGENTS.md`(그리고 `.multica/project/resources.json`)를 **사용자의 디렉터리 안에** 기록합니다. 에이전트는 당신이 직접 그 폴더를 연 것과 똑같이 그곳에서 작업합니다.
4. Multica의 런타임 산출물(`output/`, `logs/`, `.gc_meta.json`)은 사용자의 디렉터리 **바깥**의 별도 envRoot에 둡니다.
같은 디렉터리에 대한 두 번째 작업이 첫 번째 작업이 실행 중일 때 도착하면, **로컬 디렉터리 대기 중(Waiting for local directory)** 상태로 멈춥니다. 이 상태는 작업이 있는 모든 곳에서 보입니다 — 채팅 작업 알약(pill), 에이전트 배너, 실행 로그, 활동 표시기 — 그리고 멈춘 작업은 에이전트의 "대기 중" 존재로 집계됩니다. 멈춘 작업을 취소하면 그 슬롯이 즉시 해제됩니다. 실행 중인 작업을 취소하면 다음 작업이 승격됩니다.
이 대기는 타임아웃이 아닙니다 — 멈춘 작업은 잠금이 해제되거나 사용자 / 에이전트가 취소할 때까지 멈춰 있습니다.
### Multica가 당신의 디렉터리에서 건드리는 것과 건드리지 않는 것
- **기록합니다**: `CLAUDE.md` / `AGENTS.md`(또는 에이전트 제공자에 해당하는 등가물)와 `.multica/project/resources.json`을 디렉터리 루트에, 그래서 에이전트가 메타 스킬과 리소스 목록을 갖게 합니다. 커밋되기를 원하지 않으면 이것들을 `.gitignore`에 추가하세요.
- **기록합니다**: 에이전트가 결정하는 모든 코드 편집을 — 당신이 직접 로컬에서 에이전트를 실행한 것과 정확히 같은 방식으로.
- **절대 물리적으로 삭제하지 않습니다**: 디렉터리나 그 안의 어떤 것도. 가비지 컬렉션은 경로를 인식합니다: `local_directory` envRoot의 경우 `workspacesRoot` 아래의 자체 `output/`과 `logs/`만 정리하며, 사용자의 디렉터리는 출입 금지로 취급합니다.
### v1 제한 사항(후속 작업에서 좁혀질 예정)
첫 릴리스는 의도적으로 `github_repo`보다 더 날카로운 모서리를 가지고 출시됩니다. 이 목록은 시간이 지나며 줄어들 것으로 예상하세요 — 여기 문서화된 것은 오늘 사실인 내용입니다:
- **자동 브랜치 전환 없음.** 에이전트는 당신이 체크아웃해 둔 브랜치에서 실행됩니다. 중요하다면 디스패치 전에 브랜치를 전환하세요.
- **더티 트리 보호나 자동 커밋 없음.** 커밋되지 않은 변경은 에이전트에게 보이고, 제자리에서 수정될 수 있으며, stash되지 않습니다. 디렉터리를 실제 작업 트리로 취급하고 위험한 실행 전에 커밋하세요.
- **자동 PR 없음.** 작업이 끝나면 변경은 만들어진 브랜치 그대로 남아 있습니다 — 아무것도 push되지 않고 PR도 열리지 않습니다. 준비되면 직접 push하고 PR을 여세요.
- **`waiting_local_directory`는 상태를 보여 주지만 보유자는 보여 주지 않습니다.** 배지는 작업이 멈췄다고 알려 줍니다. 어떤 작업이나 어떤 파일 경로가 현재 디렉터리를 보유하고 있는지는 드러내지 않습니다.
이것들은 로컬 디렉터리 작업의 에이전트 작업 수명 주기 후속 항목으로 추적됩니다. 그것이 출시되기 전까지는 `local_directory`를 "에이전트가 당신의 폴더에서, 당신이 하는 것과 같은 방식으로 실행된다"로 취급하세요.
## 프로젝트 생성 시 저장소 연결하기
**Web** 또는 **Desktop** 앱에서 *새 프로젝트*를 열면, 이제 상태 / 우선순위 / 리드 옆에 **Repos** 알약(pill)이 표시됩니다. 워크스페이스에 바인딩된 저장소를 선택하면(또는 임시 URL을 붙여 넣으면), 프로젝트가 생성되는 순간 그것들이 `github_repo` 리소스로 연결됩니다.
**CLI**에서는:
```bash
# Create + attach in one shot. The server attaches resources in the same
# transaction as the project create — invalid resources roll back the whole
# operation, so you never end up with a project that has half its resources.
multica project create \
--title "Agent UX 2026" \
--repo https://github.com/multica-ai/multica
# Manage resources later
multica project resource list <project-id>
multica project resource add <project-id> --type github_repo --url <url>
multica project resource remove <project-id> <resource-id>
# Generic escape hatch for any resource_type the server understands —
# no CLI change needed when a new type ships:
multica project resource add <project-id> \
--type notion_page \
--ref '{"page_id":"…","title":"…"}'
```
`--repo`는 반복할 수 있습니다. 각 값은 별도의 `github_repo` 리소스로 연결됩니다.
## 런타임에 에이전트가 보는 것
데몬이 프로젝트 안의 이슈를 위해 에이전트를 생성하면, 두 가지 일이 일어납니다:
### 1. `.multica/project/resources.json`
API 응답의 구조화된 패스스루(pass-through)로, 에이전트의 작업 디렉터리에 기록됩니다:
```json
{
"project_id": "…",
"project_title": "Agent UX 2026",
"resources": [
{
"id": "…",
"resource_type": "github_repo",
"resource_ref": {
"url": "https://github.com/multica-ai/multica",
"default_branch_hint": "main"
}
}
]
}
```
스킬, 헬퍼 스크립트, 또는 에이전트 자신이 이번 실행의 *정확한* 리소스 집합이 필요할 때 이 파일을 파싱할 수 있습니다.
### 2. 메타 스킬 프롬프트의 "Project Context" 섹션
에이전트의 `CLAUDE.md` / `AGENTS.md`(제공자에 따라 다름)에는 이제 사람이 읽을 수 있는 요약이 포함됩니다:
```
## Project Context
This issue belongs to **Agent UX 2026**.
Project resources (also written to `.multica/project/resources.json`):
- **GitHub repo**: https://github.com/multica-ai/multica (default branch: `main`)
Resources are pointers — open them only when relevant to the task. For
`github_repo` resources, use `multica repo checkout <url>` to fetch the code.
```
이 텍스트는 의도적으로 최소한입니다. 전체 페이로드는 디스크에 있고, 프롬프트는 에이전트가 프로젝트가 존재한다는 것과 무엇이 연결되었는지를 알도록 방향만 잡아 줍니다.
### 실패 모드
리소스 가져오기는 **best-effort**입니다. API 호출이 실패하면 프롬프트에서 프로젝트 섹션이 생략되고 파일도 기록되지 않지만, 작업은 여전히 시작됩니다. 에이전트는 누락된 프로젝트 컨텍스트 때문에 멈추는 일이 절대 없습니다.
## 새 리소스 타입 추가하기
이 추상화의 핵심은 새 타입이 저렴하다는 것입니다. 전체 경로는:
1. **서버 검증기**(`server/internal/handler/project_resource.go`) — `validateAndNormalizeResourceRef`에 새 페이로드를 파싱하고 정규화하는 case를 추가합니다.
2. **데몬 메타 스킬 포매터**(`server/internal/daemon/execenv/runtime_config.go`) — `formatProjectResource`에 case를 추가해, 에이전트 프롬프트가 새 타입을 읽기 좋은 글머리 기호로 렌더링하게 합니다.
3. **TypeScript 타입**(`packages/core/types/project.ts`) — `ProjectResourceType`를 확장하고 페이로드 인터페이스를 추가합니다.
4. **UI 렌더러**(`packages/views/projects/components/project-resources-section.tsx`) — `ResourceRow`에 새 타입에 대한 case를 추가합니다.
**스키마 마이그레이션도, 새 sqlc 쿼리도, 새 엔드포인트도, 그리고 CLI 변경도 없습니다** — CLI의 범용 `--ref '<json>'` 플래그가 검증기가 이해하는 모든 페이로드를 받으므로, 새 타입에 대한 첫날 지원은 순전히 위 네 단계입니다. (나중에 타입별 CLI 단축 명령을 *선택적으로* 추가할 수 있지만, 필수는 아닙니다.)
같은 `project_resource` 테이블과 같은 세 개의 CRUD 호출이 모든 타입을 처리합니다.
## 워크스페이스 저장소 vs. 프로젝트 저장소
에이전트에게 보이는 저장소 목록(`CLAUDE.md` / `AGENTS.md`의 `## Repositories` 블록)은 데몬의 클레임 핸들러가 다음 우선순위로 선택합니다:
- **프로젝트가 최소한 하나의 `github_repo` 리소스를 가짐** → 그 저장소만 에이전트에게 노출됩니다. 워크스페이스에 바인딩된 저장소는 의도적으로 숨겨, 에이전트가 이 이슈에 어느 것이 속하는지 추측하지 않아도 되게 합니다.
- **프로젝트가 `github_repo` 리소스를 갖지 않음(또는 이슈가 프로젝트에 속하지 않음)** → 이전처럼 워크스페이스의 저장소 목록으로 폴백합니다.
이는 에이전트의 작업 집합을 좁게 유지합니다: 프로젝트가 저장소를 명확히 밝히면 그것이 권위 있는 답입니다. `.multica/project/resources.json`의 구조화된 리소스 목록은 항상 전체 집합을 담으므로, 모든 것을 검사하려는 스킬은 여전히 그렇게 할 수 있습니다.
데몬은 체크아웃 측에서도 이를 반영합니다: 프로젝트 범위의 `github_repo` URL을 가진 작업이 도착하면, 그 URL들은 에이전트가 생성되기 전에 워크스페이스별 허용 목록에 병합되고 *동시에* 로컬 저장소 캐시에 동기화됩니다. 그래서 워크스페이스 수준에서 바인딩되지 않은 프로젝트 저장소 URL도 여전히 `multica repo checkout`의 유효한 인자가 됩니다 — 데몬은 그것을 "구성되지 않음"으로 거부하지 않습니다. 허용 목록 분리는 내부적입니다: 워크스페이스에 바인딩된 URL과 작업 범위의 URL은 별도로 추적되므로, 워크스페이스 저장소 새로 고침이 실행 도중에 프로젝트 URL을 실수로 취소하는 일이 없습니다.
## 여기서 의도적으로 범위에 **포함하지 않은** 것
- **프로젝트 간 공유.** 오늘날 각 리소스는 정확히 하나의 프로젝트에만 존재합니다.
- **스킬별 리소스 범위 지정.** 모든 리소스는 에이전트 실행의 모든 스킬에 보입니다. 타입 인식 필터링은 후속 작업입니다.
- **캐싱 / 동기화.** `github_repo`는 그냥 메타데이터입니다 — 체크아웃은 여전히 필요할 때 `multica repo checkout`을 통해 일어납니다. Notion / Google Docs의 캐시된 문서 텍스트는 해당 타입과 함께 제공될 것입니다.
이것들은 의도적인 누락입니다 — 첫 번째 컷의 목표는 가장 적은 수의 움직이는 부품으로 이 추상화를 검증하는 것입니다.

View File

@@ -0,0 +1,49 @@
---
title: 프로젝트
description: 관련 이슈를 하나로 묶어 하나의 단위로 추적하세요 — 우선순위, 상태, 진행률, 담당자와 함께.
---
import { Callout } from "fumadocs-ui/components/callout";
Multica의 **프로젝트**는 관련 [이슈](/issues)를 담는 컨테이너입니다. 작업의 분량이 이슈 하나보다는 크지만 워크스페이스 전체보다는 작을 때 사용하세요 — 출시, 마이그레이션, 여러 부분으로 나뉘는 기능, 여러 갈래로 분기되는 조사 같은 경우입니다.
각 프로젝트에는 이름, 아이콘, 설명, **리더**(멤버 또는 [에이전트](/agents)), **상태**(`planned` / `in_progress` / `paused` / `completed` / `cancelled`), **우선순위**(`urgent` / `high` / `medium` / `low` / `none`), 그리고 연결된 이슈의 상태로부터 자동으로 산출되는 **진행률** 백분율이 있습니다.
## 프로젝트와 이슈의 관계
프로젝트와 이슈는 독립적인 객체이며 다대일 관계입니다. 하나의 이슈는 **최대 하나의** 프로젝트에 속할 수 있고, 하나의 프로젝트는 **임의 개수의** 이슈를 담을 수 있습니다. 연결과 연결 해제는 언제든지 되돌릴 수 있습니다 — 보드 뷰에서 드래그하거나, 이슈 우측 속성 패널의 프로젝트 선택기를 사용하세요.
프로젝트의 진행률 막대는 연결된 이슈로부터 계산됩니다 — `done`에 도달한 이슈가 많을수록 더 많이 채워집니다. `cancelled` 상태인 이슈는 집계에서 제외됩니다. `backlog` 상태인 이슈는 분모에는 포함되지만 분자에는 포함되지 않습니다.
## 사이드바에 고정하기
프로젝트 우측 상단의 핀 아이콘을 클릭하면 사이드바의 고정 목록에 추가됩니다. 고정된 프로젝트는 워크스페이스의 어디에 있든 한 번의 클릭으로 접근할 수 있습니다. 팀의 모든 구성원이 각자 독립적으로 고정할 수 있습니다 — 고정은 개인 설정입니다.
사이드바의 **워크스페이스 → 프로젝트** 링크는 워크스페이스의 모든 프로젝트를 항상 보여줍니다. 고정은 그 위에 얹는 개인 단축키일 뿐입니다.
## 리소스 연결하기
각 프로젝트에는 GitHub 저장소를 연결하는 **Resources** 섹션이 있습니다. 연결되고 나면, 이 프로젝트의 이슈에 할당된 [에이전트](/agents)는 작업을 실행할 때 해당 저장소를 읽고 쓸 수 있습니다 — Multica가 저장소 URL을 컨텍스트로 [데몬](/daemon-runtimes)에 전달합니다.
리소스는 프로젝트 단위입니다. 여러 프로젝트가 같은 저장소를 공유하려면 각각에 연결하세요.
## 프로젝트 삭제하기
프로젝트를 삭제해도 **이슈는 삭제되지 않습니다**. 연결된 이슈는 단지 연결이 해제되어 워크스페이스의 평면 이슈 목록으로 되돌아갑니다. 이는 의도된 동작입니다 — 프로젝트의 틀이 바뀌더라도, 프로젝트 범위로 정해졌던 작업은 일회성으로 버려지는 경우가 드뭅니다.
<Callout type="info">
작업도 함께 삭제하고 싶다면, 먼저 이슈를 보관하거나 삭제한 다음 프로젝트를 삭제하세요.
</Callout>
## 프로젝트 리더
리더는 프로젝트에 대해 책임을 지는 사람 — 또는 에이전트 — 입니다. 이는 접근 제어가 아니라 약한 신호입니다. 누가 리더든 상관없이 워크스페이스의 모든 멤버가 프로젝트를 편집할 수 있습니다. 프로젝트 리더는 다음이 될 수 있습니다:
- 워크스페이스 멤버(사람 팀원)
- [에이전트](/agents) — 프로젝트의 작업 대부분을 에이전트에게 위임할 때 유용합니다(예: "주간 버그 분류"를 분류 에이전트가 리드하는 경우).
## 다음
- [이슈](/issues) — 프로젝트 안에 들어 있는 작업 단위
- [프로젝트 리더로서의 에이전트](/agents) — 에이전트가 담당자로 적합한 경우
- [Multica 작동 방식](/how-multica-works) — 더 넓은 그림

View File

@@ -0,0 +1,127 @@
---
title: AI 코딩 도구 대조표
description: Multica는 12개의 AI 코딩 도구를 지원합니다. 모두 동일한 인터페이스를 구현하지만, 기능 세부사항은 크게 다릅니다.
---
import { Callout } from "fumadocs-ui/components/callout";
Multica는 **12개의 AI 코딩 도구**를 기본 지원합니다. 이들은 모두 동일한 인터페이스(대기열 적재, 디스패치, 실행, 결과 반환)를 구현하므로, 같은 Multica 보드에서 어느 것이든 구동할 수 있습니다. **하지만 기능 세부사항은 크게 다릅니다**: 세션 재개가 실제로 동작하는지, MCP를 지원하는지, 스킬 파일이 어디에 위치하는지, 모델을 어떻게 선택하는지. 이 페이지가 전체 대조표입니다.
에이전트를 생성할 때 도구를 고르는 방법은 [에이전트 생성 및 구성](/agents-create)을 참고하세요.
## 기능 대조 매트릭스
| 도구 | 공급사 | 세션 재개 | MCP | 스킬 주입 경로 | 모델 선택 |
|---|---|---|---|---|---|
| **Antigravity** | Google | ✅ (`--conversation <id>`) | ❌ | `.agents/skills/` | Antigravity CLI 자체 내부에서 관리 |
| **Claude Code** | Anthropic | ✅ | **✅ (실제로 사용하는 유일한 도구)** | `.claude/skills/` | 정적 + flag |
| **Codex** | OpenAI | ⚠️ 코드는 존재하지만 도달 불가 | ❌ | `$CODEX_HOME/skills/` | 정적 |
| **Copilot** | GitHub | ✅ | ❌ | `.github/skills/` | 정적 (계정 권한으로 결정) |
| **Cursor** | Anysphere | ⚠️ 코드는 존재하지만 사용 불가 | ❌ | `.cursor/skills/` | 동적 탐색 |
| **Gemini** | Google | ❌ | ❌ | `.agent_context/skills/` | 정적 |
| **Hermes** | Nous Research | ✅ | ❌ | `.agent_context/skills/` (fallback) | 동적 탐색 |
| **Kimi** | Moonshot | ✅ | ❌ | `.kimi/skills/` | 동적 탐색 |
| **Kiro CLI** | Amazon | ✅ | ❌ | `.kiro/skills/` | 동적 탐색 |
| **OpenCode** | SST | ✅ | ❌ | `.opencode/skills/` | 동적 탐색 |
| **OpenClaw** | 오픈소스 | ✅ | ❌ | `.agent_context/skills/` (fallback) | 에이전트에 바인딩되어 작업마다 전환 불가 |
| **Pi** | Inflection AI | ✅ (세션이 파일 경로) | ❌ | `.pi/skills/` | 동적 탐색 |
## 각 도구의 용도
### Antigravity
Google에서 제공합니다. CLI 바이너리 이름은 `agy`입니다. Google의 Antigravity 서비스와 연동되며 Gemini 기반의 기본 모델을 함께 제공합니다. **세션 재개가 동작합니다** — `--conversation <id>`를 통해서이며, stdout이 구조화된 이벤트 스트림이 아니라 일반 텍스트이기 때문에 데몬이 CLI의 로그 파일에서 conversation UUID를 캡처합니다. `--model` flag는 없습니다 — 모델 선택은 Antigravity CLI 설정 안에 있으므로, Multica는 이 제공자에 대해 에이전트별 모델 선택기를 비활성화합니다. 스킬은 `.agents/skills/`에 들어갑니다(CLI가 Gemini CLI의 워크스페이스 스킬 레이아웃을 그대로 따릅니다 — [Antigravity 마이그레이션 문서](https://antigravity.google/docs/gcli-migration) 참고).
### Claude Code
Anthropic에서 제공합니다. **신규 사용자에게 첫 번째 선택지**이며, 가장 완전한 기능 세트를 갖추고 있습니다: 세션 재개가 실제로 동작하고, **11개 중 MCP 구성을 진정으로 읽는 유일한 도구**이며, `--max-turns`와 `--append-system-prompt` 같은 세부 조정 flag를 지원합니다. Anthropic API 키가 필요합니다.
### Codex
OpenAI에서 제공합니다. JSON-RPC 2.0을 사용하고, 상태 유지 능력이 더 강하며, 더 세밀한 승인 메커니즘(`exec_command` 및 `patch_apply`에 대한 수동 승인)을 갖추고 있습니다. **세션 재개 코드는 존재하지만 현재 도달할 수 없습니다** — 재개가 필요하다면 Claude Code나 ACP 계열 중 하나를 선택하세요.
### Copilot
GitHub에서 제공합니다. 모델 라우팅은 GitHub 계정 권한을 거칩니다 — 도구가 직접 모델을 선택하지 않고, GitHub가 어떤 모델을 제공할지 결정합니다. `.github/skills/`에 스킬을 두는 것은 GitHub CLI의 기본 탐색 메커니즘입니다.
### Cursor
Anysphere에서 제공하며, Cursor 에디터에 대응하는 CLI입니다. **세션 재개 코드는 존재하지만 실제로는 동작하지 않습니다** — Cursor CLI 이벤트 스트림이 세션 ID를 반환하지 않으므로, 전달하는 재개 값은 항상 무효입니다. 재개가 필요하다면 다른 것을 선택하세요.
### Gemini
Google에서 제공하며, Gemini 2.5 및 3 시리즈를 지원합니다. **세션 재개도 MCP도 지원하지 않습니다** — 긴 컨텍스트 기억이 필요 없는 일회성 작업에 적합합니다.
### Hermes
Nous Research에서 제공합니다. ACP 프로토콜을 사용합니다(Kimi와 전송 계층을 공유합니다). 세션 재개가 동작합니다. 하지만 **스킬 주입 경로는 전용 경로가 아니라 범용 fallback**(`.agent_context/skills/`)입니다 — Hermes CLI 자체가 이 경로를 읽지 않으면 스킬이 적용되지 않을 수 있습니다. 테스트로 확인하세요.
### Kimi
Moonshot에서 제공하며, 중국 시장을 겨냥합니다. Hermes와 ACP 프로토콜을 공유하지만, 스킬 경로 `.kimi/skills/`는 Kimi CLI의 기본 탐색 메커니즘으로 Hermes의 fallback과는 다릅니다.
### Kiro CLI
Amazon에서 제공합니다. `kiro-cli acp`를 통해 stdio 위에서 ACP를 사용합니다. 세션 재개는 ACP `session/load`로 동작하고, 모델 선택은 `session/set_model`로 동작하며, 스킬은 프로젝트 수준 기본 탐색을 위해 `.kiro/skills/`로 복사됩니다.
### OpenCode
SST에서 제공하는 오픈소스입니다. 사용 가능한 모델을 동적으로 탐색합니다(CLI의 구성 파일을 스캔). 세션 재개가 동작합니다. **자신의 모델 카탈로그를 커스터마이징하고 싶은, 만지작거리기 좋아하는 사용자에게 적합합니다.**
### OpenClaw
오픈소스 프로젝트이며, CLI 에이전트 오케스트레이터입니다. **모델이 에이전트 계층에 바인딩됩니다**(`openclaw agents add --model`) — 작업마다 재정의할 수 없습니다. 구성이 엄격하게 통제됩니다: 사용자는 `--model`이나 `--system-prompt`를 전달할 수 없으며, 에이전트 등록 구성이 결정합니다.
### Pi
Inflection AI에서 제공하며, 미니멀합니다. **세션 재개 방식이 특이합니다** — 세션 ID가 문자열 ID가 아니라 디스크상의 파일 경로(`~/.pi/...`)입니다. 다른 도구에서는 재개 id가 CLI가 반환하는 문자열이지만, Pi에서는 재개 id가 세션 파일 그 자체입니다.
## 세션 재개: 실제로 지원하는 도구
세션 재개 메커니즘은 [작업](/tasks#can-a-task-continue-from-the-previous-context)에서 다룹니다. 다음은 도구별 **정확한 현재 상태**입니다:
| 상태 | 도구 | 의미 |
|---|---|---|
| ✅ 실제로 동작 | Antigravity, Claude Code, Copilot, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, Pi | 재개 id를 전달하면 이전 컨텍스트에서 이어집니다 |
| ⚠️ 코드는 존재하지만 도달 불가 | Codex, Cursor | 코드에 재개 경로가 있지만 실제로는 도달하지 않습니다(Codex는 조용히 폴백하고, Cursor는 세션 id를 반환하지 않습니다) — **미지원으로 간주하세요** |
| ❌ 없음 | Gemini | CLI에 재개 메커니즘이 없습니다 |
**의사결정을 위해**: 워크플로에서 에이전트가 작업 간에 컨텍스트를 유지해야 한다면(실패 재시도, 수동 재실행, 대화형 반복), ✅ 행에 있는 도구만 선택하세요.
## MCP 구성: Claude Code만 실제로 읽음
**12개 도구 중 `mcp_config`를 실제로 소비하는 것은 Claude Code뿐입니다**. 나머지 11개는 이 필드를 받아들이지만 **완전히 무시합니다** — 오류도, 경고도 없으며, 구성이 그저 효과를 내지 못합니다.
<Callout type="warning">
에이전트 구성에서 `mcp_config`를 설정했더라도 Claude Code 외의 도구를 선택하면, MCP 서버가 해당 에이전트에 **아무런 효과**도 미치지 않습니다. 현재 MCP 연동은 Claude Code만 지원합니다.
</Callout>
## 스킬 파일이 위치하는 곳
각 도구는 **자체** 스킬 탐색 경로를 사용합니다. 작업이 실행되기 전에 Multica 데몬이 워크스페이스의 스킬 파일을 해당 경로로 복사합니다:
| 도구 | 경로 | 기본 탐색 여부 |
|---|---|---|
| Claude Code | `.claude/skills/` | ✅ 기본 |
| Codex | `$CODEX_HOME/skills/` | ✅ 기본 |
| Copilot | `.github/skills/` | ✅ 기본 |
| Cursor | `.cursor/skills/` | ✅ 기본 |
| Kimi | `.kimi/skills/` | ✅ 기본 |
| Kiro CLI | `.kiro/skills/` | ✅ 기본 |
| OpenCode | `.opencode/skills/` | ✅ 기본 |
| Pi | `.pi/skills/` | ✅ 기본 |
| Antigravity | `.agents/skills/` | ✅ 기본 (Gemini CLI의 워크스페이스 레이아웃을 따름 — [Antigravity 문서](https://antigravity.google/docs/gcli-migration) 참고) |
| Gemini | `.agent_context/skills/` | ⚠️ 범용 fallback |
| Hermes | `.agent_context/skills/` | ⚠️ 범용 fallback |
| OpenClaw | `.agent_context/skills/` | ⚠️ 범용 fallback |
fallback 경로를 쓰는 도구가 실제로 이 디렉터리를 읽는지는 해당 도구 자체의 문서에 따라 달라지며 — 보장되지 않습니다. Gemini / Hermes / OpenClaw에서 스킬이 적용되지 않는다면, 먼저 이 점을 확인하세요.
스킬의 생성과 사용은 [스킬](/skills)을 참고하세요.
## 다음
- [에이전트 생성 및 구성](/agents-create) — 에이전트에 사용할 도구를 선택하세요
- [작업](/tasks) — 작업 생명주기와 세션 재개 메커니즘
- [데몬과 런타임](/daemon-runtimes) — 도구가 실행되는 곳과 Multica에 연결되는 방식
- [에이전트 런타임 설치](/install-agent-runtime) — 지원되는 12개 도구 각각의 설치 및 인증

View File

@@ -0,0 +1,275 @@
---
title: 자체 호스팅 빠른 시작
description: Docker로 자체 서버나 기기에서 Multica를 실행합니다(Kubernetes에서는 Helm 사용 가능). 약 10분 소요됩니다.
---
import { Callout } from "fumadocs-ui/components/callout";
이 페이지는 Docker로 Multica **서버**(백엔드 + 프런트엔드 + PostgreSQL)를 자체 기기나 서버에서 실행하는 과정을 안내합니다. 완료하면 [워크스페이스](/workspaces), [이슈](/issues), [댓글](/comments), [에이전트](/agents) 구성을 비롯한 데이터가 완전히 본인의 통제하에 놓입니다.
에이전트 **실행**은 여전히 로컬에서 실행하는 [데몬](/daemon-runtimes)과 그 기기에 설치된 [AI 코딩 도구](/providers)에 의존합니다 — Cloud와 완전히 동일합니다. 자체 호스팅은 서버 계층을 교체할 뿐, 실행 계층을 교체하지는 않습니다.
## 사전 요구 사항
- **Docker**가 설치되어 있고 `docker compose`를 실행할 수 있어야 함
- **Git**(선택 사항이지만 소스를 받아올 수 있으므로 권장)
- 계속 켜둘 수 있는 기기(로컬 / 내부 네트워크 / 클라우드 호스트 모두 가능)
- **데몬을 실행하는 기기**에 AI 코딩 도구가 최소 한 개 설치되어 있어야 함(서버를 실행하는 기기일 필요는 없으며, 개발용 노트북도 됩니다)
## 1. 프로젝트 받아오기 및 백엔드 시작하기
<Callout type="info">
**이미 Kubernetes를 쓰고 계신가요?** Docker를 건너뛰고 Helm 차트를 사용하세요 — 아래 [Kubernetes 배포](#kubernetes-deployment-alternative)로 이동한 다음, 첫 로그인을 위해 [4단계](#4-first-login--create-a-workspace)로 돌아오세요.
</Callout>
```bash
git clone https://github.com/multica-ai/multica.git
cd multica
make selfhost
```
`make selfhost`는 다음을 수행합니다.
1. `.env`가 없으면 `.env.example`로부터 생성하며 **무작위 JWT_SECRET**을 함께 만듭니다
2. 공식 Docker 이미지(PostgreSQL, Multica backend, Multica frontend)를 받아옵니다
3. `docker-compose.selfhost.yml`을 사용해 모든 서비스를 시작합니다
4. 백엔드의 `/health` 엔드포인트가 준비될 때까지 기다립니다
시작 이후 프로덕션 프로브에는, 데이터베이스나 migration 문제 시 검사가 실패하도록 하려면 `/readyz`를 사용하세요.
백엔드 컨테이너는 시작 시 **데이터베이스 migration을 자동으로 실행합니다**(`docker/entrypoint.sh`가 서버 시작 전에 `./migrate up`을 실행) — 백엔드 로그에서 migration 출력을 확인할 수 있습니다. 버전 업그레이드도 같은 방식으로 처리됩니다.
<Callout type="info">
**이미지가 아직 공개되지 않았나요?** `make selfhost`가 이미지를 받아오지 못한다면 아직 릴리스되지 않은 버전 태그에 있을 수 있습니다. 안정 릴리스로 전환하거나 소스에서 빌드하세요: `make selfhost-build`.
</Callout>
시작되면 다음과 같습니다.
- **프런트엔드**: [http://localhost:3000](http://localhost:3000)
- **백엔드**: [http://localhost:8080](http://localhost:8080)
<Callout type="info">
**포트는 `127.0.0.1`에서만 수신합니다.** `docker-compose.selfhost.yml`은 공개된 모든 포트를 loopback에 바인딩합니다 — `ss -tlnp`에서는 `0.0.0.0:8080`이 보이지 않으며, 설계상 다른 기기에서는 서비스에 접근할 수 없습니다. 기본 `JWT_SECRET`과 Postgres 자격 증명이 공개 인터넷에 노출되어서는 절대 안 됩니다. 기기 간 접근이 필요하면 TLS를 종료하는 리버스 프록시를 스택 앞에 두세요 — [5b단계 — 기기 간: 리버스 프록시를 앞에 두기](#5b-cross-machine-front-with-a-reverse-proxy)를 참고하세요.
</Callout>
## 2. 중요: 프로덕션 안전 설정 유지하기
<Callout type="warning">
**`docker-compose.selfhost.yml`은 기본적으로 `APP_ENV`를 `production`으로 설정하고** `MULTICA_DEV_VERIFICATION_CODE`를 비워 두므로, 공개 인스턴스에는 고정 코드가 없습니다.
`MULTICA_DEV_VERIFICATION_CODE`는 로컬 또는 비공개 테스트 자동화에서만 설정하세요. `APP_ENV`가 non-production일 때 고정 코드가 활성화되어 있으면, 코드를 요청할 수 있는 누구나 그 고정 값으로 로그인할 수 있습니다. [인증 설정 → 고정 로컬 테스트 코드](/auth-setup#fixed-local-testing-codes)를 참고하세요.
공개 배포 전에는 `.env`에 `APP_ENV=production`이 설정되어 있고 `MULTICA_DEV_VERIFICATION_CODE`가 비어 있는지 반드시 확인하세요.
</Callout>
## 3. 이메일 서비스 구성하기(선택 사항이지만 권장)
이메일을 구성하지 않으면 사용자가 이메일로 인증 코드를 받을 수 없으며, 서버가 생성된 코드를 대신 stdout에 출력합니다.
두 가지 전송 백엔드를 지원합니다 — 네트워크에 맞는 것을 고르세요.
**옵션 A — Resend(클라우드 / 공개 인터넷 배포):**
1. [Resend](https://resend.com/)에 가입하고 API key를 받습니다
2. 본인이 관리하는 발송 도메인을 인증합니다
3. `.env`에 다음을 설정합니다.
```bash
RESEND_API_KEY=re_xxxxxxxxxxxx
RESEND_FROM_EMAIL=noreply@yourdomain.com
```
**옵션 B — SMTP relay(내부 네트워크 / 온프레미스):**
배포 환경이 `api.resend.com`에 접근할 수 없거나, 이미 내부 메일 릴레이(Microsoft Exchange, Postfix, 온프레미스 SendGrid 등)가 있는 경우에 사용하세요. 둘 다 설정된 경우 `SMTP_HOST`가 Resend보다 우선하므로, 인증 및 초대 메일이 내부 릴레이에 머무릅니다. 465 포트(SMTPS / 암묵적 TLS)는 현재 지원하지 않습니다 — 25 또는 587을 사용하세요.
**익명 Exchange 내부 릴레이(포트 25)** — 호스트가 IP로 신뢰되며 자격 증명 없이 제출하는 경우:
```bash
SMTP_HOST=exchange.internal.example.com
SMTP_PORT=25
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_TLS_INSECURE=false
RESEND_FROM_EMAIL=noreply@yourdomain.com # From: 헤더로도 재사용됨
```
**인증 제출(포트 587, STARTTLS)** — 릴레이에 서비스 계정이 필요하며, STARTTLS가 광고될 때 자동으로 업그레이드되는 경우:
```bash
SMTP_HOST=smtp.internal.example.com
SMTP_PORT=587
SMTP_USERNAME=multica
SMTP_PASSWORD=...
SMTP_TLS_INSECURE=false # 비공개 CA / 자체 서명 인증서일 때만 true로 설정
RESEND_FROM_EMAIL=noreply@yourdomain.com
```
그런 다음 재시작합니다: `docker compose -f docker-compose.selfhost.yml restart backend`. 재시작 시 백엔드는 어떤 제공자를 선택했는지 출력합니다(`EmailService: SMTP relay …` / `Resend API` / `DEV mode`) — 자격 증명은 절대 로그에 남지 않으므로, 이 줄은 도움을 요청할 때 공유해도 안전합니다.
추가 인증 구성(OAuth, 가입 허용 목록)과 전체 SMTP 변수 레퍼런스는 [인증 설정](/auth-setup)과 [환경 변수 → 이메일](/environment-variables#email-configuration)을 참고하세요.
## 4. 첫 로그인 + 워크스페이스 생성
[http://localhost:3000](http://localhost:3000)을 엽니다.
- 이메일을 입력합니다
- 구성한 이메일 백엔드(Resend 또는 SMTP relay)에서 인증 코드를 받습니다. 둘 다 구성하지 않았다면 서버 컨테이너 stdout에서 복사하세요 — `[DEV] Verification code` 줄을 찾으면 됩니다
- non-production 비공개 인스턴스에서 `MULTICA_DEV_VERIFICATION_CODE=888888`을 명시적으로 설정한 경우가 아니라면 `888888`을 사용하지 마세요
- 로그인하고 첫 워크스페이스를 생성합니다
## 5. CLI를 자체 서버로 연결하기
CLI 설치는 [Cloud 빠른 시작 → 2. CLI 설치](/cloud-quickstart#2-install-the-multica-cli)와 동일합니다 — Homebrew / 스크립트 / PowerShell 중 하나를 고르세요.
### 5a. 같은 기기
CLI와 서버가 같은 호스트에서 실행된다면 기본값으로 이미 동작합니다.
```bash
multica setup self-host
```
이렇게 하면 CLI가 `http://localhost:8080`(백엔드)과 `http://localhost:3000`(프런트엔드)을 가리키고, 브라우저 로그인을 안내하며, PAT를 로컬에 저장하고, **데몬을 자동으로 시작합니다**.
### 5b. 기기 간: 리버스 프록시를 앞에 두기
compose 스택은 `127.0.0.1`에서만 수신하므로, 다른 기기에 있는 데몬은 `http://<server-ip>:8080`에 직접 연결할 수 없습니다 — 그리고 그렇게 되기를 원해서도 안 됩니다. 그렇지 않으면 기본 `JWT_SECRET`이 공개 인터넷에서 접근 가능해지기 때문입니다. 서버에 TLS를 종료하고 `127.0.0.1:8080`(백엔드)과 `127.0.0.1:3000`(프런트엔드)으로 전달하는 리버스 프록시를 두고, CLI를 공개 HTTPS URL로 연결하세요.
```bash
multica setup self-host \
--server-url https://<your-domain> \
--app-url https://<your-domain>
```
단일 호스트네임에서 프런트엔드와 백엔드를 모두 앞단에 두는(데몬과 웹 앱 모두에 필요한 WebSocket 지원 포함) 최소 Caddyfile은 다음과 같습니다.
```nginx
multica.example.com {
# WebSocket route — must come before the catch-all
@ws path /ws /ws/*
handle @ws {
reverse_proxy 127.0.0.1:8080 {
flush_interval -1
}
}
# Backend API
handle /api/* {
reverse_proxy 127.0.0.1:8080
}
# Everything else → frontend
reverse_proxy 127.0.0.1:3000
}
```
프록시를 올린 후에는 서버의 `.env`에 `FRONTEND_ORIGIN=https://multica.example.com`을 설정하고 백엔드를 재시작하세요 — 그렇지 않으면 WebSocket origin 검사가 브라우저를 거부합니다([문제 해결 → WebSocket이 연결되지 않음](/troubleshooting#websocket-cant-connect)).
[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)도 견고한 선택지입니다 — 호스트에 어떤 포트도 노출하지 않고도 TLS와 공개 호스트네임을 제공합니다. Nginx로 동등하게 구성하는 방법(`app.` / `api.`을 별도 호스트네임으로 분리, WebSocket용 `proxy_set_header Upgrade`)도 똑같이 잘 동작합니다. 핵심 요구 사항은 TLS 종료와 `/ws`에서의 `Upgrade` 헤더 전달입니다.
## 6. 에이전트 생성 + 첫 작업 할당
Cloud와 동일한 흐름입니다 — [Cloud 빠른 시작 → 5-6단계](/cloud-quickstart#5-create-an-agent)를 참고하세요.
## 7. 사용량 롤업 스케줄링(사용량 대시보드에 필수)
<Callout type="warning">
사용량 / 런타임 대시보드는 `rollup_task_usage_hourly()`가 채우는 파생 테이블 `task_usage_hourly`에서 데이터를 읽습니다. 번들된 `pgvector/pgvector:pg17` Postgres 이미지에는 **`pg_cron`이 포함되어 있지 않으며**, 백엔드도 롤업을 인프로세스로 실행하지 않습니다. `rollup_task_usage_hourly()`를 스케줄링하는 것이 없으면, 원시 `task_usage` 행은 계속 들어오는데 대시보드는 영원히 0에 머무릅니다.
</Callout>
지원되는 옵션 중 하나를 고르세요 — 하나만 있으면 됩니다.
**옵션 A — 외부 cron / systemd-timer(가장 간단함).** 임의의 외부 스케줄러에서 5분마다 롤업을 실행합니다. 멱등하고 워터마크 기반이므로, 놓친 틱은 따라잡습니다.
```bash
# /etc/cron.d/multica-rollup — every 5 minutes
*/5 * * * * root docker compose -f /path/to/multica/docker-compose.selfhost.yml \
exec -T postgres psql -U multica -d multica \
-c "SELECT rollup_task_usage_hourly();" >/dev/null
```
**옵션 B — Postgres를 `pg_cron`이 포함된 이미지로 교체.** `docker-compose.selfhost.yml`의 `pgvector/pgvector:pg17`을 `pgvector`와 `pg_cron`을 모두 갖춘 이미지(`supabase/postgres` 또는 커스텀 빌드)로 교체하고, `shared_preload_libraries=pg_cron`을 설정한 뒤 재시작하고, 작업을 한 번 등록합니다.
```sql
CREATE EXTENSION IF NOT EXISTS pg_cron;
SELECT cron.schedule(
'rollup_task_usage_hourly',
'*/5 * * * *',
$$SELECT rollup_task_usage_hourly()$$
);
```
**옵션 C — 먼저 히스토리 백필(업그레이드 경로).** `v0.3.4 → v0.3.5+`로 업그레이드하는 중이고 기존 `task_usage` 행이 있다면, migration `103`이 hourly 테이블이 시드될 때까지 `refusing to drop legacy daily rollups: ...`와 함께 `migrate up`을 중단합니다. 번들된 백필을 한 번 실행한 다음, 옵션 A 또는 B를 설정하세요.
```bash
docker compose -f docker-compose.selfhost.yml exec backend \
./backfill_task_usage_hourly --sleep-between-slices=2s
```
`--sleep-between-slices=2s`는 바쁜 DB에서 읽기 부하를 조절합니다. 완료된 후 백엔드 컨테이너를 재시작하면(시작 시 migration이 실행됨) 업그레이드가 완료됩니다.
전체 레퍼런스 — Kubernetes `CronJob` 템플릿과 업그레이드 순서 포함 — 는 저장소의 [`SELF_HOSTING_ADVANCED.md → Usage Dashboard Rollup`](https://github.com/multica-ai/multica/blob/main/SELF_HOSTING_ADVANCED.md#usage-dashboard-rollup)에 있습니다.
## Kubernetes 배포(대체 방안)
이미 Kubernetes 클러스터를 운영 중이라면, 저장소에는 `deploy/helm/multica/`에 Helm 차트도 포함되어 있습니다. k8s용 `make selfhost`에 해당합니다 — 동일한 백엔드 이미지, 프런트엔드 이미지, `pgvector/pgvector:pg17` Postgres를 Deployment / Service / Ingress로 패키징하고, `values.yaml`로 렌더링한 하나의 `ConfigMap`을 함께 제공합니다. k3s + Traefik + `local-path`를 기준으로 작성되었으며, Ingress 컨트롤러와 기본 `ReadWriteOnce` StorageClass가 있는 모든 클러스터에서 동작합니다.
이 차트는 **시크릿 값을 템플릿화하지 않습니다**. `multica-secrets`라는 이름의 Secret을 이름으로 참조하므로, 실제 JWT / DB / Resend / Google 키가 git이나 `values.yaml`에 들어갈 필요가 전혀 없습니다. 네임스페이스와 Secret을 kubectl로 한 번 생성하세요.
```bash
kubectl create namespace multica
kubectl -n multica create secret generic multica-secrets \
--from-literal=JWT_SECRET="$(openssl rand -hex 32)" \
--from-literal=POSTGRES_PASSWORD="$(openssl rand -hex 16)" \
--from-literal=RESEND_API_KEY="" \
--from-literal=GOOGLE_CLIENT_SECRET="" \
--from-literal=CLOUDFRONT_PRIVATE_KEY="" \
--from-literal=MULTICA_DEV_VERIFICATION_CODE=""
```
그런 다음 차트를 설치합니다.
```bash
git clone https://github.com/multica-ai/multica.git
cd multica
helm install multica deploy/helm/multica -n multica
```
기본값은 호스트네임 `multica.dev.lan`(web)과 `api.multica.dev.lan`(백엔드)을 가정합니다. 이것들을 `/etc/hosts`(또는 로컬 DNS)에 추가해, Ingress에 도달 가능한 임의의 노드 IP를 가리키도록 하세요. 다른 호스트네임을 사용하려면 `deploy/helm/multica/values.yaml`을 복사한 뒤 `ingress.frontend.host` / `ingress.backend.host`와 그에 대응하는 `backend.config.appUrl` / `frontendOrigin` / `localUploadBaseUrl` / `googleRedirectUri`를 편집하고, `-f my-values.yaml`로 설치하세요.
콜드 클러스터에서는 백엔드가 Postgres를 기다리고 migration을 실행하는 동안 몇 분간 `Running` 상태이지만 `Ready`는 아닐 수 있습니다 — startupProbe가 이를 흡수하므로 파드는 재시작되지 않습니다. `Ready`가 되면:
```bash
curl -H "Host: api.multica.dev.lan" http://<ingress-ip>/healthz
# {"status":"ok","checks":{"db":"ok","migrations":"ok"}}
```
그런 다음 `http://multica.dev.lan`을 열고 위의 [4단계 — 첫 로그인](#4-first-login--create-a-workspace)에서 이어서 진행하세요. CLI를 Ingress 호스트네임으로 연결합니다.
```bash
multica setup self-host \
--server-url http://api.multica.dev.lan \
--app-url http://multica.dev.lan
```
차트를 변경하지 않고 최신 이미지만 받아오려면 `kubectl -n multica rollout restart deploy/multica-backend deploy/multica-frontend`를 실행하세요. 특정 Multica 릴리스를 고정하려면 values 파일에서 `images.backend.tag` / `images.frontend.tag`를 설정하고 `helm upgrade`를 실행하세요. `helm -n multica uninstall multica`는 워크로드를 제거하지만 PVC와 Secret은 유지합니다. `kubectl delete namespace multica`는 모든 것을 삭제합니다.
전체 레퍼런스 — 세 가지 로그인 모드, web 이미지에 빌드 타임에 굳혀진 `REMOTE_API_URL`에 대한 `backend` ExternalName 우회책, 리소스 제한, TLS — 는 저장소의 [`SELF_HOSTING.md`](https://github.com/multica-ai/multica/blob/main/SELF_HOSTING.md#kubernetes-deployment-alternative)에 있습니다.
## 자주 발생하는 문제
- **백엔드가 시작되지 않음**: `docker compose -f docker-compose.selfhost.yml logs backend`로 컨테이너 로그를 확인하세요. 보통 `.env`의 잘못된 `DATABASE_URL` 또는 `JWT_SECRET`이 원인입니다
- **인증 코드를 받지 못함**: 이메일 백엔드가 구성되지 않은 경우(Resend도 SMTP도 없음) → `docker compose logs backend`에서 `[DEV] Verification code`를 찾으세요
- **WebSocket이 연결되지 않음**: 공개 배포에서는 반드시 `FRONTEND_ORIGIN`을 실제 프런트엔드 도메인으로 설정해야 합니다. [문제 해결 → WebSocket이 연결되지 않음](/troubleshooting#websocket-wont-connect)을 참고하세요
- **사용량 / 런타임 대시보드가 0에 머무름**: `rollup_task_usage_hourly()`가 스케줄링되지 않고 있습니다 — 위의 [7단계](#7-schedule-the-usage-rollup-required-for-the-usage-dashboard)와 [문제 해결 → 사용량 대시보드가 0으로 표시됨](/troubleshooting#usage-dashboard-stays-at-zero)을 참고하세요
- **`migrate up`이 `refusing to drop legacy daily rollups`로 실패함**: `v0.3.4 → v0.3.5+` 업그레이드 경로 가드입니다. 먼저 `backfill_task_usage_hourly`를 실행하세요 — [7단계 → 옵션 C](#7-schedule-the-usage-rollup-required-for-the-usage-dashboard)를 참고하세요
## 다음 단계
- [환경 변수](/environment-variables) — 전체 env 레퍼런스
- [인증 설정](/auth-setup) — Resend / OAuth / 가입 허용 목록 상세
- [GitHub 연동](/github-integration) — GitHub App을 연결해 PR이 이슈에 자동 연결되고 머지 시 이슈가 닫히도록 설정
- [문제 해결](/troubleshooting) — 문제가 생기면 여기서 시작하세요
- [데스크톱 앱](/desktop-app) — `~/.multica/desktop.json`을 통한 선택적 데스크톱 설정. 웹 프런트엔드 + CLI가 여전히 가장 빠른 자체 호스팅 경로입니다

View File

@@ -0,0 +1,67 @@
---
title: 스킬
description: 에이전트에 "지식 팩"을 연결하세요 — Anthropic Agent Skills 개방 표준과 호환됩니다.
---
import { Callout } from "fumadocs-ui/components/callout";
스킬은 [에이전트](/agents)를 위한 **지식 팩**입니다 — `SKILL.md` 한 개와 선택적인 보조 파일(스크립트, 설정, 참조 템플릿)로 구성되며, 에이전트에게 "이런 종류의 작업을 만나면 이렇게 생각하고 행동하라"고 알려줍니다. Multica는 [Anthropic Agent Skills](https://agentskills.io) 개방 표준을 채택하고 있으므로, Anthropic 공식 저장소, ClawHub, skills.sh 등에서 가져온 표준을 준수하는 어떤 스킬이든 곧바로 가져올 수 있습니다.
## 워크스페이스 스킬과 로컬 스킬
Multica는 두 가지 스킬 소스를 지원합니다.
- **워크스페이스 스킬** — Multica 클라우드에 저장됩니다. 에이전트에 연결되면 작업 실행 시점에 여러분의 데몬으로 동기화됩니다. 이것이 **팀 전체에서 스킬을 공유하는 표준 방식**입니다.
- **로컬 스킬** — 여러분의 기기에 있는 디렉터리에 존재합니다(각 AI 코딩 도구마다 관례적인 기본 경로가 있습니다. 예: Claude Code의 `~/.claude/skills/`). 여러분이 요청하면 [데몬](/daemon-runtimes)이 기기를 스캔하고, 어떤 스킬을 워크스페이스로 가져올지 직접 고릅니다.
대부분의 경우 **워크스페이스 스킬**을 원하게 됩니다. 한 번만 가져오면 모든 팀원의 에이전트가 사용할 수 있기 때문입니다. 로컬 스킬은 먼저 로컬에서 테스트하고 싶거나, 콘텐츠에 민감한 로컬 자료가 포함된 경우에 적합합니다.
## 스킬 가져오기
워크스페이스 스킬은 네 가지 소스에서 가져옵니다.
- **새로 만들기** — UI에서 `SKILL.md`와 관련 파일을 직접 작성합니다
- **GitHub에서** — 저장소 URL을 붙여 넣으면(예: `https://github.com/owner/repo/tree/main/skills/my-skill`) Multica가 해당 디렉터리의 `SKILL.md`와 모든 파일을 가져옵니다
- **ClawHub에서** — [ClawHub](https://clawhub.io) 공개 마켓플레이스에서 검색하고 버전을 선택하여 가져옵니다
- **로컬에서** — 데몬이 여러분 기기의 스킬 디렉터리를 스캔하고, 워크스페이스로 가져올 스킬을 직접 고릅니다
개별 파일과 스킬 팩 전체 모두 용량 제한이 있습니다(GitHub에서 가져올 때 단일 파일 제한은 약 1 MB). 정확한 규칙은 가져오기 대화 상자에 표시되며, 제한을 초과하면 오류가 반환됩니다.
## 에이전트에 연결하기
가져온 스킬은 **특정 에이전트에 연결**되어야 효과를 발휘합니다. 한 에이전트에 여러 스킬을 연결할 수 있고, 한 스킬을 여러 에이전트에 연결할 수도 있습니다.
연결한 뒤에는 에이전트가 다음번 작업을 시작할 때 스킬을 가져옵니다 — 각 AI 코딩 도구는 고유한 스킬 탐색 경로를 가지며(Claude Code는 `.claude/skills/`, Cursor는 `.cursor/skills/`, Antigravity는 `.agents/skills/` 등을 사용), Multica가 올바른 위치에 파일을 자동으로 배치합니다. **다만 세 가지 도구(Gemini, Hermes, OpenClaw)는 현재 범용 폴백 경로인 `.agent_context/skills/`를 사용하며, 이 도구들이 실제로 해당 경로에서 스킬을 읽어 들이는지는 도구 자체에 달려 있습니다.** 전체 경로 매핑과 네이티브 탐색 대 폴백의 구분은 [AI 코딩 도구 비교 → 스킬 파일이 놓이는 위치](/providers#where-skill-files-go)에 있습니다.
스킬의 내용을 편집한 뒤에는 **새로 생성된 작업만 새 버전을 가져옵니다** — 이미 실행 중인 작업은 이전 스킬을 그대로 사용합니다.
## 서드파티 스킬의 안전성
GitHub나 ClawHub에서 가져온 스킬에는 스크립트와 실행 가능한 콘텐츠가 포함될 수 있습니다. Multica 자체는 이를 **서명하거나, 감사하거나, 샌드박스화하지 않습니다** — 스킬 콘텐츠는 해당 AI 코딩 도구에 있는 그대로 전달되며, 도구가 이를 실행 가능한 것으로 취급할지는 도구에 달려 있습니다.
<Callout type="warning">
**서드파티 스킬을 가져오기 전에, `SKILL.md`와 함께 제공되는 모든 파일을 검토하세요.**
2026년 2월에 발생한 "ClawHavoc" 사건에서는 인기 있는 스킬 팩에 심어진 악성 지침이 영향을 받은 사용자들의 API 키를 탈취했습니다. ClawHub는 이후 VirusTotal 스캔을 추가했지만, **자동 스캔이 여러분 자신의 검토를 대신할 수는 없습니다.**
**신뢰하는 소스에서만 가져오세요.** 민감한 데이터가 관련된 프로젝트라면, 여러분이 직접 작성한 로컬 스킬만 사용하는 것을 고려하세요.
</Callout>
## 스킬과 MCP
둘 다 에이전트가 할 수 있는 일을 보강하지만, 방향이 다릅니다.
- **스킬** = 구조화된 **지식 팩**(정적 콘텐츠 + 지침). 에이전트는 스킬을 읽어 "문제 X를 만나면 이렇게 생각하고 이렇게 처리하라"를 학습합니다.
- **MCP**(Model Context Protocol) = **도구 채널**. 에이전트는 MCP를 사용해 외부 서비스(데이터베이스, 파일 시스템, 서드파티 API)에 연결하고 이를 **호출**합니다.
이 둘은 상호 보완적입니다. 현재 Multica에서 MCP 지원은 **Claude Code만 실제로 활용합니다** — 다른 도구들은 MCP 설정을 받기는 하지만 실제로 사용하지는 않습니다. MCP 전용 섹션은 추후 릴리스에서 추가될 예정입니다.
---
이제 에이전트가 무엇인지, 어떻게 만드는지, 스킬을 어떻게 연결하는지 알게 되었습니다. 다음 질문은 이것입니다. **에이전트는 실제로 어디에서 실행되며, 왜 가끔 멈춰버리는가?** 다음 장에서는 실행 아키텍처 — 데몬, 런타임, 그리고 작업이 어떻게 함께 동작하는지 — 를 다룹니다.
## 다음 단계
- [데몬과 런타임](/daemon-runtimes) — 에이전트가 실제로 실행되는 곳, 그리고 온라인과 오프라인을 구분하는 방법
- [작업 실행하기](/tasks) — 한 번의 "에이전트 작업 세션"의 전체 수명 주기
- [AI 코딩 도구 비교](/providers) — 12개 도구 전체 비교(각 도구의 스킬 주입 경로 포함)

View File

@@ -0,0 +1,136 @@
---
title: 스쿼드
description: "스쿼드는 하나의 지정된 리더 에이전트가 이끄는 에이전트(그리고 선택적으로 사람 멤버)의 그룹입니다. 스쿼드에 이슈를 할당하면 리더가 누가 맡을지 결정합니다."
---
import { Callout } from "fumadocs-ui/components/callout";
스쿼드는 하나의 지정된 **리더 에이전트**를 둔, **[에이전트](/agents)와 사람 [멤버](/members-roles)의 이름 있는 그룹**입니다. 스쿼드 자체가 일급 담당자입니다. 어떤 **Assignee** 선택기에서든 스쿼드를 고르면 리더가 트리거를 받아 이슈를 읽은 다음, 그 작업에 가장 적합한 스쿼드 멤버를 `@`로 멘션합니다. 스쿼드를 사용하면 전문가들을 한 번 구성해 두고 **이름이 아니라 주제로** 작업을 배정할 수 있습니다. 팀이 커져도 라우팅은 그대로 유지됩니다.
## 스쿼드의 작동 원리
- **리더 한 명, 멤버 여러 명.** 리더는 반드시 에이전트여야 하며, 멤버는 에이전트일 수도 사람 멤버일 수도 있습니다. 리더만 있는 스쿼드도 허용됩니다(리더 브리핑에 "다른 멤버 없음"이라고 표시됩니다). 동일한 에이전트가 여러 스쿼드에 속할 수도 있습니다.
- **사람을 고를 수 있는 모든 곳에서 할당 가능.** 스쿼드는 Assignee 선택기, @멘션 선택기, 빠른 생성 모달에 나타납니다. 에이전트나 멤버를 고를 수 있는 곳이라면 어디서든 스쿼드를 고를 수 있습니다.
- **보관을 통한 소프트 삭제.** 스쿼드를 보관하면 선택기와 목록에서 사라집니다. 현재 그 스쿼드에 할당된 이슈는 모두 **리더 에이전트에게 이전**되어 작업이 멈추지 않습니다. 보관된 스쿼드에는 새 이슈를 할당할 수 없습니다.
## 스쿼드와 단일 에이전트 중 무엇을 쓸지
| 스쿼드를 선택하는 경우… | 단일 에이전트를 선택하는 경우… |
|---|---|
| 여러 전문가가 있지만 이 이슈에 누가 맞을지 미리 알 수 없을 때 | 작업 범위가 하나의 전문 분야로 명확하고 누가 해야 할지 알고 있을 때 |
| 실제 응답자는 이슈마다 바뀌더라도 담당자(스쿼드)는 안정적으로 유지하고 싶을 때 | 이슈에 에이전트의 이름을 남기고 명확한 개인 책임을 두고 싶을 때 |
| 댓글에서 `@FrontendTeam` 같은 라우팅 대상을 원할 때 | 일대일 `@agent-name`만으로 충분할 때 |
스쿼드는 능력을 더하지 않습니다. **라우팅**을 더합니다. 멤버는 여전히 평범한 에이전트이며, 리더의 유일한 역할은 알맞은 사람을 고르는 것입니다.
## 권한
| 동작 | 할 수 있는 사람 |
|---|---|
| 스쿼드 생성 / 업데이트 / 보관 | 워크스페이스 **owner** 또는 **admin** |
| 멤버 추가/제거, 역할 변경 | 워크스페이스 **owner** 또는 **admin** |
| 스쿼드에 이슈 할당 | 모든 워크스페이스 멤버(에이전트에 할당하는 것과 동일) |
| 댓글에서 스쿼드를 `@`로 멘션 | 모든 워크스페이스 멤버 |
| 스쿼드 리더 평가 기록 | 스쿼드 리더 에이전트만(CLI로) |
전체 역할 매트릭스는 [멤버와 역할](/members-roles)에 있습니다.
## 스쿼드 생성
사이드바에서 **스쿼드 → 새 스쿼드**를 열고 다음을 입력하세요.
- **이름(Name)** — 예: `Frontend Team`, `Bug Triage`. 워크스페이스 내에서 고유할 필요는 없습니다.
- **설명(Description, 선택)** — 스쿼드 카드와 상세 페이지에 표시되는 짧은 소개.
- **리더(Leader)** — 기존 에이전트를 고릅니다. 리더는 `leader` 역할로 자동으로 스쿼드에 추가됩니다.
생성 후, 스쿼드의 상세 페이지를 열어 다음을 할 수 있습니다.
- **멤버 추가** — 에이전트나 사람 멤버를 고르고, 선택적으로 각자에게 짧은 역할 설명(예: "owns the migrations", "reviewer of last resort")을 부여합니다. 리더는 누구에게 위임할지 결정할 때 이 역할을 사용합니다.
- **지침 작성** — 리더가 매 실행마다 보는 스쿼드 수준의 안내입니다(아래에서 자세히 설명).
- **아바타 설정** — 에이전트에 사용하는 것과 동일한 선택기에서 고릅니다.
CLI에서의 동등한 명령:
```bash
multica squad create --name "Frontend Team" --leader frontend-lead-agent
multica squad member add <squad-id> --member-id <agent-or-user-uuid> --type agent --role "Owns Tailwind / shadcn surface"
```
## 스쿼드에 할당된 이슈가 실행되는 방식
`backlog`가 아닌 이슈가 스쿼드에 할당되면, Multica는 즉시 **리더 에이전트**를 위한 `task`를 대기열에 넣습니다(모든 멤버를 위해서가 아닙니다). 그다음 흐름은 다음과 같습니다.
1. **리더가 작업을 가져갑니다.** 에이전트 런타임이 다음 폴링에서 작업을 가져가며, 이는 다른 에이전트 할당과 동일합니다.
2. **리더가 브리핑을 받습니다.** 작업을 가져가는 순간, Multica는 리더의 시스템 프롬프트에 세 개의 섹션을 덧붙입니다. 아래 [리더가 매 턴마다 보는 내용](#what-the-leader-sees-on-every-turn)을 참고하세요.
3. **리더가 하나의 위임 댓글을 작성합니다.** 그 댓글은 명단에 있는 정확한 멘션 마크다운을 사용해 선택된 멤버를 `@`로 멘션합니다. 이 멘션은 멘션된 각 에이전트를 위한 새 `task`를 트리거합니다.
4. **리더가 평가를 기록합니다** — `multica squad activity <issue-id> action --reason "..."`를 통해 기록합니다. 이는 이슈의 활동 타임라인에 항목을 작성하여, 리더가 실제로 트리거를 평가했음을 사람이 확인할 수 있게 합니다.
5. **리더가 멈춥니다.** 리더는 구현 작업을 직접 하지 않습니다. 위임받은 멤버가 다시 글을 올리면, 리더가 다시 트리거되어 업데이트를 읽고 다음 단계를 위임하거나, 에스컬레이션하거나, 침묵을 지킵니다.
이슈가 **`backlog`** 상태이면 리더는 트리거되지 않습니다. `backlog`는 주차장이며, 직접 에이전트에게 할당할 때와 동일한 규칙이 적용됩니다.
### 리더가 매 턴마다 보는 내용
스쿼드 리더가 실행될 때마다, 세 개의 블록이 리더의 지침에 덧붙여집니다.
- **Squad Operating Protocol** — 하드코딩된 규칙 모음: 이슈를 읽고, `@`멘션으로 위임하고, 간결하게(이슈 본문을 다시 말하지 마세요. 담당자가 직접 읽을 수 있습니다) 작성하고, 매 턴 평가를 기록하며, **배정 후 멈춥니다**. 이 프로토콜은 시스템이 관리하며 편집할 수 없습니다.
- **Squad Roster** — 리더 자신의 행과, 보관되지 않은 멤버마다 한 행씩으로 구성됩니다. 각 행에는 리더가 붙여넣어야 할 정확한 멘션 마크다운(`[@Name](mention://agent/<uuid>)` 또는 `[@Name](mention://member/<uuid>)`)이 담겨 있습니다. 일반 텍스트 `@name`을 입력하면 아무도 트리거되지 않습니다.
- **Squad Instructions** — 이 스쿼드를 위한 사용자 지정 안내입니다(스쿼드 상세 페이지에서 설정하거나 `multica squad update --instructions`로 설정). 라우팅 규칙("DB 작업은 Alice에게, 프런트엔드는 Bob에게"), 에스컬레이션 정책, 또는 이슈 자체에 없는 그 밖의 사항 중 리더가 알아야 할 내용을 적는 데 사용하세요.
## 리더가 다시 트리거되는 경우
첫 배정 이후, 리더는 이슈의 **대부분의 후속 댓글**에 의해 자동으로 깨어납니다. 정확한 규칙은 다음과 같습니다.
| 이벤트 | 리더 트리거됨? |
|---|---|
| 비멤버(사람 보고자, 외부 에이전트)가 댓글을 작성 | **예** |
| 스쿼드 멤버가 `@mention` 없이 진행 상황 업데이트를 작성 | **예** — 리더가 다음 단계가 필요한지 다시 평가합니다 |
| 누군가 다른 에이전트 / 멤버 / 스쿼드 / `@all`을 명시적으로 `@`멘션하는 댓글을 작성 | **아니요** — 명시적 `@`가 라우팅 신호이며, 리더는 물러납니다 |
| 리더 자신의 댓글(자가 트리거) | **아니요** — 루프를 방지하기 위해 차단됩니다 |
| 이슈 상호 참조(`[MUL-123](mention://issue/...)`)만 담은 댓글 | **예** — 이슈 참조는 라우팅이 아닙니다 |
이 규칙들 위에 중복 제거가 적용됩니다. 리더가 이 이슈에 이미 `queued` 또는 `dispatched` 상태의 작업을 가지고 있다면, 새 트리거는 중복된 작업을 대기열에 넣지 않습니다.
<Callout type="info">
**멤버가 `@`멘션을 올렸을 때 리더가 트리거되지 않는 이유.** 스쿼드 멤버가 누군가를 직접 `@`하면, 그 댓글은 의도적인 인계입니다. 리더가 깨어나 라우팅을 "관찰"하게 하면 아무 동작도 없는 턴만 만들어 타임라인을 어지럽힐 뿐입니다. 에이전트가 작성한 댓글은 예외입니다. 어떤 에이전트가 다른 에이전트를 `@`하는 결과를 올리면, 리더는 여전히 깨어나 스레드를 조율할 수 있습니다.
</Callout>
## 댓글에서 스쿼드를 `@`로 멘션하기
스쿼드는 멤버 및 에이전트와 나란히 `@` 선택기에 나타납니다. 스쿼드를 멘션하면 `[@SquadName](mention://squad/<uuid>)`가 삽입되며, 이슈를 스쿼드에 할당한 것처럼 **스쿼드 리더**를 트리거합니다. 다만 담당자나 상태는 바뀌지 않습니다. 현재 소유자를 그대로 유지하면서 스쿼드가 질문이나 하위 작업을 맡을 사람을 고르게 하고 싶을 때 사용하세요.
동일한 안티 루프 규칙이 적용됩니다. 리더는 자신을 건너뛰며, 같은 댓글 안에 명시적인 멤버 `@`멘션이 있으면 그 멤버에게 직접 라우팅됩니다.
## 스쿼드 재할당 또는 보관
**이슈를 스쿼드에서 다른 담당자로 재할당하는 것**은 다른 모든 담당자 변경과 동일하게 동작합니다. 이슈의 활성 작업(리더의 것 포함)이 모두 취소되고, 새 담당자(에이전트, 멤버, 또는 다른 스쿼드)가 대기열에 들어갑니다. "담당자를 바꾸지 않고 스쿼드만 제거하는" 별도의 동작은 없습니다. 다른 담당자를 고르세요.
**스쿼드 보관**(`multica squad delete <id>`, 또는 상세 페이지의 Archive 버튼):
1. **현재 스쿼드에 할당된 이슈를 리더 에이전트에게 이전**하여, 작업이 멈추는 대신 구체적인 에이전트를 상대로 계속 진행되도록 합니다.
2. 스쿼드에 `archived_at` / `archived_by`를 표시합니다. 행은 보존되므로 과거의 활동 항목이 여전히 해석되지만, 스쿼드는 목록, 선택기, @멘션 드롭다운에서 사라집니다.
3. 이 스쿼드로의 **이후 할당을 거부**하며 `cannot assign to an archived squad`를 반환합니다.
현재 보관 해제 명령은 없습니다. 라우팅을 되살려야 한다면 새 스쿼드를 생성하세요.
## CLI에서의 스쿼드 운영
| 명령 | 용도 |
|---|---|
| `multica squad list` | 워크스페이스의 스쿼드 목록 표시 |
| `multica squad get <id>` | 한 스쿼드의 이름, 리더, 설명, 지침 표시 |
| `multica squad create --name "..." --leader <agent>` | 스쿼드 생성(owner / admin) |
| `multica squad update <id> [--name X] [--description X] [--instructions X] [--leader Y] [--avatar-url Z]` | 하나 이상의 필드 업데이트 |
| `multica squad delete <id>` | 보관(소프트 삭제) — 할당된 이슈를 리더에게 이전 |
| `multica squad member list <id>` | 스쿼드의 멤버 목록 표시 |
| `multica squad member add <id> --member-id <uuid> --type agent\|member [--role "..."]` | 멤버 추가(owner / admin) |
| `multica squad member remove <id> --member-id <uuid> --type agent\|member` | 멤버 제거(리더는 제거할 수 없습니다 — 먼저 리더를 변경하세요) |
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | 리더 에이전트가 매 턴 종료 시 기록 |
`--leader`는 에이전트 이름이나 UUID를 받습니다. 그 밖의 ID는 `multica agent list --output json`, `multica workspace member list --output json`, `multica squad list --output json`에서 가져옵니다.
## 다음
- [에이전트에게 이슈 할당하기](/assigning-issues) — 동일한 흐름이며, 스쿼드 담당자에도 적용됩니다
- [댓글에서 에이전트를 `@`로 멘션하기](/mentioning-agents) — `@` 선택기에도 스쿼드가 나타납니다
- [에이전트](/agents) — 에이전트란 무엇인지, 모든 스쿼드의 기본 구성 요소
- [멤버와 역할](/members-roles) — owner / admin / member의 전체 권한 매트릭스

View File

@@ -0,0 +1,117 @@
---
title: 작업
description: 모든 에이전트 실행의 작업 단위로, 명확한 상태 머신, 타임아웃, 재시도 규칙을 갖추고 있습니다.
---
import { Callout } from "fumadocs-ui/components/callout";
import { Mermaid } from "@/components/mermaid";
**작업**은 모든 [에이전트](/agents) 실행의 단위입니다 — [에이전트에게 이슈를 할당](/assigning-issues)하거나, [댓글에서 에이전트를 @-멘션](/mentioning-agents)하거나, [채팅](/chat)에서 메시지를 보내거나, [오토파일럿](/autopilots)이 예약된 시각에 실행되면 모두 작업이 생성됩니다. Multica는 이를 대기열에 넣고, [데몬](/daemon-runtimes)이 가져가 해당 [AI 코딩 도구](/providers)에 넘긴 다음, 완료되면 결과를 서버에 다시 기록합니다.
작업과 [이슈](/issues)는 서로 다른 두 객체입니다. 하나의 이슈는 여러 번 할당되거나 @-멘션되거나 수동으로 재실행될 수 있으며 — 그때마다 **새로운** 작업이 생성됩니다.
## 작업이 거치는 상태
<Mermaid chart={`
graph LR
Q["Queued<br/>queued"] -->|daemon picks up| D["Dispatched<br/>dispatched"]
D -->|agent starts| R["Running<br/>running"]
R -->|success| C["Completed<br/>completed"]
R -->|error or timeout| F["Failed<br/>failed"]
Q -->|user cancels| X["Cancelled<br/>cancelled"]
D -->|user cancels| X
R -->|user cancels| X
F -.retryable reason.-> Q
`} />
- **Queued(대기 중)** — 작업이 막 생성되어 데몬이 가져가기를 기다리는 상태
- **Dispatched(파견됨)** — 데몬이 작업을 점유하고 AI 코딩 도구를 시작하는 중
- **Running(실행 중)** — AI 코딩 도구가 실제로 작업을 수행하는 중
- **Completed(완료)** — 성공적으로 끝났으며, 산출물(댓글, 코드 커밋, 상태 변경)이 서버에 다시 기록됩니다
- **Failed(실패)** — 오류 또는 타임아웃으로 중단됨; 실패 사유가 재시도 가능한 경우 작업은 자동으로 `queued` 상태로 돌아가 다시 시도됩니다
- **Cancelled(취소됨)** — 사용자가 취소한 경우
## 작업이 타임아웃되면 일어나는 일
Multica 서버는 30초마다 스캔합니다. 두 종류의 타임아웃이 실패를 유발합니다:
| 상황 | 타임아웃 |
|---|---|
| 파견되었으나 시작되지 않음(데몬이 가져갔지만 AI 도구를 실행하지 않음) | **5분** |
| 너무 오래 실행됨 | **2.5시간** |
두 타임아웃 모두 실패 사유로 `timeout`을 사용하며 **자동으로 재시도됩니다**(다음 섹션). 관련된 런타임 누락 검사는 [데몬과 런타임 → 런타임이 오프라인으로 표시되는 시점](/daemon-runtimes#when-a-runtime-is-marked-offline)을 참고하세요.
## 어떤 실패가 자동으로 재시도되고 어떤 실패는 그렇지 않은지
실패는 두 가지 범주로 나뉩니다: **재시도 가능**과 **재시도 불가**.
**재시도 가능**(Multica가 자동으로 다시 대기열에 넣음):
- `runtime_offline` — 작업이 파견된 후 데몬이 사라짐
- `runtime_recovery` — 데몬이 충돌 후 재시작되어 끝내지 못한 작업을 회수함
- `timeout` — 런타임 또는 파견 타임아웃
**재시도 불가**(작업이 실패 상태로 유지됨):
- `agent_error` — AI 코딩 도구 자체가 오류를 보고함(API 오류, 할당량 초과, 내부 버그). 근본적인 문제는 재시도하지 않습니다 — 무한 반복될 수 있기 때문입니다.
자동 재시도에는 두 가지 추가 조건도 있습니다:
1. **최대 2회 시도** — 원본 1회 + 재시도 1회. 재시도도 실패하면 사유가 재시도 가능하더라도 더 이상 재시도하지 않습니다.
2. **이슈 및 채팅으로 트리거된 작업에만 해당** — 오토파일럿으로 트리거된 작업은 자동으로 재시도되지 **않습니다**.
<Callout type="warning">
**오토파일럿 작업은 자동으로 재시도되지 않습니다** — 의도된 설계입니다. 오토파일럿은 자체적인 실행 주기(예: 매일)를 갖고 있으며, 실패 시 자동 재시도가 일어나면 다음 예약 실행과 겹치게 됩니다. 실패 후 즉시 재실행이 필요하다면 수동 재실행을 사용하세요(다음 섹션).
**오토파일럿 작업이 실패했음을 알 수 있는 방법**: [인박스](/inbox)에 알림이 도착하고, 연관된 이슈의 상태가 `in_progress`에서 다시 `todo`로 되돌아갑니다. [오토파일럿](/autopilots) 페이지에서도 오토파일럿별 최근 실행 결과를 확인할 수 있습니다.
</Callout>
## 수동 재실행 vs. 자동 재시도
**수동 재실행**은 CLI 또는 API(`POST /api/issues/{id}/rerun`)에서 직접 트리거하는 것입니다:
```bash
multica issue rerun <issue-id>
```
동작:
- 기본적으로 이슈의 **현재 에이전트 담당자**를 대상으로 합니다 — 이전 작업을 누가 실행했는지와 무관하게 재실행이 현재 할당을 따르도록 하고 싶을 때 유용합니다.
- 실행 로그의 특정 행에 있는 재시도 버튼은 해당 행의 작업 ID를 함께 전송하므로, 재실행은 **현재 담당자가 아니라 바로 그 작업을 실행했던 에이전트**를 대상으로 합니다. 덕분에 스쿼드 워커, 병렬 @-멘션 에이전트, 또는 재할당으로 인해 에이전트가 교체된 행에 대해서도 행 단위 재시도가 의미를 갖게 됩니다.
- 이 이슈에 대한 대상 에이전트의 대기 중이거나 실행 중인 작업을 **취소합니다**(있는 경우). 같은 이슈에서 다른 에이전트가 소유한 작업(예: 병렬 @-멘션 실행)은 그대로 둡니다.
- **완전히 새로운** 작업을 생성합니다 — 원본 작업이 시도 횟수 상한에 도달했더라도 시도 횟수가 1로 재설정됩니다.
- **새로운 에이전트 세션**을 시작합니다 — 이전 세션 ID는 **상속되지 않습니다**. 수동 재실행은 이전 산출물이 나빴다고 판단했다는 의미이므로, 같은 대화를 이어가면 오염된 상태를 그대로 재생하게 됩니다. (반면 자동 재시도는 세션을 상속합니다 — 그 경로는 잘못된 산출물이 아니라 인프라 장애를 위한 것입니다.)
비교:
| 항목 | 자동 재시도 | 수동 재실행 |
|---|---|---|
| 트리거 | 시스템, 실패 사유 기반 | 사용자, 수동 |
| 상한 | 2회 시도 | 제한 없음 |
| 적용 가능한 소스 | 이슈, 채팅 | 에이전트 담당자가 있는 이슈 |
| 선택되는 에이전트 | 실패한 작업과 동일한 에이전트 | 소스 작업의 에이전트(UI 행 단위 재시도) 또는 이슈의 현재 담당자(CLI / task_id 없음) |
| 세션 상속 | 예(이전 세션 재개) | 아니요(새 세션) |
## 실패한 작업이 이슈 상태에 미치는 영향
이슈가 에이전트에게 할당되어 트리거된 작업이 실패하면(그리고 자동 재시도가 성공하지 못하면), **이슈의 상태가 `in_progress`에서 `todo`로 자동으로 되돌아갑니다** — 그래서 보드를 열면 "이건 다시 봐야겠다"는 것을 즉시 알 수 있습니다. [이슈와 프로젝트](/issues)를 참고하세요.
## 작업이 이전 컨텍스트에서 이어질 수 있는지
가능합니다 — AI 코딩 도구가 세션 재개를 지원하는 한 그렇습니다.
Multica는 작업 중에 세션 ID를 **두 번** 고정합니다: 시작 시 한 번(AI 도구가 첫 번째 시스템 메시지를 반환할 때), 종료 시 한 번(완료 또는 실패 시). 첫 번째는 데몬이 실행 도중 충돌하더라도 복구할 수 있게 해주고, 두 번째는 다음 **자동 재시도**를 위해 예약되어 그 ID를 다시 전달함으로써 에이전트가 이전 대화와 파일 상태를 이어받을 수 있게 합니다. **수동 재실행은 의도적으로 이 단계를 건너뛰고** 새 세션을 시작합니다 — [수동 재실행 vs. 자동 재시도](#manual-rerun-vs-automatic-retry)를 참고하세요.
하지만 **실제로 어떤 AI 코딩 도구가 이를 지원하는지**는 크게 다릅니다:
- ✅ **실제 지원** — Antigravity, Claude Code, Copilot, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, Pi
- ⚠️ **코드는 있지만 사용 불가** — Codex, Cursor
- ❌ **지원 안 함** — Gemini
[제공자 매트릭스 → 세션 재개](/providers#session-resumption-who-really-supports-it)를 참고하세요.
## 다음
- [제공자 매트릭스](/providers) — 12개 AI 코딩 도구 간의 기능 차이(정확한 세션 재개 상태 포함)
- [에이전트에게 이슈 할당하기](/assigning-issues) / [댓글에서 에이전트 @-멘션하기](/mentioning-agents) / [채팅](/chat) / [오토파일럿](/autopilots) — 작업을 트리거하는 네 가지 방법

View File

@@ -0,0 +1,279 @@
---
title: 문제 해결
description: Multica를 자체 호스팅할 때 흔히 겪는 문제 — 증상, 원인, 진단 방법, 해결 방법.
---
import { Callout } from "fumadocs-ui/components/callout";
증상으로 문제를 찾아보세요. 각 항목은 **증상 / 가능한 원인 / 진단 방법 / 해결 방법**을 제공합니다. 여러분의 상황이 목록에 없다면 [GitHub](https://github.com/multica-ai/multica/issues)에 이슈를 등록하세요.
## 데몬이 서버에 연결되지 않음
**증상**: [`multica daemon`](/cli)의 `status` 명령이 `offline` 또는 `connection refused`를 표시합니다. 서버 로그에 `/api/daemon/register`나 `/api/daemon/heartbeat` 요청이 보이지 않습니다. 데몬 메커니즘이 어떻게 동작하는지는 [데몬과 런타임](/daemon-runtimes)을 참고하세요.
**가능한 원인**:
1. **`MULTICA_SERVER_URL`이 잘못된 주소를 가리킴** — 기본값은 `ws://localhost:8080/ws`이며, 자체 호스팅 시 여러분의 서버 주소로 변경해야 합니다
2. **네트워크 / 방화벽 차단** — 데몬과 서버가 같은 네트워크에 있지 않거나, 아웃바운드 트래픽이 차단됨
3. **토큰이 만료되었거나 유효하지 않음** — `multica login`을 한 번도 실행하지 않았거나, PAT이 취소됨
4. **서버가 등록을 거부함** — 로그인한 계정이 대상 워크스페이스에 속해 있지 않음(register가 403을 반환)
5. **DNS 해석 실패** — 데몬 기기에서 호스트명이 해석되지 않음
**진단 방법**:
```bash
multica daemon logs --lines 100 # look for daemon-side errors
echo $MULTICA_SERVER_URL # confirm the address is set
curl -i http://<server-host>:8080/health # hit the server directly
curl -i http://<server-host>:8080/readyz # include DB + migration readiness
cat ~/.multica/config.json # verify api_token exists
multica workspace list # confirm you're a member of the target workspace
```
**해결 방법**: 위의 각 원인을 하나씩 처리하세요. 가장 흔한 두 가지 해결책은 **`MULTICA_SERVER_URL`을 변경하고 데몬을 재시작**하는 것(`multica daemon restart`)과 **다시 로그인**하는 것(`multica logout && multica login`)입니다.
## 작업이 `queued`에서 멈춤
**증상**: 에이전트에게 이슈를 할당한 뒤 이슈 상태가 곧바로 `in_progress`로 바뀌지만, 오랜 시간이 지나도 페이지에 에이전트 실행의 흔적이 보이지 않습니다. `multica daemon status`는 데몬을 `online`으로 표시합니다.
**가능한 원인**(빈도순):
1. **에이전트 동시 실행 상한 도달** — 이 에이전트의 `max_concurrent_tasks`(기본값 6)가 이미 다른 실행 중인 작업들로 가득 참
2. **같은 이슈에서 같은 에이전트의 다른 작업이 아직 실행 중** — 같은 에이전트 × 같은 이슈는 순차 실행이 강제됩니다(중복 실행 방지)
3. **에이전트가 보관됨** — 보관 후에도 새 작업은 여전히 대기열에 들어가지만 클레임될 수 없으며, 5분 뒤 타임아웃됩니다(code-issue G-01)
4. **데몬이 현재 워크스페이스에 이 런타임을 등록하지 않음** — 데몬을 재시작하거나 UI에서 런타임을 다시 선택하세요
5. **데몬 연결 끊김** — 최근 45초 동안 하트비트가 없었습니다. `daemon status`가 `online`으로 보이는 것은 방금 끊긴 상태를 반영한 것일 수 있습니다
**진단 방법**:
```bash
multica daemon status --output json # runtime list + last_seen_at
multica agent list # check agent archived state
multica issue show <issue-id> # inspect task history
```
서버 측(자체 호스팅)에서는 `"no_tasks"` / `"no_capacity"`를 grep하여 클레임 결과를 확인할 수 있습니다.
**해결 방법**:
- 동시 실행 가득 참 → 실행 중인 작업이 끝나기를 기다리거나, `multica agent update <id> --max-concurrent-tasks 10`으로 상한을 올리세요
- 같은 이슈 순차 실행 → 이전 작업이 끝나기를 기다리거나, 다른 에이전트에게 다시 할당하세요
- 에이전트 보관됨 → `multica agent restore <id>`
- 런타임 미등록 → `multica daemon restart`하면 데몬이 다시 등록합니다
## WebSocket이 연결되지 않음
**증상**: 브라우저 콘솔에 `WebSocket is closed`가 기록됩니다. 페이지에 실시간 업데이트(작업 진행 상황, 댓글, 인박스)가 표시되지 않아 새로고침해야 보입니다. 백엔드 작업은 여전히 실행됩니다.
**가능한 원인**:
1. **Origin 검사 실패** — 여러분의 프런트엔드 도메인이 서버의 CORS 허용 목록에 없습니다. 기본 허용 목록에는 `localhost:3000/5173/5174`만 포함되며, 공개 인터넷에서 자체 호스팅하려면 `FRONTEND_ORIGIN`이 필요합니다
2. **프로토콜 불일치** — `https://` 프런트엔드에는 `wss://`가 필요하고, HTTP는 `ws://`를 사용합니다
3. **리버스 프록시가 WebSocket 업그레이드를 활성화하지 않음** — Nginx / Envoy / HAProxy는 기본적으로 `Upgrade` 헤더를 전달하지 않습니다
4. **JWT 쿠키 만료 또는 누락** — 30일 만료 후 다시 로그인하지 않음
**진단 방법**:
- 브라우저 DevTools → Network → "WS"로 필터링하여 연결 상태와 상태 코드를 확인하세요
- 서버 로그에서 `"rejected origin"` / `"websocket"`을 grep하세요 — origin 문제라면 명시적으로 표기됩니다
- `curl -i http://<server-host>:8080/ws`는 `101 Switching Protocols`를 반환해야 합니다(`Upgrade` 헤더 포함)
**해결 방법**:
- Origin 오류 → 서버의 `.env`에 `FRONTEND_ORIGIN=https://multica.yourdomain.com`을 설정(또는 쉼표로 구분한 `CORS_ALLOWED_ORIGINS`)하고 서버를 재시작하세요
- 프로토콜 불일치 → `FRONTEND_ORIGIN`의 프로토콜이 프런트엔드와 일치하는지 확인하세요
- 리버스 프록시 → Nginx에 `proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";`를 추가하세요
- 쿠키 만료 → 페이지를 새로고침하고 다시 로그인하세요
## 이메일이 수신되지 않음
**증상**: 로그인 또는 초대 수락 중 이메일을 제출했는데, 인박스에도 스팸 폴더에도 인증 코드가 없습니다.
**먼저 서버가 어떤 제공자를 활성 상태로 인식하는지 확인하세요.** 시작 시 백엔드는 다음 중 하나를 출력합니다:
- `EmailService: SMTP relay <host>:<port> from=<addr>` — SMTP 사용(`SMTP_HOST`가 비어 있지 않으면 Resend보다 우선)
- `EmailService: Resend API from=<addr>` — Resend 사용
- `EmailService: DEV mode — codes printed to stdout …` — 제공자가 구성되지 않음
```bash
docker compose -f docker-compose.selfhost.yml logs backend | grep "EmailService:"
```
기대했던 줄이 보이지 않는다면 환경 변수가 프로세스에 도달하지 않은 것입니다 — `.env`와 `docker compose -f docker-compose.selfhost.yml exec backend env | grep -E 'RESEND_|SMTP_'`를 확인하세요. 이 시작 로그 줄에는 자격 증명이 절대 기록되지 않습니다.
### Resend가 활성 제공자일 때
**가능한 원인**:
1. **`RESEND_API_KEY`가 설정되지 않음** — 서버는 조용히 폴백하여 **코드를 자체 stdout에 기록**하며 오류를 내지 않습니다. 프로덕션에서 쉽게 빠질 수 있는 함정입니다
2. **Resend API 키가 유효하지 않거나 할당량 소진** — 서버 로그에 `"failed to send verification code"`가 표시됩니다
3. **`RESEND_FROM_EMAIL`의 도메인이 Resend에서 검증되지 않음** — Resend가 발송을 거부합니다
4. **이메일은 발송되었으나 수신자 ISP가 스팸으로 표시** — Resend 대시보드와 스팸 폴더를 확인하세요
**진단 방법**:
- 서버 로그에서 `"[DEV] Verification code for"`를 grep하세요 — 있다면 Resend가 구성되지 않았고 코드가 stdout에 기록된 것입니다
- [Resend 대시보드](https://resend.com/) → Emails에서 발송 이력을 확인하세요
- `RESEND_FROM_EMAIL`의 도메인이 Resend 콘솔의 "Verified Domains" 목록에 나타나는지 확인하세요
**해결 방법**:
- API 키 누락 → [로그인 및 회원가입 구성 → 이메일 동작 방식](/auth-setup#how-email--verification-code-sign-in-works)을 따라 구성한 뒤 서버를 재시작하세요
- 도메인 미검증 → Resend 콘솔에서 DNS 검증 절차를 진행하세요(SPF / DKIM 레코드 추가)
- 긴급 상황(내부 테스트) → 서버 로그에서 `[DEV]` 아래에 출력된 코드를 복사하세요
### SMTP가 활성 제공자일 때
SMTP 경로는 모든 실패를 실패한 단계와 함께 감싸므로, 서버 로그가 이미 relay가 어느 단계에서 세션을 거부했는지 알려줍니다. `"failed to send verification email"` / `"failed to send invitation email"`을 grep하고 감싸진 오류를 확인하세요:
| 기록된 오류 | 의미 | 해결 방법 |
|---|---|---|
| `smtp dial <host>:<port>: dial tcp …: connect: connection refused` / `i/o timeout` | 백엔드 컨테이너가 relay에 도달할 수 없음 — 잘못된 host, 잘못된 port, 방화벽, 또는 relay가 수신 대기 중이 아님 | 컨테이너 내부에서 `SMTP_HOST` / `SMTP_PORT`가 해석되는지 확인하세요(`docker compose -f docker-compose.selfhost.yml exec backend nslookup <host>` 및 `nc -vz <host> <port>`). Multica를 실행하는 호스트에서 relay로 향하는 방화벽을 여세요 |
| `smtp starttls: x509: certificate signed by unknown authority`(또는 `certificate is not valid for any names`) | relay가 사설 CA / 자체 서명 인증서를 사용하며, 컨테이너의 신뢰 저장소가 이를 거부함 | CA를 컨테이너에 설치하거나, relay가 신뢰할 수 있는 네트워크 구간에서 도달 가능함을 확인한 후에만 `SMTP_TLS_INSECURE=true`를 설정하세요 |
| `smtp auth: 535 5.7.8 Authentication credentials invalid`(또는 `534`/`530`) | `SMTP_USERNAME` / `SMTP_PASSWORD`가 잘못되었거나, relay가 `PLAIN`이 아닌 다른 인증 방식을 요구함 | 메일 관리자에게 서비스 계정 자격 증명을 다시 확인하세요. Exchange 익명 내부 relay의 경우 둘 다 비워 두세요(`SMTP_USERNAME=`, `SMTP_PASSWORD=`) |
| `smtp MAIL FROM: 550 5.7.1 Client does not have permissions to send as this sender` | relay가 `RESEND_FROM_EMAIL`을 봉투 발신자로 수락하지 않음 — 전형적인 Exchange "anonymous users not allowed" 또는 DMARC 정렬 문제 | `RESEND_FROM_EMAIL`을 relay가 수락하는 도메인으로 설정하세요. Exchange에서는 receive connector에서 원본 IP에 `ms-Exch-SMTP-Accept-Any-Sender`를 부여하세요 |
| `smtp RCPT TO <addr>: 550 5.7.1 Unable to relay` | relay의 receive connector가 여러분의 서브넷이 외부 수신자에게 중계하는 것을 허용하지 않음(외부 도메인과 통신하는 익명 내부 relay에서 가장 흔함) | 초대를 내부 수신자로 제한하거나, Multica 호스트의 서브넷을 Exchange "Anonymous Users → Relay" 권한 목록에 추가하세요 |
| `smtp DATA` / `smtp write body` / `smtp end data` | 세션은 수락되었으나 relay가 본문을 폐기함 — 보통 메시지 크기 제한, 콘텐츠 필터링, 또는 전송 중 연결 재설정 때문 | relay 로그에서 동일한 `Message-ID`(로그에는 `<unixnano>@<host>` 형식)를 확인하세요. 필요하면 메시지 크기 제한을 높이세요 |
`MAIL FROM`, `RCPT TO`, `DATA` 오류는 항상 relay의 응답 코드와 함께 기록되므로 반대편의 Exchange / Postfix 로그와 대조할 수 있습니다. 인증 코드와 초대 토큰은 감싸진 오류에 **절대** 포함되지 않습니다.
**진단 방법**:
- 시작 시 `"EmailService: SMTP relay"`를 한 번 grep하고, 런타임 실패에 대해서는 `"failed to send"`를 grep하세요
- 백엔드 컨테이너 내부에서 연결성을 점검하세요: `docker compose -f docker-compose.selfhost.yml exec backend sh -c 'nc -vz $SMTP_HOST $SMTP_PORT'`
- 환경 변수가 프로세스에 도달했는지 확인하세요: `docker compose -f docker-compose.selfhost.yml exec backend env | grep SMTP_`(출력에 비밀번호가 포함되므로 신뢰할 수 있는 셸에서만 실행하세요)
**해결 방법**:
- 잘못된 host / port → `SMTP_HOST` / `SMTP_PORT`를 조정하고 백엔드를 재시작하세요. 지원되는 relay 모드는 [인증 설정 → Option B: SMTP relay](/auth-setup)를 참고하세요
- 인증서 불일치 → relay의 CA를 컨테이너에 설치하거나, 신뢰할 수 있는 네트워크 구간에서 임시로 `SMTP_TLS_INSECURE=true`를 설정하세요
- 인증 실패 → 자격 증명을 다시 확인하세요. 익명 내부 relay의 경우 `SMTP_USERNAME`과 `SMTP_PASSWORD`를 비워 두세요
- `Unable to relay` → 내부 수신자로 제한하거나, Exchange receive connector에서 Multica 호스트의 IP에 중계 권한을 부여하세요
## 고정 로컬 테스트 코드가 동작하지 않음
**증상**: 자체 호스팅 인스턴스에서 `888888` 같은 고정 로컬 테스트 코드로 로그인하려 했지만 `invalid or expired code`로 거부됩니다.
**가능한 원인**(상호 배타적):
1. **`MULTICA_DEV_VERIFICATION_CODE`가 비어 있음** — 고정 코드는 기본적으로 비활성화되어 있습니다
2. **`APP_ENV=production`** — 이것은 **올바른** 프로덕션 구성입니다. 고정 로컬 테스트 코드는 프로덕션에서 무시됩니다
3. **구성된 코드가 6자리가 아님** — 이 단축 코드는 6자리 값만 허용합니다
**진단 방법**:
```bash
cat .env | grep -E 'APP_ENV|MULTICA_DEV_VERIFICATION_CODE'
docker exec <container> env | grep -E 'APP_ENV|MULTICA_DEV_VERIFICATION_CODE'
```
인박스(스팸 포함)에서 실제 인증 코드를 확인하세요.
**해결 방법**:
- 프로덕션에서는 `MULTICA_DEV_VERIFICATION_CODE`를 비워 두고, Resend를 구성하여 실제 코드를 사용하세요
- 로컬 개발이나 내부 테스트의 경우 서버 로그에서 생성된 코드를 복사하거나, `APP_ENV=development`와 `MULTICA_DEV_VERIFICATION_CODE=888888`을 설정하세요 — 공개 인스턴스에서는 고정 코드를 절대 활성화하지 마세요(자세한 내용은 [로그인 및 회원가입 구성 → 고정 로컬 테스트 코드](/auth-setup#fixed-local-testing-codes) 참고)
## 사용량 대시보드가 0으로 유지됨
**증상**: 에이전트가 작업을 완료하고 원시 토큰 사용량이 데이터베이스에 기록되지만, **설정 → 사용량**과 **설정 → 런타임**에서 입력 / 출력 / 비용이 전부 0으로 표시됩니다. 이것은 조용히 발생하는 현상으로 — 백엔드 로그에 오류가 없습니다.
**가능한 원인**:
1. **`rollup_task_usage_hourly()`가 전혀 스케줄링되지 않음** — 사용량 / 런타임 대시보드는 파생 테이블 `task_usage_hourly`에서 읽으며, 이 테이블은 해당 함수로 채워집니다. 번들된 `pgvector/pgvector:pg17` 이미지에는 `pg_cron`이 포함되어 있지 않으며, 백엔드도 프로세스 내에서 rollup을 실행하지 않습니다. 외부 스케줄러 없이 새로 설치한 자체 호스팅에서는 이것이 기본 상태입니다.
2. **`pg_cron`이 설치되었지만 잘못된 데이터베이스를 가리킴** — `pg_cron.database_name`의 기본값은 `postgres`입니다. Multica 데이터베이스 이름이 다르면 스케줄된 작업이 `rollup_task_usage_hourly()`를 전혀 보지 못합니다.
3. **스케줄러는 실행되지만 rollup이 조용히 오류를 냄** — 예를 들어 cron 항목 내부의 DB 역할 / search_path가 잘못됨.
**진단 방법**:
```sql
-- Confirm raw events exist but the hourly table is empty.
SELECT count(*) AS raw_rows FROM task_usage;
SELECT count(*) AS hourly_rows FROM task_usage_hourly;
-- Confirm pg_cron is (or isn't) available.
SELECT * FROM pg_available_extensions WHERE name = 'pg_cron';
SHOW shared_preload_libraries;
-- If pg_cron is installed, check the schedule + last run.
SELECT jobname, schedule, database, active FROM cron.job;
SELECT jobname, status, return_message, start_time, end_time
FROM cron.job_run_details ORDER BY start_time DESC LIMIT 10;
-- Watermark — if this is 1970-01-01, the rollup has never run.
SELECT watermark_at FROM task_usage_hourly_rollup_state;
```
**해결 방법**:
- rollup을 수동으로 한 번 호출하여 동작하는지 확인하세요: `SELECT rollup_task_usage_hourly();` — 대시보드를 새로고침하세요. 숫자가 나타나면 빠진 것은 스케줄러뿐입니다.
- [자체 호스팅 빠른 시작 → 사용량 rollup 스케줄링](/self-host-quickstart#7-schedule-the-usage-rollup-required-for-the-usage-dashboard)에서 지원되는 방식 중 하나를 선택하세요: 외부 cron / systemd-timer / Kubernetes CronJob, 또는 Postgres를 `pg_cron`이 포함된 이미지로 교체.
- 스케줄 설정 이전의 이력이 이미 있다면, 백엔드 컨테이너 내부에서 `backfill_task_usage_hourly`를 실행하여 워터마크 이전의 버킷을 채우세요.
## 마이그레이션 `103`이 `refusing to drop legacy daily rollups`로 실패함
**증상**: `v0.3.4`에서 `v0.3.5+`로 업그레이드할 때 백엔드 컨테이너가 시작되지 않거나(또는 `migrate up`이 중단됨) 다음과 같은 오류가 발생합니다:
```text
ERROR: refusing to drop legacy daily rollups:
task_usage_hourly_rollup_state.watermark_at (1970-01-01 ...) trails
task_usage latest event (...) by more than 01:00:00 — backfill is
incomplete or pg_cron is not running. Run cmd/backfill_task_usage_hourly
(and let pg_cron catch up) before re-running migrate
```
**가능한 원인**: 이것은 마이그레이션 `103`의 fail-closed 가드입니다. `task_usage_hourly`가 원시 `task_usage`를 따라잡을 때까지 레거시 daily rollup 삭제를 거부합니다. 기존 행이 존재하고 rollup 워터마크가 여전히 epoch에 머물러 있을 때 — 즉 아직 어떤 이력도 hourly 테이블로 rollup되지 않았을 때 — 이 가드가 발동합니다.
**해결 방법**:
1. 같은 데이터베이스에 대해 backfill을 실행하세요(멱등하며, 중단해도 안전하고, 다시 실행해도 안전합니다):
```bash
# Docker Compose
docker compose -f docker-compose.selfhost.yml exec backend \
./backfill_task_usage_hourly --sleep-between-slices=2s
# Kubernetes
kubectl -n multica exec deploy/multica-backend -- \
./backfill_task_usage_hourly --sleep-between-slices=2s
```
2. 업그레이드를 다시 실행하세요 — 백엔드 컨테이너를 재시작하는 것으로 충분하며, 마이그레이션은 시작 시 실행됩니다. 이제 가드가 최신 워터마크를 확인하고 `103`을 적용하도록 허용합니다.
3. 워터마크가 계속 진행되도록 지속적인 rollup 스케줄(cron / `pg_cron`)을 설정하세요 — [자체 호스팅 빠른 시작 → 사용량 rollup 스케줄링](/self-host-quickstart#7-schedule-the-usage-rollup-required-for-the-usage-dashboard)을 참고하세요.
`--sleep-between-slices=2s`는 수년 치 이력이 있는 프로덕션 데이터베이스에서 적절한 기본값입니다. 최근 N개월만 보관하고 더 오래된 버킷을 영구히 포기해도 괜찮다면 `--months-back N --force-partial`을 사용하세요.
## 포트 충돌
**증상**: `multica server`나 `multica daemon start`가 `address already in use`로 실패합니다.
**가능한 원인**:
1. **서버 포트 점유됨**(기본값 `8080`)
2. **데몬 health 포트 점유됨**(기본값 `19514`, 프로필마다 해시로 오프셋됨)
3. **Web dev 서버 포트 충돌**(`3000` / `5173`)
4. **포트에 대한 권한 부족**(`< 1024` 특권 포트 바인딩에는 sudo 필요)
**진단 방법**:
```bash
lsof -i :8080 # macOS / Linux
netstat -ano | findstr :8080 # Windows
```
**해결 방법**:
- 충돌하는 프로세스를 종료하거나(`kill -9 <PID>`), `PORT=9000`으로 포트를 변경하세요
- 80 / 443을 사용하려면 → 직접 바인딩하지 말고, 앞에 리버스 프록시(Nginx / Caddy)를 두어 높은 포트로 전달하세요
## 로그를 찾는 위치
| 구성 요소 | 위치 | 명령 |
|---|---|---|
| **데몬** | `~/.multica/daemon.log`(백그라운드 모드) 또는 포그라운드 stdout | `multica daemon logs -f --lines 100` |
| **서버(Docker)** | 컨테이너 stdout | `docker logs -f <container>` |
| **서버(systemd)** | journal | `journalctl -u multica-server -f` |
| **프런트엔드(dev)** | `pnpm dev`를 실행 중인 터미널 | 직접 확인 |
| **프런트엔드(브라우저)** | DevTools → Console | `F12`를 누르세요 |
더 자세한 데몬 로그가 필요하면, 데몬을 백그라운드에서 포그라운드로 옮기세요: `multica daemon stop && multica daemon start --foreground`.

View File

@@ -0,0 +1,56 @@
---
title: 워크스페이스
description: 워크스페이스는 그룹이 협업하는 독립된 공간으로, 모든 이슈, 멤버, 댓글, 에이전트가 하나의 워크스페이스에 속합니다.
---
import { Callout } from "fumadocs-ui/components/callout";
워크스페이스는 **Multica에서 그룹이 협업하는 독립된 공간**으로, 모든 [이슈](/issues), [멤버](/members-roles), [댓글](/comments), [에이전트](/agents)가 하나의 워크스페이스에 속합니다. 로그인 후 보이는 이슈 목록, 멤버 명단, 에이전트 설정은 모두 현재 워크스페이스로 한정되며, **워크스페이스를 전환하면 전체 화면이 교체됩니다**.
## 워크스페이스 생성
워크스페이스를 생성할 때 세 가지가 결정됩니다.
- **워크스페이스 이름** — 멤버에게 보이는 표시 이름입니다. 공백과 비ASCII 문자를 사용할 수 있습니다. 나중에 변경할 수 있습니다.
- **Slug** — 워크스페이스 URL에 사용되는 문자열입니다. 소문자와 숫자만 사용할 수 있으며(`-`로 연결), **생성 후에는 변경할 수 없으므로** 신중하게 선택하세요. slug가 이미 사용 중이거나 시스템 예약어와 겹치면, 생성 화면에서 다른 값을 선택하도록 요청합니다.
- **이슈 접두사** — 워크스페이스 내 모든 이슈 번호의 접두사입니다(`MUL-123`의 `MUL`). 대문자와 숫자만 사용할 수 있으며, 최대 10자입니다.
<Callout type="warning">
**이슈 접두사는 변경하지 마세요.** 이슈 번호는 현재 접두사로 렌더링되므로, 접두사를 변경하면 `MUL-5`가 즉시 `NEW-5`가 됩니다. 모든 외부 링크, Slack 멘션, 댓글 속 과거 참조가 기존 번호와 맞지 않게 됩니다. 이슈 접두사는 "생성 시 정하고 절대 건드리지 않는" 값으로 다루세요.
</Callout>
워크스페이스는 웹 UI에서 생성할 수도 있고, 커맨드 라인에서 생성할 수도 있습니다.
```bash
multica workspace create
```
## 이슈 번호
워크스페이스에서 생성되는 모든 이슈에는 `<접두사>-<숫자>` 형식의 번호가 자동으로 할당됩니다 — `MUL-1`, `MUL-2`, `MUL-3`. 몇 가지 특성은 다음과 같습니다.
- **워크스페이스 내에서 순차적이며 고유함** — 각 워크스페이스는 자체 카운터를 유지하며, 워크스페이스끼리 서로 간섭하지 않습니다.
- **수동으로 지정할 수 없음** — 이슈를 생성할 때는 제목만 입력하며, 번호는 시스템이 할당합니다.
- **삭제해도 회수되지 않음** — `MUL-5`를 삭제해도 다음 새 이슈는 `MUL-5`가 아니라 `MUL-6`입니다.
## 워크스페이스 삭제
워크스페이스 owner만 전체 워크스페이스를 삭제할 수 있습니다. 삭제는 **되돌릴 수 없습니다**.
<Callout type="warning">
워크스페이스를 삭제하면 다음 항목이 한꺼번에 모두 지워집니다.
- 모든 이슈, 프로젝트, 댓글, 반응
- 모든 첨부 파일
- 모든 멤버십과 대기 중인 초대
- 모든 에이전트 설정과 그 작업 기록
**데이터는 복구할 수 없습니다.** 삭제하기 전에 보관해야 할 항목을 내보내세요.
</Callout>
워크스페이스의 마지막 owner인데 워크스페이스에서 손을 떼고 싶다면, 먼저 owner 역할을 다른 멤버에게 이전한 다음, 새 owner(또는 본인)가 삭제 여부를 결정하도록 하세요. [멤버와 역할](/members-roles)을 참고하세요.
## 다음
- [멤버와 역할](/members-roles) — 워크스페이스에 사람을 추가하는 방법과, 세 가지 역할이 각각 할 수 있는 일
- [이슈와 프로젝트](/issues) — 워크스페이스 내부의 핵심 작업 객체

View File

@@ -1,9 +1,9 @@
import { defineI18n } from "fumadocs-core/i18n";
// English is the default; Chinese is available under /zh/.
// English is the default; Chinese (/zh/) and Korean (/ko/) are available.
// hideLocale: 'default-locale' keeps English URLs prefix-free
// (`/docs/`) while translated locales live under `/docs/<lang>/...`.
// parser: 'dot' picks up `page.zh.mdx` and `meta.zh.json`.
// parser: 'dot' picks up `page.zh.mdx` / `page.ko.mdx` and `meta.<lang>.json`.
export const i18n = defineI18n({
languages: ["en", "zh", "ko"],
defaultLanguage: "en",
@@ -12,10 +12,3 @@ export const i18n = defineI18n({
});
export type Lang = (typeof i18n.languages)[number];
// Korean docs routes are enabled before the full MDX corpus is translated.
// Until `*.ko.mdx` files exist, render the English source with Korean docs
// chrome so `/docs/ko/...` remains a stable locale URL instead of 404ing.
export function docsContentLang(lang: Lang): Lang {
return lang === "ko" ? "en" : lang;
}

View File

@@ -35,7 +35,7 @@ beforeEach(() => {
});
describe("docsAlternates", () => {
it("omits Korean hreflang when the page only renders via English fallback", async () => {
it("omits Korean hreflang when no Korean MDX file exists for the page", async () => {
const { docsAlternates } = await import("./site");
expect(docsAlternates(["agents"])).toEqual({
@@ -48,12 +48,27 @@ describe("docsAlternates", () => {
});
});
it("omits Korean hreflang even when Fumadocs returns a fallback page for Korean", async () => {
it("omits Korean hreflang even when source.getPage returns a page for Korean", async () => {
const { docsAlternates } = await import("./site");
expect(docsAlternates(["agents"]).languages).not.toHaveProperty("ko");
});
it("includes Korean hreflang when a real *.ko.mdx page exists", async () => {
existingDocs.add("agents.ko.mdx");
const { docsAlternates } = await import("./site");
expect(docsAlternates(["agents"])).toEqual({
canonical: "https://www.multica.ai/docs/agents",
languages: {
en: "https://www.multica.ai/docs/agents",
zh: "https://www.multica.ai/docs/zh/agents",
ko: "https://www.multica.ai/docs/ko/agents",
"x-default": "https://www.multica.ai/docs/agents",
},
});
});
it("keeps the locale root alternates limited to real localized MDX pages", async () => {
const { docsAlternates } = await import("./site");

View File

@@ -1,13 +1,24 @@
import { describe, expect, it } from "vitest";
import { docsSlugStaticParams, type DocsStaticParam } from "./static-params";
import { docsSlugStaticParams } from "./static-params";
// `source.generateParams()` hands back loosely-typed params (`lang: string`),
// so the inputs here mirror that shape — the `lang` strings are validated and
// narrowed by `docsSlugStaticParams` itself.
type RawParam = { lang: string; slug: string[] };
describe("docsSlugStaticParams", () => {
it("adds Korean fallback params for every English slug page", () => {
const params: DocsStaticParam[] = [
it("returns every localized slug page and drops the home param", () => {
// Each locale's pages come straight from `source.generateParams()` now
// that `*.ko.mdx` files exist — Korean is a first-class locale, not an
// English fallback. The only transform is dropping the empty-slug home
// param (rendered by `[lang]/page.tsx`, not the catch-all route).
const params: RawParam[] = [
{ lang: "en", slug: [] },
{ lang: "en", slug: ["agents"] },
{ lang: "en", slug: ["cli", "reference"] },
{ lang: "zh", slug: ["agents"] },
{ lang: "ko", slug: ["agents"] },
{ lang: "ko", slug: ["cli", "reference"] },
];
expect(docsSlugStaticParams(params)).toEqual([
@@ -19,17 +30,15 @@ describe("docsSlugStaticParams", () => {
]);
});
it("keeps existing localized params and does not duplicate Korean pages", () => {
const params: DocsStaticParam[] = [
{ lang: "en", slug: ["agents"] },
it("drops unknown languages and de-duplicates repeated params", () => {
const params: RawParam[] = [
{ lang: "ko", slug: ["agents"] },
{ lang: "zh", slug: ["guides", "quickstart"] },
{ lang: "ko", slug: ["agents"] },
{ lang: "fr", slug: ["agents"] },
];
expect(docsSlugStaticParams(params)).toEqual([
{ lang: "en", slug: ["agents"] },
{ lang: "ko", slug: ["agents"] },
{ lang: "zh", slug: ["guides", "quickstart"] },
]);
});
});

View File

@@ -1,4 +1,4 @@
import { docsContentLang, i18n, type Lang } from "@/lib/i18n";
import { i18n, type Lang } from "@/lib/i18n";
export type DocsStaticParam = {
lang: Lang;
@@ -37,15 +37,5 @@ export function docsSlugStaticParams(
for (const param of slugParams) addParam(param);
for (const lang of i18n.languages) {
const contentLang = docsContentLang(lang);
if (contentLang === lang) continue;
for (const param of slugParams) {
if (param.lang !== contentLang) continue;
addParam({ lang, slug: param.slug });
}
}
return output;
}

View File

@@ -54,7 +54,7 @@ export const homeCopy = {
},
ko: {
eyebrow: "Multica 문서",
titleLead: "사람과 agent,",
titleLead: "사람과 에이전트,",
titleAccent: "한곳에서.",
byline: ["시작하기", "2026년 4월 업데이트", "약 6분 읽기"],
},