오케스트레이션

워크트리 격리로 에이전트 충돌 없이 병렬 작업하기

여러 에이전트가 같은 저장소를 동시에 만지면 서로의 변경을 밟습니다. git worktree 격리로 병렬 작업을 안전하게 만드는 법.

문제부터

여러 에이전트를 병렬로 굴릴 때 가장 흔한 사고는 같은 작업 트리를 공유해서 생기는 충돌입니다. 한 에이전트가 파일을 고치는 사이 다른 에이전트가 브랜치를 바꾸거나 같은 파일을 덮으면, 커밋 안 된 작업이 조용히 사라집니다. 에러 메시지 하나 없이 사라지기 때문에, 며칠 뒤 "어제 만든 코드가 왜 없지?"라고 뒤늦게 깨닫는 식으로 문제가 드러납니다.

구체적인 장면을 그려 보겠습니다. 에이전트 A가 결제 API를 리팩터링하는 중입니다. 아직 커밋하지 않은 변경이 작업 디렉터리에 쌓여 있습니다. 같은 순간, 에이전트 B가 같은 저장소에서 git checkout main을 실행해 다른 브랜치로 이동합니다. Git은 브랜치를 바꾸면서 작업 디렉터리의 파일들을 대상 브랜치의 상태로 되돌립니다. A의 미완성 변경 중 스테이징되지 않은 것은 이 과정에서 덮이거나 충돌로 막히고, 최악의 경우 흔적 없이 지워집니다. A는 자기가 무슨 잘못을 했는지도 모른 채 몇 시간의 작업을 잃습니다.

이 사고의 뿌리는 단순합니다. 하나의 작업 디렉터리는 한 시점에 하나의 브랜치·하나의 파일 상태만 가질 수 있습니다. 그런데 병렬 에이전트는 저마다 다른 브랜치, 다른 파일 상태를 원합니다. 한 방을 여러 사람이 동시에 자기 취향으로 새로 꾸미려는 것과 같습니다. 누군가는 반드시 남의 작업을 밟게 됩니다.

왜 공유 작업 트리가 위험한가

Git의 구조를 잠깐 뜯어보면 이유가 분명해집니다. 하나의 Git 저장소는 크게 세 층으로 나뉩니다.

  • 오브젝트 저장소(.git) — 모든 커밋·트리·블롭이 담기는 영구 히스토리입니다. 브랜치도 결국 이 저장소 안 커밋을 가리키는 포인터일 뿐입니다.
  • 인덱스(스테이징 영역) — 다음 커밋에 담길 변경을 모아 두는 준비 공간입니다.
  • 작업 디렉터리(워킹 트리) — 지금 파일 시스템에 펼쳐진, 우리가 직접 편집하는 파일들입니다.

문제는 전통적인 git clone 저장소가 이 셋을 하나씩만 갖는다는 점입니다. HEAD는 한 번에 하나의 브랜치를 가리키고, 인덱스도 하나, 작업 디렉터리도 하나입니다. 에이전트 두 명이 같은 폴더에서 일하면 이 단일 자원을 놓고 경쟁하게 됩니다. git stash, git checkout, git reset 같은 명령은 모두 이 공유 상태를 조용히 갈아엎을 수 있습니다.

메모리에 남은 실제 사례가 이를 잘 보여 줍니다. 공유 체크아웃에서 여러 세션이 동시에 git을 조작하다가, 아직 커밋하지 않은 작업이 외부 git 조작에 밀려 소실된 적이 있습니다. 교훈은 두 가지였습니다. 단계마다 즉시 커밋할 것, 그리고 진짜 격리가 필요하면 워크트리를 쓸 것.

해법: git worktree

git worktree는 하나의 저장소에서 여러 작업 디렉터리를 만들어 각기 다른 브랜치를 체크아웃하게 합니다. 핵심은 이 워크트리들이 오브젝트 저장소(.git)를 공유하면서도, 작업 디렉터리·인덱스·HEAD는 각자 따로 갖는다는 점입니다. 히스토리는 하나로 이어지되, 지금 편집 중인 파일 상태는 완전히 분리됩니다.

git worktree add ../agent-a feature/agent-a
git worktree add ../agent-b feature/agent-b

