
Claude Code, Codex, Gemini CLI를 병렬로 지휘하는 Rust 기반 AI Agent Cockpit을 OSS로 만들었다
요약
여러 AI 에이전트(Claude Code, Codex, Gemini CLI 등)를 병렬로 관리하고 제어할 수 있는 Rust 기반의 오픈소스 AI 에이전트 조종석(Cockpit)인 'Zaivern Code'를 소개합니다. 사용자는 여러 터미널 탭을 오가는 번거로움 없이 하나의 인터페이스에서 에이전트들의 상태를 모니터링하고 명령을 브로드캐스트할 수 있습니다.
핵심 포인트
- Rust 기반의 네이티브 바이너리로 가볍고 빠른 성능 제공
- Claude Code, Codex, Gemini CLI 등 다양한 에이전트의 병렬 실행 지원
- 실제 PTY 기반의 라이브 터미널 인터페이스로 에이전트와 직접 상호작용 가능
- 여러 에이전트에게 동시에 명령을 내리는 브로드캐스트 기능 지원
- Apache-2.0 라이선스의 오픈소스 프로젝트
왜 만들었는가 — 병목 현상은 어느샌가 나 자신이었다
새벽 2시. 문득 터미널 탭을 전환하다가 멈칫했다.
Claude Code가 승인 대기 상태로 30분 동안 멈춰 있었다.
Do you want to proceed?
이 한 줄 때문에, 피로도 불평도 모르는 에이전트가 30분 동안 성실하게 인간을 기다리고 있었다. 옆 탭에서는 Codex가 이미 테스트 작성을 마쳤고, 그 옆에서는 Gemini CLI가 문서 작성을 이어가길 기다리고 있다. 잘못한 사람은 누구인가. ……아무리 생각해도 나다.
이 순간, 명확하게 깨달아 버렸다.
개발의 병목 현상(Bottleneck)은 이제 코드를 쓰는 속도가 아니다. 에이전트를 "기다리게 만드는" 인간 쪽이다.
Claude Code에게 구현을 맡기고, Codex에게 테스트를 쓰게 하고, Gemini CLI에게 문서를 정리하게 하는——그런 개발은 이제 미래의 이야기가 아니다. 에이전트는 빠르다. 병렬로 움직인다. 지치지 않는다. 그런데 정작 내 손에 있는 것은 흩어진 터미널 탭뿐이다.
- 어떤 에이전트가 작동 중이고 어떤 것이 멈춰 있는지 한눈에 알 수 없다
- "그 방향으로 OK, 계속 진행해"라는 말을 3개의 탭에 3번씩 붙여넣기 하며 돌아다니는 무의미한 왕복
- 그리고 승인 대기 상태로 방치되어 있던 세션을 발견한 순간의 그 허탈함
나는 어느샌가 "코드를 쓰는 사람"이 아니라 "에이전트 무리를 관리하는 사람"이 되어 있었다. 그런데 그 관리를 위한 오피스가 없다. 어지러운 탭의 바다에서 육안으로 순찰을 돌고 있다.
없다면 만들 수밖에 없다. 그것도 관리 화면이 무거우면 본말전도이기에, Rust로.
그렇게 탄생한 것이 Zaivern Code입니다.
Zaivern Code란
Claude Code, Codex, Gemini CLI를 병렬로 지휘하는 Rust 기반의 AI Agent Cockpit입니다. 에디터도 터미널도 아닌 "조종석(Cockpit)". Zed의 속도 × Cmux의 병렬 에이전트 × AGI Cockpit의 조종석 UX를 지향했습니다.
30초 만에 체험할 수 있습니다:
# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/tacyan/zaivern-code/main/install.sh | sh
# Windows (PowerShell)
...
실행은 zai [워크스페이스 경로].
Electron도 Node도 사용하지 않는 네이티브 바이너리 하나입니다. 소스 코드는 약 15,000행의 Rust이며, Apache-2.0 라이선스로 전부 공개하고 있습니다.
할 수 있는 일
1. 복수 PTY의 병렬 실행 — 전군을 일망타진
Cockpit을 누르면 실행 중인 모든 에이전트가 그리드 형태로 나열됩니다.
여기서 강조하고 싶은 점은, 각 셀이 단순한 미리보기가 아니라 그대로 타이핑할 수 있는 진짜 라이브 터미널이라는 것입니다. portable-pty + vt100 기반의 진짜 PTY이므로, Claude Code의 풀스크린 TUI가 그대로 작동합니다. 256색/TrueColor, 브래킷 페이스트(Bracket Paste), 스크롤백, 일본어 IME(변환 중인 미확정 문자는 커서 위치에 오버레이 표시되며, 확정된 부분만 에이전트에게 전송됨)를 지원합니다.
Claude Code가 구현을 진행하고, Codex가 테스트를 수정하며, Gemini CLI가 README를 쓰는——5개의 에이전트가 동시에 움직이는 화면을 처음 보았을 때, 솔직히 조금 소름이 돋았습니다. "아, 이게 내가 보고 싶었던 풍경이다"라고.
세션마다 가동 상태(●/○)·가동 시간·재시작·강제 종료 기능이 있으며, 앱을 닫으면 자식 프로세스는 모두 자동으로 kill 됩니다. 고아 프로세스(Orphan Process)는 남기지 않습니다.
2. 브로드캐스트(Broadcast) — 한마디로 모두에게 명령
"테스트가 통과하는지 확인해줘", "그 방향으로 OK, 계속 진행해"
하나의 입력창에서 가동 중인 모든 세션으로 동시에 전송할 수 있습니다. 탭을 옮겨 다니며 같은 문장을 붙여넣기 하던 밤은 이것으로 끝입니다. 사소한 기능처럼 보일 수 있지만, 체감상 가장 크게 변한 것은 이것일지도 모릅니다. 지시 사항을 복사해서 붙여넣는 것은 지휘가 아닙니다.
3. 승인 대기 감지 — 호출되면 반드시 알아챈다
Zaivern의 핵심입니다. 에이전트가 승인을 요청한 순간——
- 팝업이 뜨고 효과음이 울리며
- 세션의 ● 표시가 노란색으로 변하고
- 윈도우가 포커스되지 않은 상태라면 OS 알림 센터로 전송되며
- 그리고 화면 구석을 걸어 다니는 데스크톱 펫 「🦀자이가니 (Zaigani)」가 「❗승인 대기 중」이라며 안절부절못하기 시작합니다.
자이가니의 머리 위 말풍선에서 ✔승인 / ✖거절을 원클릭. 성공하면 🎉와 함께 점프하고, 실패하면 💥와 함께 X 표시가 뜹니다.
"진지한 도구에 왜 펫이 필요해?"라고 생각할지도 모릅니다. 저도 만들기 전에는 그렇게 생각했습니다. 하지만 실제로 운용해 보면 알게 됩니다. 차가운 로그를 눈으로 계속 감시하는 것과, 파트너가 어깨를 톡톡 쳐주는 것은 정신적인 부하가 완전히 다릅니다. 에이전트를 단 1초도 기다리게 하지 않는 개발은 상상보다 훨씬 기분이 좋습니다.
또한 세 가지 권한 모드가 있습니다.
- ⚡전자동 (Full-Auto): 각 CLI의 bypass 플래그(
--dangerously-skip-permissions등)를 자동으로 부여하고, 그럼에도 남는 대화형 프롬프트(최초 경고, 폴더 신뢰 확인, 플랜 승인 등)는 화면 텍스트를 감지하여 자동으로 응답하는 2단계 구조 - 🛡승인 (Approval): 반대로, 커맨드에 섞여 들어온 bypass 계열 플래그(
--yolo나--full-auto등)를 자동으로 제거하여 안전한 쪽으로 기울어지게 설정 - 🤖에이전트 우선 (Agent-First): 프리셋에 작성한 플래그를 그대로 존중
공격할 때는 공격하고, 방어할 때는 방어합니다. 속도와 안전은 양자택일의 문제가 아니라는 것이 Zaivern의 답입니다. 툴바에서 원클릭으로 전환할 수 있으며, 실행 중인 세션에도 일괄 전송할 수 있습니다.
4. 스마트폰 리모트 — 자리를 떠나도 지휘는 계속된다
상단 바의 📱에서 QR 코드를 읽는 것만으로, 동일한 Wi-Fi 내의 스마트폰이 리모컨이 됩니다.
소파에서, 베란다에서, 커피를 내리면서——승인도, 지시도, 파일 편집도, 터미널 열람도. 에이전트가 일하는 동안 인간이 책상에 묶여 있어야 할 이유는 이제 없습니다.
보안은 실행 시마다 랜덤하게 생성되는 토큰으로 인증(QR URL에 포함됨)하며, 토큰 없는 API 액세스는 401 거부, LAN 내부에서만 작동합니다. 그리고 이 HTTP 서버는 의존성 크레이트(crate) 추가 없이 std::net만으로 작성했습니다(이유는 후술).
5. 에디터와 LSP — 마지막 한 획은 자신의 손으로
AI가 9할을 쓰는 시대라도, 마지막 1할——설계의 핵심, 이름 짓기, 책임을 지는 한 줄——은 인간의 것이라고 믿습니다. 그래서 지휘관의 의자 바로 옆에 날카로운 펜을 놓았습니다.
- syntect를 이용한 구문 강조 (Rust/TS/Python/Go/Markdown 등)
rust-analyzer/typescript-language-server/pyright/gopls가 PATH에 있으면 자동으로 실행하여 진단 표시. 행 번호 거터(gutter)가 빨강/노랑으로 색칠되며, 상태 표시줄에⛔건수 ⚠건수표시- Git 행 거터 (초록=추가/노랑=변경), 퍼지 팔레트 (⌘P), 파일 내 검색 (⌘F)
- VS Code와 동등한 파일 조작 및 스크롤 (고정 거터, scrollBeyondLastLine, PageUp/PageDown)
- 덤으로, 셸(shell)을 쓸 줄 안다면 누구나 만들 수 있는 플러그인 시스템 (
.zvplug로 배포 가능), VS Code 호환 스니펫 및 테마 JSON 대응
파일 트리 우클릭에 「🤖 경로를 에이전트에게 전송(@path)」이 있는 것이 Zaivern다운 부분입니다. 에디터와 에이전트가 하나로 이어져 있습니다.
기술 구성
| 역할 | 채택 |
|---|---|
| 언어 | Rust (1.88+) |
| ... | std::net 만을 사용한 초소형 HTTP/1.1 서버 |
직접적인 의존성은 단 12개의 크레이트뿐입니다. tokio도 hyper도 axum도 들어있지 않습니다.
이것은 억지로 줄인 것이 아니라 설계 사상입니다. 콕핏은 계기판이어야 하며, 계기판 자체가 무거우면 말이 되지 않습니다. Electron 탭 하나보다 가벼운 메모리로, 실행은 순식간에, 5개의 에이전트 화면을 60fps로 계속 그려냅니다. 이를 위해 네이티브 바이너리 하나에 모든 것을 담았습니다.
기술적으로 어려웠던 점
솔직히 말하면 "터미널을 GUI에 임베딩하는 것뿐이겠지"라고 생각했습니다. 안일했습니다. 아래는 피와 눈물의 기록입니다.
1. PTY와 UI의 동기화
egui는 immediate mode GUI로, 기본적으로 "무언가가 일어났을 때만" 재그리기를 합니다. 반면 PTY의 출력은 UI의 사정 따위는 아랑곳하지 않고 별도의 스레드로 흘러 들어옵니다.
구성 방식은 다음과 같습니다.
PTY 읽기 스레드 UI 스레드(매 프레임)
┌─────────────────────┐ ┌──────────────────────┐
│ read()로 8KB씩 읽음 │ Mutex │ parser.screen()의 │
...
핵심은 ctx.request_repaint()
한 줄입니다. 이것을 잊으면, 에이전트의 출력이 "마우스를 움직였을 때만" 화면에 반영되는, 그야말로 한심한 동작이 됩니다 (Immediate mode GUI에서 흔히 발생하는 문제입니다. 처음 접했을 때 "에이전트가 느린가?"라고 진심으로 의심했던 제 자신에게 미안할 정도입니다).
일본어 IME도 난관이었습니다. 변환 중인 미확정 문자를 그대로 PTY로 보내면, 에이전트에게 "にほんg"와 같은 파편이 전달됩니다. Zaivern에서는 미확정 문자열(preedit)을 UI 측만의 상태로 유지하고, 커서 위치에 밑줄과 함께 오버레이로 그려서, 확정된 부분만 PTY로 기록하도록 구현했습니다. 터미널에서 일본어를 제대로 입력할 수 있는지 여부는, 일본어 사용자가 에이전트에게 지시를 내리는 도구로서 타협할 수 없는 부분이었습니다.
2. 외부에서 변경된 파일의 자동 반영
이것은 실제로 겪어보고 나서야 깨달은 문제입니다.
에이전트가 파일을 수정합니다. 하지만 에디터에서 열려 있는 탭은 이전 내용 그대로입니다. 이를 인지하지 못한 채 이전 버퍼를 ⌘S로 저장하면——에이전트가 한 일을 인간이 덮어써서 지워버리는, 본말전도 중의 본말전도가 일어납니다. AI와 인간의 공동 편집에서는, 에디터가 "외부 변경이 당연히 존재하는 세계"를 전제로 다시 만들어질 필요가 있었습니다.
구현은 각 버퍼에 disk_mtime을 기록하고 약 1초마다 폴링(polling)하는 방식입니다:
- 저장되지 않은 편집이 없는 버퍼 → 디스크 내용으로 자동 리로드(토스트 알림 + LSP로 재통지)
- 저장되지 않은 편집이 있는 버퍼 → 덮어쓰지 않고, 단 한 번만 경고(매초 경고를 계속하면 신경쇠약에 걸릴 테니까요)
가장 고생했던 부분은 자가 복제식 오탐지입니다. 자신이 ⌘S로 저장하면 당연히 mtime이 변합니다. 그것을 "외부에서 변경되었다!"라고 감지하여 자신의 저장을 스스로 리로드하는, 무한 루프 직전의 동작이 됩니다. 저장 경로(⌘S, 스마트폰 원격 저장, 플러그인의 on_save 포맷팅) 모두에서 mtime을 다시 기록하도록 하여 겨우 해결했습니다. 이 부분은 유닛 테스트(Unit Test)를 추가하여, 현재 리포지토리 전체에서 129개의 테스트가 통과하고 있습니다.
3. 승인 프롬프트 감지
Claude Code를 포함한 각 CLI에는 "지금 승인 대기 중입니다"를 외부로 알리는 API가 (적어도 범용적으로는) 없습니다. 단서는 화면에 그려진 텍스트뿐입니다.
그래서 Zaivern는 약 1초마다 VT100의 화면 내용을 가져와 패턴 분류를 수행합니다. 실제 코드는 다음과 같습니다:
/// 전자동 YES 모드용: 화면의 승인 프롬프트를 분류하여, 보낼 키 시퀀스와 설명을 반환한다.
pub fn auto_yes_reply(text: &str) -> Option<(&'static [u8], &'static str)> {
// 최초의 bypass 경고: 기본 선택이 "1. No, exit"이므로
...
투박하냐고요? 네, 투박합니다. 하지만 이 투박함에는 하나하나 이유가 있습니다.
가장 큰 함정은 첫 번째 분기입니다. Claude Code의 bypass 경고는 **기본 선택이 "1. No, exit"**입니다. 즉, "승인 = Enter"라고 생각 없이 구현하면, 자동 승인을 했다고 생각했는데 세션을 종료시켜 버리는 최악의 동작이 됩니다. 그래서 프롬프트를 분류하여 "2"를 보내야 할 상황에서는 "2"를 보냅니다. Pet의 버블(bubble) ✔ 승인 버튼도 이 동일한 분류기를 거친 후 전송할 키를 결정합니다.
그 외에도:
- 동일한 프롬프트에 대한 연타 방지 (응답이 올 때까지 2초의 유예를 두되, 화면에 계속 남아 있는 한 재전송을 놓치지 않기 위한 대책)
- "새롭게 승인 대기 상태로 전이되었을 때만" 알림을 보내는 상태 관리 (매초 알림이 울리면 지옥이 됩니다)
Do you want라는 문자열이 우연히 코드 중에 표시되었을 뿐인데 오작동하지 않도록 하는 조건의 조합
들을 쌓아 올렸습니다. 그리고 이 경로는, 실제로 PTY를 열고 더미 승인 프롬프트를 출력하는 자식 프로세스를 실행하여, 감지 → 승인 키 전송 → 처리 계속까지 이어지는 E2E 테스트로 보호하고 있습니다. 화면 텍스트의 휴리스틱(Heuristics)은 테스트가 없으면 반드시 부패하기 때문입니다.
4. 멀티 OS 대응
「Rust니까 크로스 플랫폼은 식은 죽 먹기겠지」 —— 절반은 정답이었고, 절반은 함정이었습니다.
- PTY는 OS마다 별개입니다 (Unix의 pty / Windows의 ConPTY). 이 부분은
portable-pty가 흡수해 줍니다. 정말 감사합니다. - 커맨드는 로그인 쉘 (
$SHELL -lc)을 통해 실행합니다. 이렇게 하면 사용자의 PATH나 alias가 그대로 적용됩니다. "터미널에서는 작동하는데 Zaivern에서는 claude를 찾을 수 없다"는 문제를 해결하기 위해서입니다. - 가장 웃겼던 것은 macOS 인스톨러입니다. Rosetta 쉘 환경에서는
uname -m이 x86_64를 반환하기 때문에, Apple Silicon임에도 불구하고 x86_64 바이너리를 다운로드하게 됩니다.sysctl로 실제 CPU를 확인하여 올바른 아키텍처를 선택하도록 수정했습니다. - 스마트폰 리모트를
std::net만으로 작성한 것도 이와 관련이 있습니다. 비동기 런타임 (Asynchronous runtime)을 탑재하면 편하겠지만, 의존성이 늘어날수록 OS × 아키텍처 조합에서 빌드가 깨지는 경우가 많아집니다. 요청량 측면에서 스레드(Thread) + mpsc 채널로도 충분했기에, UI 스레드와는 채널로 대화하고request_repaint()로 깨우는 소박한 설계로 결정했습니다.
릴리스는 macOS arm64/x86_64 · Linux x86_64/arm64 · Windows x86_64의 빌드된 바이너리를 배포하고 있으며, 동일한 코드 그대로 빌드할 수 있습니다.
기존 도구와의 차이점
"tmux면 충분하지 않아?" —— 처음에 반드시 듣게 되는 질문이기에 정면으로 답하겠습니다.
| 터미널 탭 / tmux | VS Code + 터미널 | Zed | Zaivern Code |
|---|---|---|---|
| 병렬 에이전트의 가시성 | △ 페인(Pane) 숙련도 필요 | △ 탭 전환 | △ |
| 브로드캐스트 (Broadcast) | △ synchronize-panes | ✕ | ✕ |
| ... | |||
| (집필 시점의 일반적인 사용법에 기반한 비교입니다. 각 도구에 대한 존중을 전제로 합니다.) |
tmux는 위대합니다. 하지만 tmux는 "터미널을 나열하는 도구"이지, "에이전트를 지휘하는 도구"가 아닙니다. 승인 대기 상태를 감지하여 어깨를 두드려주지도 않고, bypass 플래그를 안전한 방향으로 벗겨주지도 않으며, 스마트폰에서 ✔ 승인을 할 수도 없습니다.
반대로 VS Code나 Zed는 훌륭한 에디터이지만, 주인공은 어디까지나 "인간이 작성하는 코드"입니다. Zaivern의 주인공은 에이전트의 무리이며, 에디터는 지휘관의 옆에 놓인 펜에 불과합니다. 이 주객전도가 기존 도구들과의 가장 본질적인 차이라고 생각합니다.
설치
# macOS / Linux — 빌드된 바이너리를 자동 취득 (없으면 소스에서 빌드)
curl -fsSL https://raw.githubusercontent.com/tacyan/zaivern-code/main/install.sh | sh
# Windows — PowerShell
...
실행은 zai ~/dev/my-project 입니다. 각 OS의 빌드된 바이너리는 Releases에서 직접 다운로드할 수도 있습니다.
에이전트 프리셋은 ~/.zaivern/config.toml에 원하는 만큼 추가할 수 있습니다:
[[agents]]
name = "Claude Code"
icon = "🤖"
...
향후 계획
로드맵 (구현 완료 포함):
- 권한 모드 3종 + 실행 중인 세션으로의 일괄 전환
- 승인 프롬프트 감지 · 자동 응답 (E2E 테스트 포함)
- 스마트폰 리모트
- LSP 진단 · Git 차이점(diff) 게이터 · 세션 복원
- 플러그인 시스템 (커맨드/테마/스니펫/
.zvplug배포) - 외부 변경 파일의 자동 반영
- LSP 완성 · 호버(Hover) UI (기반은 구현 완료, 표시는 앞으로 진행)
- 인라인 diff 뷰
- 스플릿 에디터
- 플러그인의 TextMate grammar 대응 · 레지스트리 공유
참고로 이 도구는 커밋 로그를 보시면 아시겠지만, Zaivern 위에서 에이전트를 지휘하면서, 에이전트와 함께 만들고 있습니다. 에이전트가 작성한 파일을 에디터가 자동으로 리로드하는 기능은, 바로 그 개발 중에 "에이전트의 작업을 자신의 ⌘S로 날려버렸던" 고통에서 탄생했습니다. 도그푸딩(Dogfooding)이라기보다, 이미 공동생활에 가깝습니다.
마지막으로, 아주 조금만 진심을 전하자면.
에이전트가 인간을 기다리는 시간은 앞으로 개발 과정에서 가장 큰 낭비가 될 것이라고 진심으로 생각합니다. 모델은 매달 더 빨라지는데, 승인을 기다리고 있다는 사실을 깨닫기까지의 30분은 인간 측의 시스템을 바꾸지 않는 한 영원히 30분 그대로일 것이기 때문입니다.
Zaivern Code는 그 30분을 알림 1초와 원클릭으로 바꾸기 위한 도구입니다. 아직 거친 부분도 있습니다. 그렇기에 실제로 에이전트를 병렬로 실행하고 있는 분들의 "이 부분이 걸린다", "이런 탐지 패턴을 추가해달라"는 피드백이 지금 가장 절실합니다.
Issue든 X(구 Twitter)든, 주저하지 말고 달려들어 주세요. ⭐(Star)를 주신다면, Zai-gani가 🎉와 함께 점프할 것입니다 (비유가 아니라, 정말로 점프하는 것을 만든 사람보다 더 격하게 점프할 것입니다).
에이전트는 이미 충분히 빠릅니다.
다음에 빨라져야 할 것은, 지휘하는 당신입니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기