병행 실행되는 여러 Claude가 중복 실행 및 누락을 일으키다 ── 지휘자 없는 장부로 충돌을 방지한 17일간
요약
여러 개의 Claude Code 세션을 동시에 실행할 때 발생하는 작업 중복, 누락, 상태 파악 불능 문제를 다룹니다. 병행 실행 환경에서 AI 에이전트 간의 충돌을 방지하기 위한 관리 메커니즘의 필요성을 강조합니다.
핵심 포인트
- Claude 세션 병행 실행 시 작업 상태 추적의 어려움 발생
- 동일 태스크에 대한 중복 수행 및 작업 누락 문제 직면
- 독립적인 기능 단위의 병행 실행은 효율적이나 관리 체계 필수
- AI 에이전트 간의 충돌을 방지할 '지휘자' 역할의 메커니즘 필요
2026년 1월에 TOKIUM에 입사하여, QA 팀에서 테스트 자동화에 힘쓰고 있는 ikedan입니다.
지금까지의 기사에서는 Claude를 '또 다른 동료'로 맞이했을 때, 회고와 시운전을 준비하는 이야기나 AI 지식을 어디에 둘 것인지를 정리하는 이야기를 써왔습니다. 둘 다 '한 명의 AI 동료를 하나의 세션 안에서 어떻게 다스릴 것인가'에 대한 이야기였습니다.
본 기사는 거기서 한 걸음 더 나아갑니다. 테마는, 여러 명의 AI 동료를 동시에 실행했을 때, 그 전원을 어떻게 운영할 것인가입니다.
이전 기사에서 "미리 막는 메커니즘"에 관한 기사를 쓰겠다고 선언했지만, 옆길로 새어 이 글을 집필하게 되었습니다.
계기는 책상이 어질러진 것이었습니다.
테스트 설계 자동화가 본격화되면 하고 싶은 일이 한꺼번에 늘어납니다.
"이 관점의 생성", "저 E2E의 실행", "평가 파이프라인의 수정"을 하나의 창에서 순서대로 하고 있으면 대기 시간만 길어집니다.
그래서 Claude Code를 2개, 3개씩 동시에 띄워 보았더니, 이번에는 다른 문제가 발생했습니다.
어느 창에서 무엇을 하고 있는지 스스로도 알 수 없게 된 것입니다.
배경을 조금 설명하겠습니다. 저희 팀이 테스트하고 있는 것은 경비 정산·청구서 SaaS이며, 저의 업무는 기능별로 테스트 관점 생성 → 리뷰용 자동 평가 → 테스트 케이스 전개 → 다시 평가라는 다단계 파이프라인을 돌리는 것입니다.
그 외에도 E2E 실행이나 화면 조사, 평가 로직 자체의 개수도 있습니다.
여기서 핵심은, 이들 중 상당수가 "기능 A의 관점 생성"과 "기능 B의 E2E"처럼 서로 독립되어 있다는 점입니다.
하나의 기능에 대한 평가 루프(수 분이 소요됩니다)를 기다리는 동안, 다른 창에서 다른 기능의 관점 생성을 진행할 수 있습니다 ── 테스트 설계 자동화는 구조적으로 병행 실행과 궁합이 좋습니다.
이는 QA에만 국한된 이야기가 아닙니다.
여러 개의 feature 브랜치를 병행하여 진행하거나, 리뷰 대기 태스크가 쌓이거나, 생성 AI에게 독립적인 생성을 여러 건 시키는 상황 ── 그러한 장면은 모두 같은 형태를 띠고 있습니다.
역설적으로 말하면, 하나의 창으로 충분할 때는 Foreman와 같은 메커니즘이 필요 없습니다. 두 번째 창을 열고 "어라, 어느 쪽에서 무엇을 하고 있었는지 모르겠네"라고 느끼는 순간부터, 이 기사는 당신의 이야기가 됩니다.
병행 실행을 시작하자 태스크 관리가 파탄 났다
여러 개의 Claude를 동시에 실행하자 다음과 같은 일이 일어났습니다.
어디에 무엇이 있는지 알 수 없음: 태스크는 인수인계용 메모나 각 창의 대화 속에 흩어져 있어, "미처리 항목이 전부 몇 개인지"를 일람할 수 없음 -
중복 수행: 다른 창이 이미 착수한 태스크를, 이쪽은 알지 못한 채 처음부터 시작함 (실제로 동일한 기능의 테스트 케이스 생성을 두 개의 창이 따로 실행하고 있었던 적이 있었습니다) -
누락: "나중에 해야지"라고 생각한 태스크가 어떤 메모에도 남지 않고 사라짐
한 번은 웃음밖에 나오지 않는 사건이 있었습니다.
이 협업 메커니즘을 구현하고 있는 바로 그 도중에, 대시보드를 확인했더니 다른 창이 동일한 메커니즘의 다른 파트를 병행하여 구현하고 있었던 것입니다.
제가 토대를 만들고, 상대방이 종료 플로우를 만들고 있었습니다. 병행 실행이 충돌한다는 것을, 그 대책을 만드는 작업 자체가 증명해 버린 아이러니한 상황이었습니다.
다른 날에는 이런 일도 있었습니다.
제가 긴 화면 조사를 하는 동안, 다른 세션이 동일한 평가 설정 파일을 수정하고 있었고, 저는 "방금 본 상태"라고 생각하며 그곳에 덮어쓰기를 하려고 했습니다.
편집 직전에 행 번호가 어긋나 있는 것을 발견하고서야 겨우 병행 실행 중임을 깨달았습니다. 커밋 히스토리를 봐도 아직 아무 일도 일어나지 않은 것처럼 보였기에, 아직 커밋되지 않은 변경사항이야말로 다른 창에서 작업이 진행 중이라는 가장 확실한 신호였다는 것이 교훈이었습니다.
처음에 시도한 것은 인수인계 문서를 정성껏 작성하는 것이었습니다.
하지만 문서는 "읽은 사람이 그렇게 해석했다"라는 파생 정보일 뿐, 지금 정말로 미처리 상태인 것이 무엇인가라는 질문에는 답해주지 않습니다. 문서가 늘어날수록 무엇이 최신인지 알 수 없게 되었습니다.
그래서 사고방식을 한 단계 바꿨습니다. 미처리 태스크의 "정본(Original/Source of Truth)"을 그냥 한 곳으로 정해버리는 것입니다.
인수인계 메모도, 진행 중인 대화도, 모두는 거기서 파생된 뷰(View)에 불과하다고 말이죠. 이 "유일한 정본(SSoT)"을 추가 전용 파일 하나에 두는 것이 Foreman의 출발점입니다.
이것이 서두에서 언급한 문제 ① "어디에 무엇이 있는지 알 수 없음"에 대한 첫 번째 답이었습니다.
Claude 공식의 「Agent View」는 다른 문제를 해결하고 있다
미처리 태스크의 정본(Source of Truth)을 한 곳으로 정한다 ── 그렇게 결정했을 때, 공식 기능을 잘 아는 사람일수록 당연한 의문을 가질 것입니다. "Claude Code에는 공식 멀티 에이전트 (Multi-agent) 기능이 있는데, 왜 직접 만들었는가".
2026년에 들어서며, Claude Code에는 여러 에이전트를 묶는 공식 기능이 차례차례 추가되었습니다. 제가 조사한 범위 내에서는 주로 4가지가 있습니다.
| 공식 기능 | 하는 일 | "다음에 무엇을 실행할지" 결정하는 주체 |
|---|---|---|
| Agent View (리서치 프리뷰) | 백그라운드의 여러 세션을 한 화면에서 모니터링 및 회신 | 사용자 (=지휘자) |
| ... |
이것들은 정말 강력하며, 사실 이 글의 사전 조사(공식 기능의 사실 확인 및 제 장부의 집계)에도 Dynamic Workflows를 사용했습니다.
다만, 이것들은 모두 "세로형 협조"입니다. 한 명의 지휘자(사용자, 부모 Claude, 스크립트, 혹은 팀의 리더 역할)가 있고, 그 휘하의 서브 에이전트(Sub-agent)나 멤버들을 관리하는 구조입니다. 누가 다음에 무엇을 실행할지는 그 지휘자가 결정합니다.
반면에 제가 맞닥뜨렸던 것은 "가로형 협조"였습니다.
독립적인 인간 주도의 세션이 여러 개 실행되고 있으며, 서로 대등하고, 지휘자는 없습니다.
각각이 동일한 코드베이스를 병행하여 수정합니다.
이 "지휘자가 없는 대등한 세션들끼리, 락 (Lock)도 사용하지 않고 어떻게 충돌을 피할 수 있는가"라는 유스케이스는, 공식 기능이 주로 상정하고 있는 형태와는 조금 달랐습니다 (공유 태스크 리스트를 가진 공식 기능도 있습니다. 글의 후반부에서 다시 다루겠습니다).
공식: 세로형 협조 (지휘자 있음) ── 한 명의 지휘자가 휘하를 관리함
Foreman: 가로형 협조 (지휘자 없음·대등함) ── 대등한 세션들이 장부를 통해 정보를 전달함
이 "가로형 협조"를 락도 중앙 서버도 사용하지 않고 성립시키려 한 것이, 여기서부터 설명할 Foreman입니다.
Foreman이란 무엇인가 ── 락도 중앙 서버도 없는 협조 기구
Foreman(포어맨=현장 감독)은, 병행하는 여러 Claude 세션이 상호 배제 락 (Mutual Exclusion Lock) 없이 태스크의 인계와 충돌 회피를 성립시키기 위한 메커니즘입니다.
중앙 서버도 데이터베이스도 갖추지 않으며, 파일 시스템상의 "추가 전용 로그"와 "세션별 작은 파일"만으로 구성되어 있습니다.
설계의 기둥은 5가지입니다.
| 기둥 | 내용 |
|---|---|
| advisory / no-lock | 훅 (Hook)은 다른 세션을 멈출 수 없다 (후술할 구조적 제약). 따라서 충돌은 "감지하여 알리는" 것에 그친다 |
| 고정 경로로 집약 | 장부와 세션 목록은 작업 디렉토리에 의존하지 않는 고정된 위치(~/.claude/hooks/)에 모은다. 위치가 제각각이면 다른 폴더에서 실행된 세션들끼리 서로를 볼 수 없기 때문이다 |
| event-sourcing (추가 전용) | 정본 장부는 1행 1이벤트의 추가 전용 파일이다. 상태를 변경할 때는 "덮어쓰기"가 아니라 "새로운 행을 추가"한다. 읽을 때 이를 합쳐서(fold) 최신 상태로 만든다 |
| fail-open (고장 나도 멈추지 않음) | 모니터링이나 협조 메커니즘이 버그로 인해 중단되어도, AI 본체의 작업은 멈추지 않는다. 예외는 무시하고 조용히 통과시킨다 |
| 쓰는 곳은 단 4곳뿐 | 공유 파일에 쓰는 것은 심박 기록, 세션 ID 기록, 장부 추가, 충돌 감사 등 4가지뿐이다. 집계하여 보여주는 쪽은 읽기만 할 뿐 쓰지 않는다 |
그중에서도 핵심은 **advisory (권고/조언)**라는 절연입니다.
이유는, Claude Code의 훅(명령어나 편집 직전에 개입할 수 있는 자체 스크립트)에는 명확한 구조적 제약이 있기 때문입니다.
훅은 옆에서 실행 중인 다른 세션을 정지시키거나, 대기시키거나, 강제 종료할 수 없습니다.
개입할 수 있는 것은 "자신의 세션에서 앞으로 실행할 조작"뿐입니다. 옆의 창에는 손을 뻗을 수 없습니다.
사실 이전에, 순수하게 락으로 해결하려다 한 번 좌절한 적이 있습니다.
공유 파일에 락을 걸도록 하는 방식을 시도했더니, 락 파일 주변의 에러(문자 깨짐이나 권한 에러로 인한 크래시)가 쌓여 하나의 로그가 100여 KB까지 불어났고, 락으로 보호해야 할 메커니즘이 락 때문에 망가지는 본말전도 상황이 되었습니다.
그 반성으로부터, 락 방식 자체를 버렸습니다.
그래서 Foreman은 「멈추는 것」을 포기하고, 충돌할 것 같으면 알리기 / 올바른 순서는 장부(Ledger)로 공유하기에 집중했습니다.
이는 데이터베이스에서 말하는 「낙관적 병행 제어 (Optimistic Concurrency Control, 충돌은 거의 일어나지 않는다는 전제하에 충돌했을 때만 사후 처리하는 방식)」와 같은 발상입니다.
역할은 두 가지로 나뉩니다.
업무를 뿌리는 측은 orchestrator (기점 세션. 태스크 정리 · 장부의 정합성 · 뿌릴 대상의 생성 · 진행 모니터링을 담당),
뿌려진 업무를 받아 실제 작업을 하는 측은 worker (별도 창의 세션. 1개 태스크를 스스로 확보(claim)하여 구현이나 E2E, 집필 등을 수행하고, 완료되면 done을 입력함).
여기서 Foreman이 자신에게 부여한 규율이 하나 있습니다.
orchestrator는 실제 작업을 자신의 창 안에서 수행하지 않고, 반드시 worker의 별도 창으로 뿌린다.
기점이 업무를 떠안게 되면, 결국 하나의 창에 업무가 쌓여 병행 실행의 의미가 없어지기 때문입니다.
이름 때문에 오해할 수 있으니 미리 말씀드립니다. 「Foreman(현장 감독)」이라고 명명했고, 업무를 뿌리는 orchestrator라는 역할도 등장합니다.
「결국 그것도 지휘자 아닌가?」라고 생각하실지도 모릅니다. 하지만 이것은 부하를 통제하는 “관리자”와는 조금 다릅니다. 상상해 주셨으면 하는 것은, 현장 감독이 벽에 걸어두는 “작업 장부” 쪽입니다.
물론 다음 업무를 장부에 나열하고 worker의 창으로 전달하는 역할(orchestrator)은 있습니다.
하지만 그것은 고정된 사령탑이 아니라, 그때 업무를 뿌리고 있는 세션이 일시적으로 담당하는 역할일 뿐입니다.
게다가 일단 실행된 다른 세션을 멈추거나 명령을 다시 내릴 수도 없습니다 (앞서 썼듯이, 애초에 그 권한이 없기 때문입니다).
각 세션은 받은 업무를 스스로 확보(claim)하여 진행하며, 중간 경과를 위로 “보고”하는 상사도 없습니다. 기록하는 대상은 모두가 똑같이 읽고 쓸 수 있는 단 한 장의 장부뿐입니다.
전체를 통제하는 것은 사람도 중앙 에이전트도 아닌, 그 장부 그 자체 ── 그래서 저는 “지휘자 없음”이라고 부르고 있습니다.
메커니즘 ── 「걷는」 5가지 커맨드
Foreman의 운용은 5가지 커맨드를 「걷는 것」으로 집약됩니다. 어려운 동기화 처리(Synchronization)는 없으며, 착수 전과 종료 시에 장부를 확인한다는 수수한 절차입니다.
/sessions(읽기 전용): 현재 누가 · 어떤 브랜치에서 · 살아있는지 · 충돌 의심은 없는지를 목록으로 확인/pickup(착수 전): 장부에서 다음 태스크를 확보(claim) (장부에 쓰는 것은 이 claim과 종료 시의/closeout등록, 이 두 가지뿐)/dispatch(뿌리기): orchestrator가 worker를 별도 창으로 깨우는 기동 커맨드를 생성/closeout(종료 시): 커밋부터 「다음 태스크의 장부 등록」까지를 마무리/foreman(운용 모드를 불러오기)
특히 착수 전의 /pickup에는 「묵묵히 빼앗지 않는다」라는 원칙이 적용됩니다. 흐름은 다음과 같습니다.
- 현재 살아있는 세션 목록을 읽는다.
- 앞으로 자신이 다룰 파일이 다른 생존 세션이 최근 5분 이내에 작성한 파일과 겹치지 않는지 대조한다. 겹친다면 대상이나 순서를 변경한다.
- 태스크를 확보한다. 만약 다른 생존 세션이 이미 확보했다면, 함부로 빼앗지 않고 사람에게 「빼앗을지 / 다른 태스크로 할지 / 기다릴지」를 묻는다.
- 죽은 세션이 붙잡고 있는 채 방치된 태스크라면, 해제한 후 확보한다.
여기에 한 단계 더 나아가, 확보하기 전에 「그 태스크는 내가 파악하고 있는 것인가」도 확인합니다.
장부의 각 태스크에는 「누가 · 언제 만들었는가」의 표식이 붙습니다.
다른 세션이 오늘 들어와 쌓아둔, 내가 모르는 태스크는 내용물인 문서로 들어가기 전에 일단 반려(release)하는 것이 기본 규칙입니다.
사람 없이 돌아가는 AI들끼리는 아무도 요청하지 않은 “환상의 태스크”가 장부에 쌓일 수 있는데, 이를 사람이 반사적으로 집어 들어 실행하지 않도록 하기 위한, 수수하지만 효과적인 게이트(Gate)입니다.
종료 시의 /closeout도 같은 사상입니다.
「검증 없는 완료는 없다」를 전제로, 완료를 다른 수단으로 실증한 뒤 의미 있는 단위로 커밋하고, 남은 작업을 반드시 장부에 다시 등록한 뒤에 마무리합니다.
다음에 집어 들 worker가 헤매지 않도록, 등록 시에는 ready_prompt의 맨 앞에 【종별 | 기동할 skill | 완료 정의】라는 헤더를 배치합니다. 이렇게 하여 「다음 수」를 결정적으로 넘겨줍니다.
착수 전의 대조가 문제 ② 「중복 실행」을, 종료 시의 등록이 문제 ③ 「누락」을 각각 담당하는 형태입니다.
기술적으로 가장 흥미로웠던 점은, 장부(ledger)의 상태를 「접는(fold)」 부분입니다.
장부는 단순한 추가 전용 로그이므로, 동일한 태스크의 상태를 변경할 때도 행을 덮어쓰는 것이 아니라 새로운 이벤트 행을 추가할 뿐입니다.
읽을 때, 동일한 ID를 가진 행을 시계열로 접어 넣어 「최신 상태」를 만듭니다.
접는 방식의 핵심은, 「상태를 포함하는 이벤트만이 상태를 변화시키고, 상태를 포함하지 않는 이벤트는 현재 상태를 유지한다」는 가법적(additive)인 규칙으로 정한 것입니다.
덕분에 「이 태스크를 다른 윈도우로 분산했다」라는 정보를 태스크의 진행 상태를 전혀 해치지 않고 나중에 추가할 수 있습니다. 락(Lock)도, 비교 교환(CAS, Compare-And-Swap)도, 덮어쓰기도 필요 없습니다.
글로만 설명하면 이해하기 어려우므로, 하나의 태스크에 발생한 일이 장부에 어떻게 쌓이는지 간략화하여 보여드리겠습니다.
// 동일한 id의 행이 덮어쓰여지지 않고 한 줄씩 쌓여간다
{"event":"add", "id":"L-...-a1b2c3", "status":"queued", "title":"기능 X의 테스트 관점 만들기"}
{"event":"claim", "id":"L-...-a1b2c3", "status":"claimed", "by":"session-7f43"}
...
실제 이벤트에는 필드가 조금 더 있지만, 요점은 이것뿐입니다.
상태를 가진 이벤트(claim이나 done)만이 status를 변화시키고, 상태를 가지지 않는 이벤트(「다른 윈도우로 분산했다」는 기록 등)는 status에 영향을 주지 않고 정보만 추가합니다.
이 「나중에 온 것이 이기는(last-write-wins) 방식으로 접는」 규칙 하나만으로, 락도 덮어쓰기도 없이 상태 관리가 이루어집니다.
예: 하나의 테스트 설계 태스크가 흘러가는 과정
설명이 이어졌으므로, 실제 업무가 어떻게 흘러가는지 한 번 따라가 보겠습니다. 「어떤 기능의 테스트 관점을 만든다」라는 업무를 예로 듭니다.
- 기점 윈도우(orchestrator)에서 이 업무를 장부에 등록(
add)한다. 맨 앞에는 【관점 생성 | 사용할 skill | 완료 조건】이라는 헤더를 붙여둔다. /dispatch로 worker의 별도 윈도우를 일으킨다. 확보(claim)는 일으켜진 worker가 스스로 한다 ── 「누가 하고 있는가」에 대한 기록을 실제로 움직이는 측에 맡기기 위해서입니다. worker의 윈도우에서는 관점 생성 → 자동 평가 → 테스트 케이스 전개라는 다단계 파이프라인이 돌아간다. 그동안 기점이나 다른 윈도우에서는 다른 기능의 E2E나 평가 로직 수정이 병행되어 진행된다.- 끝나면 worker가
done을 입력하고, 남은 내용을 장부에 쓴 뒤 접는다(closeout).
이 4단계 어디에도 「다른 윈도우를 멈추는」 조작이 없다는 것이 포인트입니다. 각 윈도우는 장부를 읽고 쓸 뿐이며, 전체를 지휘하는 지휘자는 없습니다.
그럼에도 불구하고, 같은 일을 두 번 하거나 도중에 누락하는 일이 발생하기 어려워집니다.
여담 ── 살아있는지 확인하려다 하마터면 상대를 죽일 뻔한 이야기
죽은 세션의 태스크를 다른 세션이 인계하려면, 「상대가 이미 종료되었는지」를 알고 싶어집니다.
여기서 한 번, 기술적으로 가장 무서운 지뢰를 밟았습니다.
프로세스의 생사를 확인하는 정석적인 방법으로 os.kill(pid, 0)이라는 코드가 있습니다.
많은 해설에서 「시그널을 실제로 보내지는 않고, 상대가 존재하는지 여부만 알 수 있는 안전한 확인 방법」으로 소개됩니다.
하지만 Windows의 Python에서는 내부 동작이 달라, 시그널 0이라도 내부적으로 대상 프로세스를 종료시켜 버리는 경우가 있습니다.
생사를 확인하려는 순간, 감시해야 할 별도의 세션을 휘말리게 하여 떨어뜨릴 수도 있는 것입니다.
「멈출 권한이 없을 텐데, 살아있는지 확인하려 했을 뿐인데 상대를 죽여버린다」 ── 이 기사의 테마 그 자체의 형태를 한 사고였습니다.
지금은 os.kill을 피하고, OS 함수를 직접 호출하여 「종료됨 / 아직 살아있음 / 판정 불가」의 3단계로 보고 있습니다.
그 상태에서 판정을 비대칭적으로 만들었습니다. 죽었다는 확증이 있을 때만 인계를 진행하고, 「살아있음」이나 「모름」은 안전한 쪽으로 기울여 아무것도 하지 않는다.
확실한 정보로만 움직이고, 애매할 때는 건드리지 않는다. 이 비대칭적인 설계는 「충돌하기 전에 피한다」는 장부 운용의 사상과 완전히 같은 형태를 띠고 있었습니다.
Agent View에 무엇을 추가했는가 ── 차이점 정리
세로와 가로의 차이를 바탕으로, Foreman이 Agent View에 구체적으로 무엇을 추가했는지 정리합니다.
전반부에서 언급한 4가지 공식 기능 중, 모니터링이라는 토대 측면에서 Foreman과 가장 유사한 것은 Agent View입니다.
둘 다 "여러 배경 세션 (background sessions)을 다루는" 도구이기 때문입니다. 다만, 두 기능은 대립하는 관계가 아닙니다.
Foreman은 Agent View가 제공하는 토대를 그대로 활용하면서, 그 위에 "가로 방향의 협조 (horizontal coordination)" 층을 더한 것이라는 덧셈의 관계에 있습니다.
아래 표의 상단 3행은 Agent View가 이미 제공하고 있는 토대(Foreman도 그대로 사용하는 부분)이며, 그 아래부터는 **Foreman에서 추가된 차이점 (diff)**입니다.
| 기능 | Agent View | Foreman |
|---|---|---|
| 여러 배경 세션을 한 화면에서 모니터링 | ✓ | ✓ |
| ... | ↓ 여기서부터 Foreman에서 추가된 차이점 | |
| 미처리 태스크의 공유 정본 (장부·SSoT) | ― | ✓ |
| 세션 간의 태스크 인계 (확보/인계/반송) | ― | ✓ |
| 동일 파일의 병행 편집·이중 실행을 "감지하여 알림" | ― | ✓ |
| ... |
상단 부분(모니터링·답장·태스크 투입)은 Agent View가 잘 수행해 주는 부분입니다.
Foreman은 그 부분을 새로 만들지 않으며, 오히려 worker를 뿌릴 때 Agent View를 그대로 열어서 사용하기도 합니다.
그 토대 위에 하단 부분 ── 대등한 세션끼리 동일한 코드베이스 (codebase)에서 충돌하지 않기 위한 "인수인계의 층" ── 을 겹쳐 놓은 것이 Foreman입니다.
충돌을 피하는 방식도 흔히 "대조적이다"라고 말해집니다.
공식적인 병행 방식은 작업 공간 (git의 worktree)을 나누어 "애초에 같은 파일을 건드리지 않게 함"으로써 충돌을 예방합니다. 반면 Foreman은 동일한 작업 트리 (worktree)를 공유한 채로, 장부를 통해 "충돌할 것 같다"라고 서로 알립니다.
다만, 이 두 가지를 "반대되는 사상"으로 대립시키는 것은 솔직히 말해 약간의 과장이었습니다. 이유는 두 가지입니다.
첫 번째. 동일 파일에 대한 쓰기 충돌 그 자체는 작업 공간을 나누면 근절할 수 있습니다. 나중에 감지하여 알리는 것은 나누지 않았기 때문에 발생하는 사후 처리일 뿐, 예방보다 강력한 수단은 아닙니다. 실제로 저도 index나 commit의 경합으로 몇 번인가 사후 처리를 해왔습니다. 따라서 파일 충돌만 놓고 본다면 분리하는 쪽이 더 강력합니다 ── 이 점은 공식 방식에 우위가 있습니다.
두 번째. 그렇다면 Foreman은 왜 나누지 않는가. 사실 장부는 작업 트리 외부에 (고정 경로에) 놓여 있습니다. 즉, "미처리 태스크를 일원 관리하여 이중 실행을 방지한다 / 세션을 넘나들며 인수인계한다"라는 Foreman의 핵심은, worktree로 나누더라도 깨지지 않습니다. 양자는 본래 either/or 관계가 아니라 겹쳐질 수 있습니다. 제가 동일한 트리를 공유하는 이유는 "감지하는 쪽이 더 훌륭해서"가 아니라, 수 시간에서 수 일간 지속되는 대화 세션끼리 "지금 누가 무엇을 건드리고 있는지"를 그 자리에서 서로 확인할 수 있는 편의성 ── trunk 기반의 지속적 통합 (CI)에 가까운 감각 ── 을 우선시했기 때문입니다. 이는 예방과 맞바꾼 선택이며, 정답이 하나로 정해지는 문제는 아닙니다.
또 하나, 솔직히 덧붙이겠습니다. 사실 공식 기능에도 "공유 태스크 리스트"를 갖는 기능이 있습니다 ── 전반부 표에서 언급한 Agent Teams (실험적 기능)입니다.
팀의 태스크를 teammate가 직접 가져가서 진행한다는 점은 Foreman의 장부와 매우 닮아 있습니다.
다만 Agent Teams는 (1) 리더 역할을 하는 에이전트가 주도한다 (=지휘자가 있다), (2) 하나의 세션에 닫힌 팀으로, 독립된 세션을 넘나들 수 없다, (3) 쟁탈 방지를 위해 파일 락 (file lock)을 사용한다 (제가 바로 버린 방식) ── 라는 설계였습니다.
그래서 제가 원했던 "지휘자 없음·독립된 세션 간·락(lock) 없음"의 병행 협조는 Agent Teams로도 채워지지 않았습니다.
바꿔 말하면, Foreman은 Agent Teams와 마찬가지로 "각자가 리스트에서 태스크를 가져가는" 방식을 채택하면서, 그 리더 역할만을 제거한 것입니다.
이 "리더의 부재"야말로 Foreman에 사령탑이 없다는 의미입니다. 뒤집어 말하면, 저에게 부족했던 것은 공식 기능 그 자체가 아니라, 공식이 주로 상정하는 "세로" 모델만으로는 채워지지 않는 "가로" 방향의 병행 모델이었다는 뜻이 됩니다.
다시 말해, Foreman은 Agent View를 대체하는 것이 아니라, Agent View에 「공유 장부」와 「충돌 회피」를 더한 것입니다.
실제로 17일간 돌려보며 건수를 세어보았다
여기서부터가 본론입니다.
「편해진 것 같다」는 느낌만으로는 기사가 될 수 없습니다.
장부는 추가 전용이므로 과거의 이벤트가 전부 남아 있습니다. 이를 집계하면, 자신의 업무 방식이 실제로 어떻게 변했는지를 숫자로 확인할 수 있을 것입니다. 직접 해보았습니다.
대상은 장부가 움직이기 시작한 2026년 6월 13일부터 7월 1일까지의 약 2주 반입니다. 가공되지 않은 이벤트 행(row)은 약 1,200행 정도 있었습니다.
도입 전 ── 셀 수는 있어도, 움직일 수는 없었다
솔직히 말하면, Foreman을 도입하기 전에도 태스크를 「세는」 것 자체는 가능했습니다.
백로그(Backlog)나 인수인계 메모에 적어두면 남은 것이 몇 개인지 정도는 알 수 있습니다.
정말로 곤란했던 것은 그 점이 아니었습니다. 적어둔 태스크를 기록으로서 “움직일” 수단이 없었다는 점입니다.
예를 들어, 누가 착수 중인지 기록하여 중복 수령을 방지하거나, 상태를 「착수 → 진행 → 완료」로 전이시키거나, 끝난 것이나 불필요해진 것을 정리하거나, 쌓여 있는 미정리 태스크를 「지금 바로 착수 가능 / 보류 / 더 이상 불필요」로 분류하는 ── 이러한 조작을 백로그의 Markdown으로는 할 수 없었습니다.
Markdown은 “읽기” 위한 것이지, 태스크를 조작하는 그릇이 아니었기 때문입니다. 그래서 오래된 태스크가 아무도 건드리지 않은 채 계속 쌓여갔습니다.
Foreman에서는 태스크가 추가 전용 장부에 올라가는 “레코드(Record)”가 되어, 일련의 조작(CRUD)과 정리가 가능해졌습니다. 도입 전후를 조작 관점에서 비교하면 다음과 같습니다.
| 조작 | 도입 전 (백로그 Markdown) | 도입 후 (Foreman의 장부) |
|---|---|---|
| 생성 (Create) | 수동으로 써넣기 | add (종류·완료 조건과 함께 등록) |
| 읽기 (Read) | 육안으로 세기 | list / next (상태·담당·신선도와 함께 목록화) |
| 갱신 (Update) | 덮어쓰기 (누구의 버전이 최신인지 모호함) | claim / progress / done (상태 전이가 이력으로 남음) |
| 삭제 (Delete) | 행을 삭제 (이력까지 삭제됨) | cancel / release (취소·반환도 이력으로 남김) |
| 정리 (Triage) | 수작업 · 기준 없음 | 미정리 태스크를 「착수 가능 / 보류 / 더 이상 불필요」로 분류하여 일괄 정리 |
세는 것뿐이라면 이전에도 할 수 있었습니다. 변한 것은, 태스크를 “움직일 수 있는 기록”으로 만들 수 있게 되었다는 점입니다.
이 「정리 (Triage)」 단계가 추가됨으로써, 쌓여 있는 미처리 작업을 방치하지 않고 착수 가능한 것부터 처리할 수 있게 되었습니다.
장부를 도입한 첫 며칠 동안은 아직 아슬아슬한 상태였습니다.
하나의 세션에서 돌리던 6월 14~15일에는, 태스크를 추가하는 속도가 처리하는 속도를 앞지르고 있었습니다.
예를 들어 6월 15일에는 33건을 새로 쌓았고, 끝낸 것은 12건이었습니다. 차이인 21건이 그날 안에 backlog로 쌓여갔습니다. 하나의 창(Window)에서 순차적으로 해내는 한, 하고 싶은 일의 증가를 처리가 따라잡지 못했던 것입니다.
도입 후 ── 별도의 창으로 뿌릴 수 있게 되어, 흐름이 바뀌었다
전환점은 6월 16일이었습니다. 장부를 SSoT(Single Source of Truth, 단일 진실 공급원)로 삼고, orchestrator가 worker의 별도 창으로 태스크를 뿌리는 운영 방식이 자리를 잡으면서, 하루에 장부에 활동을 기록하는 세션 수가 단번에 늘어났습니다. 이 기간의 데이터를 집계하면 다음과 같습니다.
| 지표 | 실측치 (2026-06-13 ~ 07-01 · 약 2주 반) |
|---|---|
| 추적한 태스크 (distinct) | 342건 |
| ... | 35 (※ 동시 병행이 아닌, 하루 총합) |
이 342건이 실제로 무엇이었는지도 장부에서 찾아낼 수 있습니다.
종류를 지정한 것들만 세어보면, E2E 실행·수정이 18건, 테스트 관점 및 테스트 케이스 생성이 14건, 화면 조사 13건, 평가 파이프라인 개수가 9건, 그 외 툴 정비 등이 이어집니다 (대부분은 제대로 종류를 지정하지 않았으므로, 어디까지나 내역의 참고치입니다).
모두 순차적으로 진행했다면 하나씩 차례를 기다려야 했을 종류의 작업들입니다.
주별 「완료 건수」를 나열하면 흐름의 변화가 명확히 나타납니다.
장부가 움직이기 시작한 첫 며칠(주말인 6/13~14) 동안 완료된 것은 10건이었습니다.
병행 실행이 본격화된 첫 번째 풀 가동 주간에는 100건, 그다음 주에는 134건까지 늘어납니다.
그리고 추적한 전체 태스크의 약 75%(256건)는, 제가 기점으로 만든 것이 아닌 다른 창(window)의 AI가 처리하고 있었습니다. 하나의 창에서 순서대로 기다렸다면 물리적으로 해낼 수 없었을 양입니다.
주목해야 할 점은 건수뿐만 아니라 백로그 (backlog)의 방향이 바뀌었다는 것입니다.
도입 직후에는 「추가 > 완료」 상태로 계속 쌓이기만 했으나, 병행 실행이 정착된 이후에는 완료가 추가를 따라잡았고, 최종적으로 추적한 342건 중 약 85%를 처리할 수 있었습니다.
하고 싶은 일의 증가에 처리가 마침내 따라잡은 것입니다. 이것이 제가 실감하고 있는 가장 큰 변화입니다.
한 가지 보충하겠습니다. 여기서 집계하는 「완료」는 단순히 손을 움직여 끝낸 숫자가 아닙니다.
특히 테스트 관점이나 테스트 케이스 생성에 대해서는 그때마다 자동 평가 (eval)를 실시하여, 우리의 품질 기준(100점 만점에 90점 이상)을 통과한 것만을 「완료」로 집계하고 있습니다.
병행 실행으로 건수를 늘리더라도, 이 품질의 문턱은 낮추지 않았습니다. 속도를 위해 질을 떨어뜨리고 있지는 않은지 ── 그 부분은 측정하여 담보하고 있다고 생각합니다.
측정해도 알 수 없는 것 ── 이 운용의 솔직한 한계
지금까지 숫자를 나열해 왔지만, 지킬 수 있는 것만 적으면 신뢰할 수 없는 글이 되기에, 이 숫자가 증명하지 못하는 것도 명확히 적어 두겠습니다. 한계는 네 가지가 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기