이렇게 하면 ../agent-a../agent-b라는 별도의 폴더가 생기고, 각각 자기 브랜치를 체크아웃한 채 나란히 존재합니다. 에이전트마다 독립된 워크트리를 주면, 서로의 파일을 밟지 않고 병렬로 일할 수 있습니다. A가 feature/agent-a에서 파일을 반쯤 고쳐 놓아도, B의 feature/agent-b 폴더에는 아무 영향이 없습니다. 각 워크트리는 자기 브랜치·자기 파일 상태를 가지므로, A의 미완성 변경이 B에게 보이지 않습니다.

하나의 저장소.git 오브젝트 저장소 공유워크트리 Afeature/apiClaude 담당워크트리 Bfeature/uiCodex 담당워크트리 Cfeature/testsAntigravity 담당
하나의 저장소가 .git 오브젝트 저장소를 공유하면서, 에이전트마다 자기 브랜치를 체크아웃한 별도 워크트리에서 병렬로 일합니다.

격리 워크스페이스의 해부

워크트리가 "공유하는 것"과 "따로 갖는 것"을 정확히 구분하면 격리의 원리가 손에 잡힙니다. 각 워크트리는 자기만의 작업 디렉터리, 자기만의 인덱스, 자기만의 **HEAD**를 갖습니다. 반면 커밋·트리·블롭이 담긴 오브젝트 저장소는 모두가 하나를 공유합니다.

이 설계가 절묘한 이유는, 격리와 효율을 동시에 잡기 때문입니다. 작업 디렉터리가 분리되어 있으니 파일 편집은 서로 간섭하지 않습니다. 그런데 히스토리는 하나로 이어져 있으니, 한 워크트리에서 만든 커밋을 다른 워크트리에서 곧바로 볼 수 있습니다. git clone을 여러 벌 뜨는 것과는 다릅니다. clone은 오브젝트 저장소까지 통째로 복제해 디스크를 몇 배로 쓰고, 서로의 커밋을 보려면 fetch가 필요합니다. 워크트리는 저장소를 공유하므로 디스크도 아끼고, 커밋도 즉시 공유됩니다.

워크트리 A워크트리 B작업 디렉터리인덱스 (스테이징)HEAD → feature/api작업 디렉터리인덱스 (스테이징)HEAD → feature/ui공유 .git 오브젝트 저장소 — 커밋 · 트리 · 블롭모든 워크트리가 하나의 히스토리를 공유
워크트리마다 작업 디렉터리·인덱스·HEAD는 따로 갖지만, 그 아래 .git 오브젝트 저장소(히스토리)는 하나를 공유합니다.

언제 격리하나

격리는 공짜가 아닙니다. 워크트리를 만들면 디스크에 파일 트리가 한 벌 더 펼쳐지고, 의존성 설치(npm ci 같은)나 환경 설정을 워크트리마다 다시 해 줘야 할 수도 있습니다. 그래서 무조건 격리하는 것이 아니라, 간섭 가능성이 실제로 있을 때 격리하는 것이 옳습니다.

  • 두 에이전트가 같은 파일·모듈을 만질 가능성이 있을 때. 겹치는 코드를 동시에 고치면 충돌은 시간문제입니다.
  • 위험한 리팩터링을 본체와 분리해 시험할 때. 크게 갈아엎는 작업은 언제든 되돌릴 수 있는 별도 공간에서 하는 편이 안전합니다.
  • 실패해도 메인 작업 공간을 오염시키지 않아야 할 때. 실험이 잘못돼도 격리된 폴더만 버리면 깨끗하게 원상복구됩니다.
  • 에이전트가 브랜치를 자주 바꾸거나 스테이징을 크게 건드리는 작업을 할 때. 공유 트리라면 옆 에이전트를 밟기 쉽습니다.

반대로, 서로 다른 파일만 만지는 게 확실하면 격리는 과할 수 있습니다 — 격리에는 디스크·설정 비용이 따릅니다. 예컨대 한 에이전트는 문서만, 다른 에이전트는 완전히 다른 서비스 폴더만 고친다면, 굳이 워크트리를 나누지 않고 자주 커밋하는 규율만으로도 충분할 때가 많습니다.

실전 워크플로

실제로는 이런 순서로 흘러갑니다. 먼저 에이전트별로 워크트리를 만들고, 각자 자기 폴더에서 작업하게 합니다.

# 에이전트마다 격리된 워크트리 생성
git worktree add ../agent-a feature/agent-a
git worktree add ../agent-b feature/agent-b

# 각 워크트리에서 자주 커밋 — 소실 방지의 1차 방어선
cd ../agent-a && git add -A && git commit -m "wip: API 초안"

