Claude Code에는 버그 하나를, Codex에는 작은 기능 하나를 맡기고 자리를 뜬 뒤 돌아와 보니 파일이 뭔가 이상하다. 한쪽 에이전트의 수정이 사라져 있다. 아니면 양쪽 수정이 다 들어가 있는데 빌드가 깨진다. 더 나쁘게는 모든 게 컴파일은 되는데 어느 에이전트도 건드리지 않은 곳에서 테스트가 깨진다. 자리를 비운 사이 아무것도 죽지 않았다. 두 에이전트 모두 시킨 일을 해냈다. 다만 그 파일이 작성자 둘을 동시에 감당하지 못했을 뿐이고, 누가 이겼는지는 아무도 알려주지 않았다.
문제의 본질은 바로 그 침묵이다. 파일은 병합 도구가 아니다. 두 코딩 에이전트가 같은 디렉터리의 auth.ts에 시점이 겹쳐 쓰기를 하면 락도, 대기열도, 경고도 없다. 그저 마지막에 저장한 프로세스가 있을 뿐이다. 이 글에서는 충돌이 벌어지는 과정을 단계별로 짚고, 각 단계에서 어떤 실패 양상이 나타나는지 이름 붙인 뒤, 다음 병렬 세션 전에 돌려볼 수 있는 체크리스트로 마무리한다.
아울러 이런 해법들이 이끄는 워크플로도 소개한다. Sharkly는 이미 쓰고 있는 코딩 에이전트 위에 얹는 올인원 에이전트 명령·관리 플랫폼이다. 각 태스크에 격리된 워크트리를 따로 부여하고, 요청부터 릴리스까지 컨텍스트, 진행 상황, 블로커, 결과, 사람의 리뷰를 한눈에 볼 수 있게 유지한다. 같은 규율을 순수 Git만으로 직접 구축할 수도 있으며, 이 글의 대부분은 그 방법을 다룬다. 마지막 섹션에서는 직접 하는 방식이 언제부터 효율이 떨어지는지 이야기한다.
락은 없다: 마지막으로 쓴 쪽이 이긴다
가장 나쁜 경우부터 보자. 가장 흔하면서도 가장 눈에 띄지 않기 때문이다. 두 에이전트가 같은 작업 디렉터리에서 돌아간다. 에이전트 A가 auth.ts를 읽고 변경을 계획한 뒤 쓰기 시작한다. 1초 뒤 에이전트 B가 같은 auth.ts를 읽고, 자기가 읽은 버전을 기준으로 다른 변경을 계획한 뒤 역시 쓴다.
둘 다 상대의 수정을 읽지 못했다. 읽을 때는 아직 저장되지 않았기 때문이다. 나중에 저장한 쪽이 첫 번째 에이전트의 작업이 전혀 담기지 않은 버전으로 디스크의 파일을 덮어쓴다. 이것이 마지막 쓰기 우선(last-write-wins)이며, 아무 소리도 없이 일어난다. 충돌 마커도, 에러도, 종료 코드도 없다. 에이전트 A는 성공했다고 보고한다. 에이전트 B도 성공했다고 보고한다. 둘 중 하나는 거짓말을 하고 있고, 그 사실조차 모른다.
락 파일 경합은 같은 실패가 더 비싼 곳에서 벌어지는 경우다. 에이전트 A가 npm install을 돌려 package-lock.json을 다시 쓰고, 설치 중이던 에이전트 B가 다른 시작 상태에서 다시 덮어쓴다. 결과는 어느 의존성 트리와도 맞지 않는 락 파일이고, 며칠 뒤 다른 사람 머신에서 문제가 터진다. 공유 빌드 산출물, 하나뿐인 개발 서버 포트, 전역 캐시도 똑같은 방식으로 깨진다.
해법은 더 똑똑한 에이전트가 아니다. 각 에이전트에 자기만의 방을 주는 것이다.
에이전트마다 작업 사본 하나: 조용한 덮어쓰기가 눈에 보이는 충돌로
Git 워크트리는 하나의 저장소를 별도 브랜치로 두 번째 체크아웃한 사본이며, 단일 .git 객체 저장소를 공유한다. 각 에이전트는 다른 누구의 작업 중인 내용도 보지 않고 편집·빌드·테스트할 수 있는 실제 디렉터리를 갖는다.
# From the main repo
git worktree add ../repo-agent-a -b agent/auth-refactor
git worktree add ../repo-agent-b -b agent/session-timeout
에이전트 A는 ../repo-agent-a를, 에이전트 B는 ../repo-agent-b를 가리키게 하자. 이제 A가 auth.ts에 저장하지 않은 수정은 B에게 보이지 않고, 두 에이전트는 서로 다른 브랜치에서 서로 다른 파일을 편집하므로 디스크에서 상대 파일을 덮어쓸 수 없다. 워크트리 모델은 AI 코딩 에이전트를 위한 Git 워크트리와 코딩 에이전트 워크트리: 태스크당 하나의 체크아웃에서 더 자세히 다룬다.
여기서 격리가 해낸 일을 보자. 두 에이전트가 같은 파일을 편집하는 것을 막지는 않았다. 브랜치가 다시 합쳐지는 순간, 조용한 데이터 손실을 눈에 보이는 병합 충돌로 바꿔놓았을 뿐이다. 이건 실질적인 개선이다. Git이 멈추고 어긋난 정확한 줄을 보여주며, 양쪽 버전을 모두 보면서 해결할 수 있다. 눈에 보이는 충돌은 고칠 수 있는 문제다. 보이지 않던 덮어쓰기가 바로 배포까지 나간 그 문제였다.
분명히 짚어둘 경계가 하나 있다. 워크트리는 파일을 격리하지, 부수 효과를 격리하지는 않는다. 공유 데이터베이스, 하나뿐인 포트, 하드코딩된 출력 경로는 워크트리를 넘나들며 여전히 충돌한다. 이런 것도 격리하거나, 그것을 필요로 하는 에이전트들을 순차 실행하라.
보이지 않는 충돌
격리를 하고도 살아남는 실패 양상이 있다. 자기 워크트리에서 작업하는 에이전트 A가 getUser()를 fetchUser()로 이름을 바꾸고 자신이 담당하는 모든 호출 지점을 갱신한다. 동시에 자기 워크트리에서 작업하는 에이전트 B는 getUser()를 호출하는 지점을 세 군데 새로 작성한다. B가 파일을 읽었을 때의 함수 이름이 그랬기 때문이다.
두 브랜치는 충돌 마커 하나 없이 병합된다. Git은 줄 단위로 비교하는데, 줄 단위에서는 겹치는 게 없었다. A는 자기 파일을 바꿨고 B는 새 파일을 추가했을 뿐이다. 병합은 깔끔하다. 그러고 나서 정의되지 않은 getUser 때문에 빌드가 깨지거나, 컴파일은 통과하고 타입 검사기가 미치지 못하는 런타임에서 실패한다. 어떤 도구도 이걸 잡아내지 못했다. 텍스트상으로는 충돌이 없었기 때문이다. 두 수정은 파일의 텍스트가 아니라 의미를 두고 어긋난 것이다.
그래서 “에이전트만 격리하면 된다”는 건 전부가 아니다. 격리는 같은 파일 충돌을 같은 인터페이스 충돌로 미룰 뿐이며, 두 번째 종류가 첫 번째보다 더 잘 숨는다. 여기서 쓸 수 있는 지렛대는 도구가 아니라 태스크 범위 설정이다. 쓸 만한 규칙이 하나 있다. 두 에이전트의 태스크를 설명할 때 같은 파일이나 같은 함수를 언급하지 않고는 설명할 수 없다면, 그 둘은 두 개가 아니라 하나의 태스크다. 모듈 경계를 따라 작업을 나누는 것이 한 코드베이스에서 Claude Code와 Codex 함께 쓰기가 디버깅으로 반나절을 날리는 상황으로 번지는 것을 막는 핵심이다. 두 태스크가 정말로 인터페이스를 공유한다면 순차 실행하거나, 단계 사이에 병합·리베이스해서 두 번째 에이전트가 첫 번째 에이전트의 현실을 읽도록 하라.
직접 구축한 방식이 한계에 부딪히는 지점, 그리고 태스크 보드가 더하는 것
에이전트 둘에 공유 파일 하나 정도는 손으로 관리할 수 있다. 워크트리 둘, 신중한 범위 설정, 지켜보는 병합이면 된다. 이 방법은 세는 게 어려워지는 순간까지는 버틴다. 어느 에이전트가 어느 브랜치에 있는지, 어느 워크트리가 어느 태스크에 대응하는지, 깔끔하게 병합된 다섯 개 중 어느 것이 조용히 공유 인터페이스를 바꿨는지, 어느 브랜치가 리뷰를 기다리고 있고 어느 것을 이미 봤는지. Git 명령 자체가 어려워진 게 아니다. 그 주변의 기록·관리가 어려워진 것이고, 바로 그 관리 지점에서 같은 파일 충돌이 빠져나간다. 제대로 읽지 않은 병합을 승인했기 때문이다.
태스크 보드는 무엇을 추적하는지를 바꾼다. 터미널 벽과 분리된 워크트리 더미 대신, 작업 단위마다 담당자와 브랜치, 상태를 가진 태스크가 된다. Sharkly에서는 태스크가 자동으로 전용 격리 워크트리를 갖기 때문에, 파일 단위 격리가 기억해서 실행해야 하는 명령이 아니라 기본값이 된다. 실행, 블로커, 결과가 모두 태스크로 돌아오므로, diff에서 의도를 역추적하는 대신 명시된 범위를 기준으로 변경을 리뷰하게 된다. 그리고 역할 분담은 고정된다. 에이전트는 조사하고 실행하고 테스트하고 보고하며, 사람은 방향을 정하고 권한을 부여하고 결과를 받아들인다. 자동화는 팀의 판단이 필요한 지점에서 멈춘다.
그 마지막 지점은 격리만으로는 결코 해결되지 않는 부분이다. 더 나은 도구가 격리와 병합을 쉽게 만들어주지만, 리뷰는 에이전트 수에 비례해 늘지 않는다. 리뷰는 당신의 주의력에 비례해 늘 뿐이며, 병렬 에이전트 작업의 진짜 병목은 사람의 리뷰다. 에이전트 다섯 개는 당신이 하나를 꼼꼼히 읽는 동안 브랜치 다섯 개를 만들어낸다. 태스크 보드가 코드를 대신 읽어주지는 않는다. 다만 대기열을 투명하게 유지해서, 그럴듯해 보이는 수정이 어느 더미에 있었는지 놓친 탓에 대충 훑고 병합되는 일을 막는다. 같은 충돌 계산은 저장소 수준에서도 나타나며, 같은 저장소의 여러 코딩 에이전트에서 다룬다.
워크트리와 규율로 직접 구축하거나, Sharkly로 워크플로를 관리할 수 있다. 이미 여러 에이전트를 돌리고 있다면, 가치는 그들의 태스크와 결과가 여러 터미널에 흩어져 있는 대신 한곳에 모여 있다는 데 있다.
다음 병렬 세션 전에 점검할 체크리스트
두 에이전트를 어디에든 투입하기 전에 이걸 돌려보라:
- 각 에이전트에 전용 워크트리를 부여하라. 두 에이전트를 한 작업 디렉터리에 두지 말라. 태스크마다
git worktree add ../repo-<task> -b agent/<task>를 실행하라. 격리된 워크트리에서의 병렬 에이전트 참고. - 같은 파일이나 함수를 언급하지 않도록 태스크 범위를 잡아라. 불가피하다면 그건 하나의 태스크이니 순차 실행하라.
- 파일뿐 아니라 부수 효과도 격리하라. 데이터베이스, 포트, 출력 경로, 캐시를 분리하거나, 그것을 공유하는 에이전트를 순차 실행하라.
- 병합 전에 리베이스하라. 두 브랜치가 같은 시작 커밋에서 출발했다면 두 번째 에이전트의 작업이 첫 번째 위에 올라가도록 하라.
- 매 병합 후에 타입 검사와 전체 테스트 스위트를 돌려라. 마지막 병합 후가 아니라 매번이다. 의미상 충돌은 깔끔한 병합을 통과하고 바로 여기서 실패한다.
- 각 브랜치를 해당 태스크의 범위에 비추어 하나씩 리뷰하라. 읽지 않은 초록색 체크 표시 더미가 바로 보이지 않는 충돌이 배포되는 곳이다.
두 에이전트가 한 파일을 편집하는 건 드문 예외 상황이 아니다. 태스크가 조금이라도 겹치는 순간 병렬 작업이란 바로 이런 모습이 된다. 충돌을 미리 계획하고, 눈에 보이게 만들고, 무엇을 병합할지는 한 사람이 결정하게 하라.



