tutti-os/tutti · 언어 TypeScript 65% · Go 29% · 데스크탑 Electron 43 + React 19 · 백엔드 Go 데몬 tuttid · 라이선스 Apache-2.0 · 분석 시점 v0.2.2-rc.23 · shallow clone 소스 직접 분석)
요즘은 코딩 에이전트를 한 개만 쓰지 않는다. Claude Code로 백엔드를 짜고, Codex로 프론트를 만들고, 디자인 앱으로 UI 초안을 뽑는다. 문제는 여기서 시작된다. Claude가 API를 끝내면, 이제 Codex에게 그 API 문서를 붙여 넣고, 지금까지 뭘 했는지 설명하고, 왜 그렇게 설계했는지 다시 브리핑해야 한다. 프론트가 시작되기도 전에 쓸 만한 UI를 원하면 디자인·에셋도 필요하니, 또 전체를 요약해 이미지 앱에 넣고, 내려받고, 올리고, 붙여 넣는다. 에이전트가 나 대신 일하라고 있는 건데, 정작 나는 에이전트들 사이를 오가는 메신저가 돼 있다.
Tutti는 이 반복을 끊는다. 맥락·파일·실행 중인 앱·작업이 전부 연결된 실시간 공유 워크스페이스를 두어, Codex가 Claude의 결과물을 맥락 손실 없이 이어받는다. 마치 두 에이전트가 하나의 뇌를 공유하는 것처럼. 그 위에 이미지 생성·UI 디자인·문서·PPT 같은 자체 앱 생태계가 얹혀, 사람도 에이전트도 같은 앱을 호출할 수 있다.
에이전트가 여럿인 지금의 작업은, 각자 다른 방에 있는 전문가들에게 내가 뛰어다니며 말을 전하는 것과 같다. "저쪽 방에서 이렇게 정했어요", "이 파일 좀 전해 주세요" — 일 자체보다 정보를 나르는 일이 사람을 지치게 한다.
Tutti는 전문가들을 하나의 회의실(공유 워크스페이스)에 모으고, 모두가 같은 화이트보드를 실시간으로 본다. 누가 뭘 바꿨는지, 무엇이 돌아가는지, 지금 프로젝트 상태가 어떤지 서로 보인다. 심부름꾼이 사라진다.
중요한 설계 선택이 하나 있다: Tutti는 모델을 되팔지 않는다. 당신이 이미 가진 Claude·Codex 구독 위에서 모든 앱과 에이전트가 추가 비용 없이 돌아간다. 에이전트는 여전히 로컬에서 당신의 구독·설정으로 실행되고, Tutti는 그 위를 감싸는 워크스페이스일 뿐이다. "또 하나의 AI 구독"이 아니라 "이미 가진 구독들을 엮는 판(板)"인 셈이다.
코딩 에이전트 자체는 이미 넘친다(Claude Code, Codex, Cursor, OpenCode…). 그런데 Tutti가 트렌딩에 오른 건 겨냥하는 지점이 다르기 때문이다. 남들이 "더 똑똑한 에이전트"를 만들 때, Tutti는 에이전트와 에이전트 사이의 빈틈을 노렸다. 네 가지 결이 겹친다.
기존 핸드오프는 "이전 대화를 요약해서 다음 에이전트에게 전달"하는 방식이라, 요약하는 순간 디테일이 샌다. Tutti는 맥락·파일·실행 중인 작업·앱을 같은 실시간 상태로 공유한다. 당신의 Codex는 Claude가 무엇을 바꿨는지, 무엇이 돌고 있는지, 프로젝트의 현재 상태를 그대로 본다. 여기서 Big @(과거 대화·파일·앱 호출·작업을 @로 불러오기)와 + 참조(로컬 파일·앱 산출물 끌어오기)가 나온다.
완결된 워크플로는 에이전트 하나로 안 끝난다. 페이지 하나 만들려 해도 프로토타입 → 코드 → 비주얼이 필요하고, 그때마다 다른 툴을 열고 내려받고 올린다. Tutti는 워크스페이스 안에 앱 센터를 두어, 이미지 생성·프로토타입 디자인·문서·PPT 앱을 사람이 직접 쓰거나 에이전트가 호출하게 한다. Codex가 프로토타입 디자인 앱을 @하면, 사실상 Codex가 디자인 능력을 얻고, 그 산출물이 곧바로 개발로 이어진다. 앱 산출물은 전부 같은 워크스페이스에 남아 +로 다음 단계에 참조된다.
Tutti는 특정 벤더에 묶이지 않는다. 내부적으로 에이전트를 딱 3가지 런타임 프로토콜(Claude SDK 사이드카 · Codex 앱서버 · 표준 ACP)로 추상화해, Claude Code·Codex·Cursor·OpenCode 같은 이질적인 에이전트를 같은 워크스페이스에서 협업시킨다. 서로 충돌을 피하거나 해소하고, 병렬로 갈지 순차로 갈지도 스스로 판단한다. "특정 툴에 락인되고 싶지 않은" 개발자에게 정확히 꽂히는 지점이다.
에이전트는 로컬에서 당신의 구독·설정으로 돌고, 작업 상태도 로컬에 남는다(오픈소스판 기준). 모델을 묶어 되파는 대신 이미 가진 Claude·Codex 구독 위에서 추가 비용 없이 앱과 에이전트가 동작한다. 나중에 팀·다기기 협업이 필요하면 클라우드판 Tutti · VM으로 확장하는 2단 구성이다.
실시간 협업이라 하면 흔히 Google Docs식 CRDT(문서 병합 알고리즘)를 떠올린다. 그런데 Tutti 소스에는 yjs·automerge 같은 CRDT가 없다. 대신 "무엇이 바뀌었다"는 작은 신호(signal)만 이벤트로 쏘고, 클라이언트가 HTTP로 권위 있는 최신 상태를 다시 읽는(reconcile) 방식이다. 화려한 알고리즘 대신, "서버가 진실의 단일 출처, 이벤트는 알림일 뿐"이라는 단순하고 견고한 선택을 했다. 이 절제가 오히려 신뢰도를 높인다(§4에서 상술).
| 구분 | 전형적 '에이전트 툴' | Tutti |
|---|---|---|
| 겨냥하는 문제 | 에이전트 하나를 더 똑똑하게 | 에이전트 '사이'의 맥락 전달 |
| 핸드오프 방식 | 대화 요약을 복붙 | 실시간 공유 상태 + @/+ 참조 |
| 다중 에이전트 | 대개 단일 벤더 | Claude·Codex·Cursor·OpenCode 혼합 |
| 앱/도구 | 툴마다 따로 구독·전환 | 워크스페이스 공용 앱 센터(사람+에이전트) |
| 과금 | 모델을 묶어 재판매 | 내 기존 구독 재사용, 추가 비용 0 |
| 데이터 위치 | 클라우드 | 로컬 우선(오픈소스판) |
Tutti는 거대한 모노레포다. 하지만 아무렇게나 큰 게 아니라, TypeScript 워크스페이스와 Go 워크스페이스가 한 저장소 안에 겹쳐 있는 이중 구조다. pnpm이 apps/*·packages/*/*(두 단계!)를 묶고, 동시에 go.work가 16개 Go 모듈을 묶는다. UI는 TypeScript/React가, 백엔드·에이전트 런타임 코어는 Go가 맡는 역할 분담이 명확하다.
스택에서 눈에 띄는 선택 두 가지. 첫째, 린트·포맷·타입체크가 전부 차세대(네이티브) 도구다 — ESLint/Prettier 대신 Rust로 짠 oxlint·oxfmt, TypeScript 컴파일러도 Go로 재작성된 tsgo. 거대 모노레포의 CI 속도를 위한 포석으로 읽힌다. 둘째, SQLite 드라이버가 순수 Go 구현 modernc.org/sqlite라, C 컴파일러 없이 크로스 빌드된다(paxm와 같은 선택 — 데스크탑 배포에 유리).
| 영역 | 선택 | 버전 / 메모 |
|---|---|---|
| 데스크탑 셸 | Electron | ^43.2.0 (+ electron-vite ^3.1 · builder ^26.8 · updater ^6.8) |
| UI 프레임워크 | React | ^19.1.0 · Vite ^6.3.5 |
| 모바일 | React Native | bare(Expo 아님) + NativeWind + Reanimated/Hermes |
| 백엔드 언어 | Go | 1.24.3 (toolchain 1.24.5) · go.work 16개 모듈 |
| DB | modernc.org/sqlite | 1.45.0 · 순수 Go(cgo 없음) · ~/.tutti/tuttid.db |
| Claude 연동 | @anthropic-ai/claude-agent-sdk | 0.3.201 (사이드카가 감쌈) + zod 4 |
| 패키지 매니저 | pnpm | 10.11.0 (workspace 2단계 glob) |
| 런타임 | Node | ≥24 (.node-version=24) |
| 린트·포맷 | oxlint · oxfmt | 1.70.0 · 0.55.0 (JSON/MD/YAML만 Prettier) |
| 계약 코드젠 | OpenAPI · JSON-Schema · Codex export | @hey-api/openapi-ts · 이벤트 스키마 · codexproto |
replace 지시어 없이 로컬 소스로 바로 참조하게 해 준다. Tutti는 services/tuttid, packages/agent/host, packages/device-link 등 16개 Go 모듈을 하나의 워크스페이스로 묶어, TS 모노레포와 Go 모노레포가 한 저장소에서 공존한다.
Tutti의 구조를 한 장으로 압축하면 이렇다. Electron 데스크탑이 화면을 그리고, 그 안에서 Go 데몬 tuttid를 자식 프로세스로 띄운다. 화면(React)은 데몬과 로컬 HTTP/WebSocket으로만 대화하고, 데몬이 모든 비즈니스 로직·에이전트 세션·공유 상태를 쥔다. 에이전트들은 데몬 아래 런타임 어댑터를 통해 붙는다.
가장 인상적인 설계 규율이다. 에이전트의 세션·턴·골(goal) 수명 로직 전부가 packages/agent/host 한 곳에 산다. 데몬 tuttid는 그 코어를 감싸는 어댑터일 뿐 — HTTP를 받아 ApplicationHost()로 넘긴다. 놀라운 건 이 경계를 CI가 빌드 실패로 강제한다는 점이다. 어댑터에 *Coordinator·*Worker 같은 오케스트레이션이 새로 새면 pnpm check:agent-host-boundary가 빌드를 깬다. 새 수명 로직은 반드시 host/conformance에 시나리오부터 추가해야 한다.
같은 에이전트 코어를 오픈소스 데스크탑판과 비공개 클라우드판(Tutti·VM, 코드명 TSH)이 함께 쓰기 때문이다. 코어를 순수하게 지켜야 두 제품이 갈라지지 않는다. "비즈니스 로직은 한 곳, 나머지는 다 어댑터"라는 헥사고날 아키텍처를 말로만이 아니라 CI 게이트로 못 박은 드문 사례다.
데몬은 화면·CLI와 세 갈래로 대화하는데, 각각 역할이 엄격히 분리된다. 이 분리가 실시간의 핵심이다.
/v1/... — OpenAPI로 코드젠된 REST. 권위 있는 상태(authoritative read-model)는 오직 여기서 읽는다./v1/events/ws — "무엇이 바뀌었다"는 작은 신호만 흘린다. 신호는 진짜 데이터가 아니다.
세 채널 모두 데스크탑이 발급한 실행별 Bearer 토큰으로 인증한다. 그 토큰과 접속 주소는 데몬이 부팅 때 <state-dir>/run/tuttid.listener.json에 0600으로 원자적으로 써 두고(임시파일+rename), Electron과 CLI가 그 파일을 읽어 접속한다. 리스너는 127.0.0.1:0(OS가 임의 포트 배정)에 바인딩 — 외부에 열리지 않는 루프백 전용이다.
실시간 공유의 실제 구현이 여기다. Tutti는 CRDT를 쓰지 않는다. 대신 데몬이 agent.activity.updated 같은 "더티 신호"를 이벤트 WS로 쏘면, 클라이언트가 HTTP로 권위 있는 최신 상태를 다시 읽어(reconcile) 맞춘다. 이때 버전 커서(메시지의 afterVersion, 앱별 stateRevision 등)로 "내가 가진 것보다 새 것"일 때만 적용한다. 이벤트는 알림, HTTP 스냅샷이 진실이다.
packages/events/protocol/definitions/, 현재 18개 토픽), 여기서 TS 타입+검증기와 Go 타입+레지스트리를 자동 생성한다.{ id, topic, version, emittedAt, scope?, payload }. 스코프는 global / desktop / workspace 3종.1013 Try Again Later로 끊고, 재접속→재구독→reconcile로 복구한다.tuttid만 권위 이벤트를 발행하고, 클라이언트는 타입이 정해진 의도(intent)만 publish할 수 있다.
provider 중립의 실체다. 어떤 에이전트든 packages/agent/daemon/providerregistry의 RuntimeDescriptor.Kind 3종 중 하나로 붙는다. 에이전트를 추가하려면 분기문이 아니라 ProviderDescriptor 한 개를 선언하면 된다(전략 패턴).
| 런타임 종류 | 대상 에이전트 | 어떻게 붙나 |
|---|---|---|
claude_sdk | Claude Code | Node 사이드카가 공식 claude-agent-sdk를 감싸고, 데몬↔사이드카는 stdio 위 개행 구분 JSON(모든 메시지 version:2) |
codex_app_server | Codex · Tutti Agent | Codex의 app-server(JSON-RPC). Rust export에서 코드젠한 codexproto 바인딩(전체 생성, 쓰는 부분만 배선) |
standard_acp | Cursor · OpenCode · OpenClaw | Agent Client Protocol(ACP) + 벤더별 어댑터 전략(cursor-agent acp 등) |
캐노니컬 provider 정체성은 8개(codex·claude-code·cursor·tutti-agent·opencode·nexight·hermes·openclaw)인데, 실제 런타임이 배선된 건 7개다. README가 지원한다고 적은 hermes는 정체성만 있고 런타임 서술자가 없다 — 실제 배선은 비공개 Tutti·VM 쪽에 있는 것으로 보인다. 이런 "문서와 코드의 미세한 어긋남"까지 소스로 확인해야 정확하다.
@tutti-os/claude-sdk-sidecar라는 Node 프로세스를 옆에 띄워 SDK 호출을 맡긴다. 이유는 환경 호환 — 사이드카가 ~/.claude/settings.json의 env(예: ANTHROPIC_BASE_URL)를 native claude CLI와 똑같이 읽어 병합하기 때문. 그래서 바이너리가 아니라 SDK를 감싼다.
@-멘션 / +-참조의 지연(lazy) 해석. 멘션은 파일 목록·경로·자격증명을 전혀 담지 않는 짧은 URI다 — 예: mention://workspace-reference/<id>(+ source·workspaceId 쿼리). 에이전트는 이걸 쓰는 시점에 주입된 reference 스킬과 tutti CLI로 펼친다. 그래서 멘션을 적은 뒤 파일이 생기거나 지워져도, 해석 결과는 항상 최신 상태를 반영한다.
워크스페이스 앱 = CLI 매니페스트. 이미지 생성·PPT·디자인 같은 앱은 tutti.app.cli.v1 매니페스트로 에이전트가 호출 가능한 명령을 선언한다({schemaVersion, scope, commands:[{path, summary, inputSchema, visibility}]}). 데몬이 이를 광고하고, 얇은 tutti CLI가 데몬이 준 inputSchema로 플래그를 파싱해 구조화된 결과를 돌려준다. "에이전트가 프로토타입 디자인 앱을 호출한다"의 정체가 바로 이 CLI 계약이다.
"골 → 작업" 분해(Tutti Mode)는 목표를 검토 가능한 Markdown 계획으로 바꾼 뒤 Issue로 materialize한다. 여기서 Workspace Issue(Issue·Task·Run·의존성·예산)와 Agent Host(Session·Turn)를 분리하고, IssueExecutionCoordinator가 Issue별 뮤텍스 아래 2단계로 잇는다(내구성 Run 청구 → 락 해제 → 워크트리 준비 + 세션 생성). 취소도 issue-run:<runID> 턴을 정확히 짚어 끊는다. 실행 상태와 대화 상태를 섞지 않은 깔끔한 분리다.
모노레포지만 규칙이 뚜렷하다. apps/는 사용자가 실행하는 껍데기, packages/는 재사용 로직(도메인/이름 2단계), services/tuttid는 비즈니스 두뇌. 패키지 대부분은 TS지만, 여러 도메인이 TS 패키지와 Go 모듈을 나란히 둔다(예: events/stream-core + events/stream-go).
이 레포를 처음 연다면 docs/architecture/business-event-stream.md(실시간 모델) → services/tuttid/main.go(부팅·트랜스포트) → packages/agent/daemon/providerregistry/(에이전트 3종 배선) → docs/architecture/agent-reference-*.md(@-멘션 해석) 순서를 추천한다. "실시간이 어떻게 도는가 → 데몬이 어떻게 뜨는가 → 에이전트가 어떻게 붙는가 → 참조가 어떻게 풀리는가"로 내려가면 큰 그림이 자연스럽게 잡힌다. AGENTS.md·CONTEXT.md는 이 레포를 에이전트가 개발하도록 쓴 지침이라, 설계 의도가 사람 말로 정리돼 있어 먼저 읽어도 좋다.
말로만 "비즈니스 로직은 코어에, 나머지는 어댑터에"가 아니라, check:agent-host-boundary로 경계 위반 시 빌드를 깨는 실전 예다. 대형 코드베이스가 시간이 지나며 무너지는 이유는 대개 "편의상 어댑터에 로직 한 줄"이 쌓여서다. Tutti는 그걸 기계가 막는다. 새 수명 로직은 conformance 시나리오부터 — 테스트가 설계를 이끄는 규율을 배울 수 있다.
"실시간 = CRDT"라는 통념을 깬다. signal-and-reconcile(신호는 이벤트, 진실은 HTTP read-model, 버전 커서로 최신만 적용)은 구현·디버깅이 훨씬 쉽고, 백프레셔(1013)로 느린 소비자를 격리한다. "언제 CRDT가 과하고, 언제 신호+재조회로 충분한가"는 실무에서 계속 부딪히는 판단이다. 그 답의 한 축을 코드로 보여 준다.
Claude(SDK)·Codex(app-server)·Cursor(ACP)는 프로토콜이 전부 다르다. Tutti는 이들을 3개 런타임 종류 + 선언적 서술자(descriptor)로 감싸, 공용 로직에 if provider == ... 분기가 없게 만든다. "제각각인 외부 API들을 하나의 인터페이스로 흡수하는" 어댑터/전략 패턴의 대규모 실전 사례다.
OpenAPI → 클라이언트/서버, 이벤트 JSON-Schema → TS+Go 타입, Codex Rust export → codexproto. 계약(스키마)이 단일 진실이고, TS와 Go 양쪽 타입이 거기서 생성돼 드리프트가 원천 차단된다. check:*-generated CI가 "생성물과 소스가 어긋났는지"까지 감시한다. 폴리글랏(다언어) 시스템에서 타입 안정성을 지키는 정석.
데몬은 PID 파일 리스로 중복 실행을 막고, TUTTI_DESKTOP_PARENT_PID를 500ms마다 getppid()로 감시해 Electron이 죽으면 스스로 종료한다(유령 데몬 방지). 리스너 정보는 임시파일+rename으로 원자적으로 쓰고, 부팅 시 낡은 락도 복구한다. "GUI가 백그라운드 데몬을 안전하게 거느리는" 데스크탑 앱의 정석 패턴이 main.go에 응축돼 있다.
renderer(React)는 데몬에 HTTP/WS로만 접근하고, preload는 타입이 정해진 host 능력만 노출한다. 이 경계도 check:electron-runtime-boundaries로 검사한다. Electron 보안의 핵심(렌더러에 Node 권한을 주지 않기)을 규칙이 아니라 강제로 지키는 구성이다.
주제(에이전트 오케스트레이션)를 지워도 남는 게 많다. 헥사고날 경계의 CI 강제 · 2-plane 트랜스포트 · signal-and-reconcile 실시간 · 계약 우선 코드젠 · 프로세스 감독 · 대형 TS/Go 이중 모노레포 운영. "Electron + Go 데몬으로 진지한 데스크탑 제품을 어떻게 구조화하는가"의 종합 교재로 읽을 수 있다.
Tutti 자체는 모델을 돌리지 않는다. 에이전트가 로컬에서 당신의 Claude·Codex 구독으로 실행되므로, GPU도, 별도 추론 서버도 필요 없다. 필요한 건 에이전트 구독(없으면 Early Access 동안 무료인 Tutti Agent로 시작 가능)과, 데스크탑 앱을 돌릴 평범한 PC다. 다운로드판(Tutti · Local)은 "coming soon" 단계이고, 지금은 소스에서 빌드하는 경로가 열려 있다.
| 필요 | 버전 / 비고 |
|---|---|
| Node.js | 24 이상 (.node-version이 baseline 고정) |
| pnpm | 10.11.0 (corepack 권장) |
| Go | 1.24 이상 (toolchain 1.24.5) |
| C 컴파일러 | 불필요 — 순수 Go SQLite(cgo 없음) |
| 에이전트 | Claude Code · Codex 구독(또는 무료 Tutti Agent) |
# 소스 빌드 & GUI 개발 실행
pnpm install
pnpm setup:dev # golangci-lint 등 개발 도구 설치
make dev-gui # = bash ./tools/scripts/dev-gui.sh
# 데이터 위치: ~/.tutti/tuttid.db (개발: ~/.tutti-dev/)
# 리스너 정보: <state-dir>/run/tuttid.listener.json (0600)
services/tuttid는 Electron 앱의 자식으로 떠서 부모(데스크탑)가 죽으면 함께 종료되도록 설계됐다(getppid() 감시). 그래서 데몬만 따로 실행하면 --help만 보이거나 곧 종료된다. 데몬을 단독 기동해 디버깅하려면 부모 PID 감시 로직(TUTTI_DESKTOP_PARENT_PID)과 리스너 파일 발행 경로를 먼저 이해해야 한다.
TSH가 이 VM 제품의 코드명이다.Node 24·pnpm 10.11·Go 1.24를 준비하고 pnpm install → pnpm setup:dev → make dev-gui로 앱을 띄운다. 그다음 상태 디렉토리의 run/tuttid.listener.json을 열어 바인딩 주소(127.0.0.1:임의포트)와 Bearer 토큰이 어떻게 발행되는지 확인하라. HTTP /v1/..., 이벤트 WS /v1/events/ws, 터미널 WS가 왜 따로인지 services/tuttid/api/routes.go에서 대조한다.
AGENTS.md의 Agent Host Boundary 절을 읽고, services/tuttid/service/agent가 어떻게 로직을 직접 갖지 않고 ApplicationHost()로 위임하는지 추적하라. 그다음 pnpm check:agent-host-boundary를 돌려 보고, 어댑터에 일부러 fooCoordinator 같은 이름을 넣으면 빌드가 깨지는지 실험한다. "규칙을 기계가 지키게 하는" 감각을 체험하는 게 목표.
docs/architecture/business-event-stream.md를 읽고, packages/events/protocol/definitions/에서 workspace.issue.updated 같은 이벤트 정의 JSON 하나를 고른다. 그 스키마가 코드젠으로 packages/events/protocol(TS 타입/검증기)과 services/tuttid/api/events/generated(Go 타입)로 양쪽에 생성되는 걸 확인하라. "스키마가 단일 진실"의 실감.
packages/agent/daemon/providerregistry/types.go와 claude_code.go·codex.go를 읽어 RuntimeDescriptor.Kind 3종(claude_sdk·codex_app_server·standard_acp)의 차이를 정리하라. 그다음 "새 ACP 에이전트를 붙인다면 ProviderDescriptor에 무엇을 채워야 하나"(command·auth·skill root·composer 프로파일)를 종이 설계로 써 본다. 분기문 없이 선언만으로 확장되는 구조를 이해하는 게 핵심.
docs/architecture/agent-reference-*.md를 읽고, mention://workspace-reference/<id>가 파일·경로·자격증명을 담지 않은 채 주입된 reference 스킬 + tutti CLI로 사용 시점에 해석되는 흐름을 정리하라. 함께: 워크스페이스 앱의 tutti.app.cli.v1 매니페스트(packages/appcli/core/manifest.go)가 어떻게 "에이전트가 앱을 호출"로 이어지는지 대조한다. 지연 해석이 왜 "적은 뒤 파일이 바뀌어도 최신"인지 설명할 수 있으면 성공.
| 주차 | 주제 | 할 것 |
|---|---|---|
| 1주차 | 실시간 & 트랜스포트 | business-event-stream.md·desktop-transport.md 정독 → signal-and-reconcile·버전 커서·백프레셔(1013) 이해 → HTTP read-model과 이벤트 WS의 역할 분리 정리 |
| 2주차 | 에이전트 런타임·프로토콜 | providerregistry의 3종 런타임 읽기 → Claude SDK 사이드카(stdio JSON)·Codex app-server(codexproto)·ACP 비교 → ADR 0001(코드젠 접근) 읽기 |
| 3주차 | 도메인 설계·코드젠 | tuttid의 api→service→biz→data 계층 추적 → 이벤트/OpenAPI 코드젠 파이프라인 → Issue(실행)와 Session(대화) 분리, Tutti Mode 흐름 |
| 4주차 | 데스크탑·감독·경계 | main.go의 PID 리스·부모 감시·리스너 발행 → Electron 3계층 경계와 check:*-boundary CI → 헥사고날 경계를 강제하는 방법 재현 |
이 저장소는 "AI 협업"이라는 주제를 잠시 접어 두고 봐도 값지다. 대형 TS/Go 이중 모노레포 운영 · 헥사고날 경계의 CI 강제 · 계약 우선 코드젠 · 실시간을 신호+재조회로 푸는 절제 · GUI가 데몬을 거느리는 프로세스 감독 — 진지한 데스크탑 제품을 구조화하는 정석이 한 레포에 다 있다. Electron/Go로 견고한 앱을 짜고 싶은 사람에게 특히 참고가 된다.
| 용어 | 뜻 |
|---|---|
| Tutti | "전원 합주"를 뜻하는 음악 용어. 여러 AI 에이전트가 한 워크스페이스에서 함께 일하게 하는 로컬 우선 협업 앱 |
| 에이전트 오케스트레이션 | 여러 에이전트를 조율해 각자 잘하는 일을 맡기고 결과를 잇는 것. 핵심 난제는 맥락 전달 |
| 공유 워크스페이스 | 맥락·파일·앱·작업이 실시간으로 연결된 공간. 요약을 넘기는 대신 '상태'를 공유 |
| tuttid | Go로 짠 로컬 데몬. "로컬 제품의 유일한 비즈니스 계층". Electron의 자식으로 실행 |
| agent host boundary | 세션/턴 수명 로직은 packages/agent/host에만, tuttid는 어댑터. CI가 위반을 빌드 실패로 강제 |
| signal-and-reconcile | 이벤트로 "무엇이 바뀌었다" 신호만 쏘고, 클라이언트가 HTTP로 최신 상태를 재조회해 맞추는 실시간 방식(CRDT 아님) |
| 2-plane 트랜스포트 | 권위 상태=HTTP, 라이브 신호=이벤트 WS, 터미널=별도 WS로 채널을 엄격히 분리 |
| 버전 커서 | afterVersion·stateRevision 등으로 "내가 가진 것보다 새 것"일 때만 상태를 적용 |
| 런타임 종류(3종) | claude_sdk(Claude Code)·codex_app_server(Codex)·standard_acp(Cursor·OpenCode 등) |
| 사이드카 | 본체 옆에 붙는 보조 프로세스. Claude SDK 호출을 Node 사이드카가 대신 처리(환경 호환 목적) |
| ACP | Agent Client Protocol — 여러 코딩 에이전트를 표준 방식으로 붙이는 프로토콜 |
| app-server / codexproto | Codex가 노출하는 JSON-RPC 서버. Rust export에서 코드젠한 Go 바인딩이 codexproto |
| @-멘션 / +-참조 | 파일·경로를 담지 않는 짧은 URI를 '쓰는 시점에' 스킬+CLI로 펼쳐 최신 상태를 해석 |
| tutti.app.cli.v1 | 워크스페이스 앱이 "에이전트가 호출 가능한 명령"을 선언하는 CLI 매니페스트 스키마 |
| Tutti Mode | 목표(goal)를 검토 가능한 Markdown 계획으로 바꾼 뒤 Issue로 materialize하는 기능 |
| Issue / Session 분리 | 실행 상태(Workspace Issue: Issue·Task·Run)와 대화 상태(Agent Host: Session·Turn)를 다른 도메인으로 분리 |
| 계약 우선 코드젠 | OpenAPI·JSON-Schema·Codex export를 단일 진실로 두고 TS+Go 타입을 생성해 드리프트 차단 |
| go.work | 여러 Go 모듈을 한 워크스페이스로 묶는 상위 설정. Tutti는 16개 Go 모듈을 묶음 |
| oxc 스택 | Rust로 짠 차세대 JS 도구(oxlint·oxfmt)와 네이티브 TS 컴파일러 tsgo |
| device-link | ICE 후보 협상 + QUIC + 상호 인증서 고정으로 다기기 P2P를 잇는 패키지(Tutti·VM용) |
| Tutti · VM (TSH) | 로컬 에이전트를 실시간 공유 클라우드 Room으로 확장하는 비공개 클라우드판. 코드명 TSH |
services/tuttid/main.go(부팅·감독) · services/tuttid/server/server.go(HTTP·인증) · packages/agent/daemon/providerregistry/(런타임 3종) · packages/agent/claude-sdk-sidecar/(Claude 연동)docs/architecture/business-event-stream.md · docs/architecture/desktop-transport.md · docs/architecture/agent-reference-mention-resolution.md · docs/adr/0001-codex-appserver-codegen-approach.md · AGENTS.md · CONTEXT.md