화면과 테스트처럼 서로 다른 작업을 여러 에이전트에게 맡겨 동시에 진행하는 것을 병렬 실행이라고 부른다. 작업을 넷으로 나누면 시간도 그만큼 줄어들 것 같지만, 먼저 확인해야 할 것이 있다.
나누는 순간 같은 파일에 쓰는 문제가 먼저 걸린다. 에이전트마다 고쳐도 되는 폴더를 미리 정해 두는데, 그걸 소유 경로라고 부른다. 두 소유 경로가 같은 파일을 포함하면 둘이 그 파일을 고치고 나중에 저장한 쪽이 앞선 쪽의 수정을 덮어쓴다. 둘 다 자기 몫을 마쳤다고 보고하니 무엇이 사라졌는지가 어디에도 안 남는다. 문서는 그 덮어쓰기가 “리뷰에서도 안 잡히고 diff에도 안 남는다” 고 적어 뒀다.
curvez의 team-orchestration은 에이전트를 띄우기 전에 이 확인을 끝내도록 절차를 정했다. 여기서는 문서에서 부르는 대로, 띄운 에이전트를 워커라고 쓰겠다.
1. 작업을 나누기 전에 팀이 필요한지 판단하기
문서는 먼저 팀이 아닌 것을 팀으로 만들지 마라고 못 박는다.
지금까지의 대화 내용을 새 워커에게 다시 넣어 주고 인수인계도 한 번 왕복해야 하니, 워커 1명을 띄우는 비용이 단일 파일 작업보다 크기 때문이다. 그래서 절차 1번에서 팀이 필요한지 판정한다.
| 상황 | 판단 |
|---|---|
| 단일 파일·단일 관심사 | 팀을 만들지 않는다. 직접 처리하거나 담당 1명에게만 넘긴다 |
| 파일·모듈 여러 곳에 걸친다 | 팀 |
| 구현과 검증을 동시에 굴릴 수 있다 | 팀 |
| 앞 결과가 없으면 뒤가 시작 못 한다 | 팀이지만 병렬 아님. 순차 라운드로 쪼갠다 |
| 요구사항이 한 문장으로 안 써진다 | 팀 이전에 curvez-requirements 단독 1라운드 |
표 1. 팀을 만들지 말지 가르는 상황별 판단
네 번째 줄이 헷갈리기 쉬운 자리다. 순차로 돌 거면 혼자 하는 것과 같아 보이는데, 쪼개면 담당을 나눌 수 있다. 자기 코드를 자기가 리뷰하는 상황을 막으려면 검증 담당을 분리해야 한다. 그건 세 번째 줄에 따로 적혀 있고 동시에 돌리느냐와 별개다.
판정의 완료 조건도 값으로 정해져 있다. .curvez/profile.json에서 stack 값을 읽었을 것. “팀이다 / 팀이 아니다”를 근거 한 줄과 함께 적었을 것. 팀이 아니면 여기서 절차가 끝난다.
profile.json이 없으면 워커를 하나도 띄우지 않는다. status: blocked로 끝내고 blocked_on에 who: user와 “bootstrap을 먼저 실행해야 한다”를 적는다. 그 파일이 무엇을 담는지는 이 시리즈 1편에 적었다. 프로젝트별 경로와 실행 명령을 에이전트 설정으로 분리하기 다.
2. 겹치는 파일은 어떻게 찾나
팀이 필요하다고 판정하면 후보 에이전트의 정의 파일 plugins/curvez/agents/<name>.md를 읽고 누구를 같은 라운드에 둘지 정한다. 다른 자료는 이 판정에 쓰지 않는다. 거기 ## 협업과 팀 내 위치의 파일 소유권을 읽어 서로 교차 비교한다. 그 자리에 쓸 수 있는 폴더가 owns:로 적혀 있다.
| 상황 | 판단 |
|---|---|
| 소유 경로가 완전히 분리 | 병렬 |
| 소유 경로가 한 글자라도 겹친다 | 병렬을 포기하고 순차로 강등한다 |
| 한쪽이 다른 쪽 산출물을 읽어야 한다 | 순차 |
| 소유권이 정의에 안 적혀 있다 | 순차 |
.curvez/handoff/를 여럿이 쓴다 |
병렬 허용 |
표 2. 소유 경로 비교에 따른 병렬·순차 판단
네 번째 줄에 붙은 이유가 이 표의 근거다. “겹치지 않는다는 근거가 없다. 근거 없는 병렬은 도박이다.” 소유권을 안 적어 둔 에이전트는 겹치지 않을 수도 있다. 그래도 겹치지 않는다고 말할 근거가 없다. 그래서 순차로 내린다.
다섯 번째 줄에서 병렬을 허용하는 건 인수인계 파일 이름이 <from>.<timestamp>.json이기 때문이다. 디렉터리는 같아도 워커마다 다른 이름으로 파일을 쓴다.
그런데 이 표를 그대로 돌리면 단일 저장소에서 병렬이 영영 안 되는 경우가 생긴다. owns: ${paths.web}이 단일 저장소에서는 .으로 치환돼, 저장소 최상위 폴더 전체가 자기 것이 된다. 잘못 들어간 값이 아니라 정상 값이다. 워크스페이스가 아닌 저장소에서 bootstrap.mjs가 그 값을 넣게 되어 있고 presets/stack/nextjs.md도 “paths.web은 앱 패키지의 루트다. 소스 디렉터리가 아니다”를 정본으로 삼는다.
그러면 curvez-nextjs의 소유 범위가 paths.tests도 .curvez/도 docs/retro/도 전부 포함할 만큼 넓어져, 쓰기 권한을 가진 어떤 워커와도 병렬이 안 된다. 그래서 차감 절차가 붙어 있다. A의 owns가 B의 owns를 포함하면, A의 정의 파일에서 “…는 읽기만 한다”로 적힌 배제 목록을 읽는다. B가 거기 있으면 차감하고 병렬을 허용한다. 배제 목록이 없거나 B가 없으면 순차로 내린다.
배제 목록은 에이전트가 “여기는 안 쓴다” 고 선언한 계약이다. 이 선언이 있어야 동시 쓰기가 안 일어난다는 근거로 삼을 수 있다.
주인이 없는 경로가 하나 있다
.curvez/profile.json의 paths.domain에는 소유자가 없다. 여러 곳에서 같이 가져다 쓰는 공유 코드 패키지라 curvez-nextjs도 자기 소유로 선언하지 않는다. 다들 읽기는 하고 쓰기도 하고 싶어 한다.
규칙은 단순하다. 이번 라운드 작업이 paths.domain을 건드리면, 거기 쓰게 될 워커를 동시에 띄우지 않는다. 누가 먼저 도는지도 정해 뒀다. 함수 이름이나 인자를 바꿔 달라고 요청한 쪽이 먼저고 이 기준으로 순서가 안 나오면 blast_radius가 큰 쪽을 먼저 돌린다. 그 변경 때문에 같이 고쳐야 하는 파일이 더 많은 쪽이다.
한쪽 사정으로 공유 함수의 인자를 바꾸면 그 함수를 쓰던 다른 쪽이 깨진다. 그런데 그쪽을 맡은 에이전트가 다음에 실행될 때까지 발견되지 않는다. 동시에 띄웠으면 그 차례가 이번 라운드에 다시 오지 않는다. 깨진 채로 리뷰와 QA 라운드까지 흘러간다. 어느 변경이 원인이었는지 특정할 수 없게 된다.
3. 왜 띄우기 전에 승인을 받나
구성이 정해지면 띄우기 전에 사용자에게 보고한다. 보고에는 아래 항목을 담는다.
- 누가 — 워커의
name목록 - 왜 — 각 워커가 왜 필요한지 한 줄
- 무엇을 — 각 워커의 담당 범위와 소유 경로
- 어떻게 — 병렬인지 순차인지, 순차라면 그 근거
- 몇 라운드 — 예상 라운드 수와 리뷰 루프 상한
하나라도 비면 승인 단계로 넘어가지 못한다. 그리고 이 시점까지 Agent 호출 횟수는 0 이어야 한다.
이때 승인을 받는 건 워커가 각자 격리된 대화창에서 돌아 실행 중에는 사람에게 되물을 수 없기 때문이다. 구성을 잘못 짜고 5명을 띄우면, 각자 읽은 문서와 각자 쓴 코드 5명분을 다 청구하고 나서야 잘못됐다는 게 드러난다. 이미 쓰인 파일은 되돌려야 한다. 문서는 이걸 “승인은 되돌리기 비용이 가장 싼 유일한 지점”이라고 적었다.
예외는 하나다. 같은 세션에서 이미 승인된 구성을 변경 없이 다시 돌릴 때. 리뷰 지적 → 수정 → 재리뷰의 2회차 이후다. 구성원이 한 명이라도 바뀌면 다시 받는다.
수치 상한도 여기서 확정된다. 전체 라인업 12명 중 한 라운드에 동시 실행하는 워커는 최대 5명이고 5를 넘겨야 할 것 같으면 팀을 키우지 말고 라운드를 하나 더 만든다. 재리뷰 루프는 최대 2회로, 초회를 포함해 리뷰가 3번을 넘지 않는다. 2회 안에 안 닫히는 건 코드 문제가 아니라 기준 문제고 기준은 사용자만 정할 수 있다.
띄울 때 걸리기 쉬운 것도 같이 적혀 있다. 어떤 에이전트를 부를지 적는 subagent_type 값에는 플러그인 접두사가 붙어 curvez:curvez-nextjs 형태다. 문서에 적힌 curvez-nextjs는 지칭이지 호출 값이 아니고 접두사를 빠뜨리고 부르면 거부된다(2026-08-23 실측). 그리고 병렬 워커는 한 메시지에서 Agent를 동시에 호출한다. 한 명씩 순차로 호출하면 병렬이 아니다.
general-purpose 나 claude 같은 Tools: * 범용 타입은 워커로 띄우지 않는다. 그 타입은 Agent도구까지 갖고 있어 워커가 또 워커를 띄운다. 라인업 전체에서 tools에 Agent가 있는 건 curvez-orchestrator 하나뿐이다. 워커가 또 워커를 띄우지 못하게 하려는 설계다.
4. 돌아온 것을 어떻게 수합하나
띄운 다음 오케스트레이터가 하는 일은 기다리는 것뿐이다. 워커가 도는 동안 파일을 쓰지 않는다. 그래야 워커가 읽는 시점의 상태가 확정된다.
돌아오면 .curvez/handoff/를 읽어 status 별로 나눈다. 워커가 일을 마치며 남기는 인수인계 파일이고 어디까지 했는지가 done·partial·blocked로 적혀 있다. done 인데 검증 기록(verification)이 비어 있으면 done을 믿지 않고 partial로 취급한다. partial이면 summary의 “남은 것”을 다음 라운드 목록에 넣고 남은 범위가 안 적혀 있으면 그것부터 되묻는다. 이 판정 규칙 자체는 agent-contract가 정본이다. 에이전트의 작업 완료 기준과 인수인계 형식 정하기 에 따로 적었다.
blocked는 blocked_on[].who로 라우팅한다. who가 에이전트 이름이면 그 에이전트에게 넘기고 who: user 인 질문은 여러 워커에게서 올라온 것까지 전부 모아서 한 번에 묻는다. 질문마다 사용자를 멈춰 세우면 워커들이 답을 기다리며 순서대로 선다. 병렬로 띄워 놓고 첫 질문에서 전부 멈추면, 나눠 돌린 이점이 사라진다.
답을 받으면 원래 물어본 에이전트를 같은 맥락으로 재실행한다. 질문 원문과 답변을 짝지어 프롬프트에 넣는다. 답변만 다른 에이전트에게 넘기면 안 된다. 원래 질문을 낸 쪽이 왜 그걸 물었는지 아무도 모르게 된다.
리뷰어 2종은 사정이 다르다. curvez-reviewer와 curvez-structure-reviewer는 파일을 못 쓴다. disallowedTools에 쓰기 도구가 전부 들어 있어서다. 그래서 최종 응답 텍스트 자체를 인수인계 JSON으로 반환하고 오케스트레이터가 그걸 파일로 기록한다. 응답이 JSON이 아니면 1회 재요청하고 그래도 아니면 원문을 .curvez/tmp/에 남긴 뒤 partial로 보고한다. JSON을 지어내 채우지 마라 가 따로 박혀 있다. 지어낸 리뷰 결과는 검증된 것처럼 다음 라운드에 전파된다. 어느 지적이 실제 리뷰였는지 사후에 분리할 수 없다.
두 리뷰어의 지적 목록(findings[])을 합칠 때는 id 앞에 출처를 붙인다. <from>/<id>로 쓴다. 양쪽 다 DUP-01을 쓸 수 있기 때문이다.
curvez-reviewer/DUP-01 ← 원본 id: DUP-01
curvez-structure-reviewer/DUP-01 ← 원본 id: DUP-01, 다른 지적
수합을 마치면 검증기를 돌려 결과를 대조한다.
node "$CLAUDE_PLUGIN_ROOT/scripts/validate-handoff.mjs" .curvez/handoff/
# 핸드오프 파일 수 — 띄운 워커 수와 대조한다
ls -1 .curvez/handoff/*.json | wc -l
# Agent 도구 독점 확인 — 결과가 1이어야 한다
grep -l '^tools:.*Agent' "$CLAUDE_PLUGIN_ROOT"/agents/*.md | wc -l
띄운 워커 수와 새 인수인계 파일 수의 차이가 0이어야 한다. 검증 오류도 0개여야 다음 라운드를 시작한다. 워커가 소유 경로 밖 파일을 썼을 수도 있다. 그때 오케스트레이터는 되돌리지 않는다. 위반 경로와 담당 워커를 보고해 사용자 판단을 받는다.
구현·QA·리뷰가 다 끝나도 curvez-git은 자동으로 돌지 않는다. 커밋 담당이다. 사용자가 커밋·PR·머지를 명시적으로 요청했을 때만 띄운다. 그때도 단독으로 띄운다. owns: none이지만 브랜치 전환이 작업 트리 전체를 바꾸기 때문이다.
5. 도입 비용과 한계
| 무엇을 내주는가 | 어떻게 나타나는가 |
|---|---|
| 라운드마다 승인 왕복이 앞에 붙는다 | 구성원이 한 명이라도 바뀌면 다시 받는다. 무변경 재실행만 예외다 |
| 겹치면 무조건 순차로 내려간다 | 단일 저장소는 paths.web이 .이라, 배제 목록이 없는 에이전트는 영영 병렬이 안 된다 |
| 잘못 짠 구성의 비용이 워커 수만큼 곱해진다 | 5명을 띄웠으면 5명분 토큰을 다 쓰고 나서야 드러난다 |
| 워커끼리 서로를 못 읽는다 | 프롬프트에 없는 것은 워커가 추측한다 |
| 상한에 닿으면 사람이 판단해야 한다 | 재리뷰 2회를 넘으면 더 돌리지 않고 남은 지적·담당·시도 이력을 사용자에게 올린다 |
| 소유권 위반을 되돌리지 않는다 | 경로 밖에 쓰인 파일은 그대로 두고 보고만 한다 |
표 3. 병렬 실행에서 내주는 것과 그것이 드러나는 방식
네 번째 줄에서 하나를 덧붙인다. 워커끼리 서로를 못 읽으면 같은 것을 둘이 조사할 수 있다. 그 중복 비용은 SKILL.md에 적혀 있지 않다. 문서가 적은 건 “프롬프트에 없는 것은 워커가 추측한다”까지고 중복은 그 성질에서 이어 붙인 추론이다. 실제로 얼마나 겹치는지는 확인하지 못했다.
6. 파일 소유 경로를 확인한 뒤 작업 나누기
담당을 정해 먼저 띄우고 결과를 보며 조정하는 방식으로는 겹침이 뒤늦게 드러난다. 그래서 순서를 뒤집었다. 정의 파일에서 소유 경로를 읽어 교차 비교하는 것이 먼저다. 겹치면 라운드를 하나 더 만든다. 그 구성안을 다 보여 준 뒤에 띄운다.

답글 남기기