# 현재 워크트리 목록 확인
git worktree list

여기서 가장 중요한 규율은 자주 커밋하기입니다. 커밋되지 않은 변경만이 소실 위험에 노출됩니다. 일단 커밋하면 오브젝트 저장소에 안전하게 기록되고, 설령 워크트리를 잘못 지워도 브랜치만 살아 있으면 복구할 수 있습니다. 그래서 "단계마다 즉시 커밋"이 병렬 작업의 기본기입니다.

병합할 때

각 워크트리에서 자주 커밋하고, 작업이 끝나면 PR로 통합합니다. 병렬로 갈라졌던 브랜치들이 리뷰를 거쳐 main 하나로 다시 모이는 지점입니다. 이때 사람이 변경 내용을 검수하고, 충돌이 있으면 여기서 해소합니다. 통합이 끝난 뒤에는 변경 안 한 워크트리는 정리해 디스크와 혼란을 줄입니다.

# 통합 후 필요 없어진 워크트리 정리
git worktree remove ../agent-a

# 참조가 꼬였을 때 정리
git worktree prune
워크트리 Afeature/api · 커밋 3개워크트리 Bfeature/ui · 커밋 2개Pull Request리뷰 · 검수main 브랜치통합 완료변경 없는 워크트리는 자동 정리
병렬 워크트리에서 쌓인 커밋이 각각 PR로 올라오고, 리뷰를 거쳐 main으로 통합된 뒤 워크트리는 정리됩니다.

흔한 함정과 주의점

워크트리를 쓰다 보면 몇 가지 걸림돌을 만납니다. 미리 알아 두면 대부분 피할 수 있습니다.

  • 같은 브랜치를 두 워크트리가 동시에 체크아웃할 수 없습니다. Git이 막아 줍니다. 이는 버그가 아니라 안전장치입니다 — 한 브랜치를 두 곳에서 동시에 밀면 히스토리가 꼬이기 때문입니다. 에이전트마다 브랜치를 다르게 주세요.
  • 의존성·환경은 자동으로 따라오지 않습니다. 새 워크트리는 소스는 공유하되 node_modules 같은 무시된(gitignore) 산출물은 갖고 있지 않습니다. 워크트리를 만든 직후 npm ci나 빌드가 필요할 수 있습니다.
  • 커밋하지 않은 변경은 여전히 취약합니다. 워크트리 격리는 "서로 다른 폴더"를 보장할 뿐, 한 폴더 안에서 커밋 안 한 변경을 실수로 되돌리는 것까지 막아 주지는 않습니다. 자주 커밋하는 습관이 최종 방어선입니다.
  • 정리를 잊으면 워크트리가 쌓입니다. 끝난 작업의 워크트리는 git worktree remove로 지우고, 참조가 꼬였으면 git worktree prune으로 청소하세요.

마블로에서

마블로는 필요할 때 에이전트를 격리된 워크트리에서 스폰해, 병렬 작업이 서로 충돌하지 않도록 합니다. 여러 에이전트가 같은 파일·모듈을 만질 위험이 있는 작업이라면, 오케스트레이터는 각 에이전트에게 독립된 워크트리를 배정합니다. 각 에이전트는 자기 폴더·자기 브랜치에서 마음껏 파일을 고치고, 옆 에이전트의 미완성 변경에 영향을 받지 않습니다.

작업이 끝나면 결과는 커밋과 PR로 흘러가고, 사람이 REVIEW 단계에서 검수합니다. 그리고 변경이 없는 워크트리는 자동으로 정리되므로, 실험이 헛되이 끝나도 작업 공간에 찌꺼기가 남지 않습니다. 병렬로 밀어붙이면서도 각 작업을 안전하게 떼어 놓는 것 — 이것이 마블로가 "통제 가능한 병렬성"을 실제로 구현하는 방식입니다.

통제 가능한 병렬성 — 격리는 그 토대입니다. 에이전트 군단을 동시에 굴리는 힘은 결국 "서로를 밟지 않는다"는 보장 위에서만 안전하게 발휘됩니다. git worktree는 그 보장을 저장소 수준에서 제공하고, 마블로는 그것을 오케스트레이션 흐름 안에 자연스럽게 녹여 냅니다. 더 넓은 그림이 궁금하다면 마블로란 무엇인가 글에서 오케스트레이터·칸반·이종 에이전트가 어떻게 맞물리는지 이어서 볼 수 있습니다.

댓글

댓글 기능은 곧 제공됩니다.