여러 개의 코딩 에이전트를 실행하는 것은 빠르게 느껴지지만, 첫 세 개의 '완료된' 브랜치가 모두 같은 릴리스를 목표로 할 때 문제가 발생합니다. 각 에이전트는 자체 워크트리(worktree) 내에서 녹색 테스트 실행을 가질 수 있지만, 결합된 변경 사항은 임포트(import)를 깨뜨리거나, 테스트를 약화시키거나, 혹은 다른 브랜치의 가정을 조용히 뒤집어 놓을 수 있습니다. Git은 겹치는 라인(overlapping lines)은 잡아낼 수 있습니다. 하지만 한 에이전트가 함수 이름을 변경한 반면 다른 에이전트는 이전 이름의 호출자를 추가했다는 같은 의미상의 충돌은 알려주지 못합니다.
해결책은 모든 에이전트에게 더 큰 프롬프트(prompt)를 제공하거나, CI가 녹색일 때마다 병합하도록 두는 것이 아닙니다. 병렬 작업과 공유 브랜치 사이에 작고 명시적인 **AI 코딩 에이전트 병합 큐 (AI coding agent merge queue)**를 하나 배치해야 합니다. 이 큐는 제안된 변경 사항을 받아 현재 베이스(base)와 재검사하고, 의미 있는 충돌(meaningful collisions)을 감지하며, 해당 변경 사항이 적용되거나 중단된 이유를 기록합니다.
이 가이드는 도구에 구애받지 않습니다. 에이전트가 터미널, IDE 또는 내부 오케스트레이터에서 실행되는지에 관계없이 작동합니다. 목표는 간단합니다: 구현은 병렬화하고, 통합은 증거와 함께 순차적으로(serialize) 처리하는 것입니다.
워크트리가 첫 번째 문제만 해결하는 이유
별도의 Git 워크트리(worktree)는 강력한 기본 설정입니다. 이는 실행 중인 한 에이전트가 다른 에이전트의 파일을 덮어쓰는 것을 방지하고, 각 작업을 검사하거나 폐기하기 쉽게 만듭니다. 하지만 워크트리는 파일 트리만 격리할 뿐, 의도(intent)를 격리하지 못합니다.
두 개의 에이전트가 같은 커밋(commit)에서 시작한다고 상상해 봅시다:
| Agent | Task | Local result |
|---|
| A | getUser(id)를 getAccount(id)로 교체하기 | 브랜치 내 테스트 통과 |
| B | getUser(id) 이후에 감사 로깅 추가하기 | 브랜치 내 테스트 통과 |
만약 A가 먼저 적용된다면, B는 rebase하는 데 실패할 수 있습니다. 더 나쁜 것은, 광범위한 호환성 래퍼(compatibility wrapper)가 추가된 후 B가 깨끗하게 병합되더라도 잘못된 식별자(identifier)를 기록할 수 있다는 것입니다. 이것이 바로 의미론적 충돌(semantic collision)입니다. 코드는 충돌 마커(conflict markers)가 없지만, 결합된 동작은 틀린 것입니다.
최근 개발자 논의는 정확히 이러한 압박을 설명합니다: worktree를 사용하면 동시 세션이 가능하지만, 팀은 여전히 단일 랜딩 레인(landing lane), 파일 소유권, 그리고 통합 검사(integration check)가 필요합니다. 또한 최근 병합 충돌 데이터셋(merge-conflict dataset)에서도 에이전트가 생성한 풀 리퀘스트에서 텍스트 충돌이 흔하다는 것을 발견했습니다. 이러한 신호들을 각 저장소마다 한 연구의 백분율에 의존할 이유가 아니라, 통합 경계(integration boundary)를 설계해야 할 이유로 간주해야 합니다.
큐를 상태 기계(state machine)로 모델링하기
단순히 브랜치 목록에 불과한 큐는 피해야 합니다. 각 후보(candidate)는 재시도 과정에서 오래된 작업이 병합되거나 인간의 결정이 손실되는 것을 방지하기 위해 영구적인 기록(durable record)을 가져야 합니다.
type QueueState =
| "proposed"
| "claimed"
...
중요한 필드는 baseSha, headSha, 그리고 attempt입니다. "최신 브랜치(latest branch)"를 증거로 절대 받아들여서는 안 됩니다. 큐는 에이전트가 정확히 어떤 커밋을 생성했고, 그것이 어떤 베이스(base)를 기준으로 평가되었는지 알아야 합니다.
최소한의 전이 규칙은 다음과 같습니다:
proposed → claimed → rebasing → validating → merged
↘ needs_review → rejected | proposed
오직 큐만이 후보를 merged 상태로 진행시킵니다. 구현 에이전트(implementation agent)는 제안을 생성하고, 증거를 첨부하며, 피드백에 응답할 수 있지만, 보호된 브랜치(protected branch)에 직접 쓰지는 않습니다.
편집 전에 에이전트에게 의도(intent)를 선언하도록 요청하기
파일 잠금(File lock)은 유용하지만, 경로만으로는 너무 광범위합니다. 두 개의 안전한 변경 사항이 하나의 공유 구성 파일에 영향을 줄 수 있고, 두 개의 위험한 변경 사항은 동일한 API 계약을 변경하면서도 서로 다른 파일에 영향을 줄 수 있습니다. 다음의 두 가지 가벼운 선언(lightweight claims)을 사용해야 합니다:
- 경로 선언(Path claim): 예상되는 파일 또는 디렉토리.
- 행동 선언(Behavior claim): 작업이 변경할 것으로 예상하는 인터페이스, 라우트(routes), 마이그레이션(migrations), 기능 플래그(feature flags) 또는 불변성(invariants).
예를 들어:
task: add-invoice-export
paths:
- apps/api/src/invoices/**
...
클레임(claims)은 보안 경계가 아닌 계획 데이터입니다. 에이전트는 필요한 추가 파일을 발견할 수 있습니다. 이 경우, 해당 파일로 클레임을 업데이트하고 충돌 감지(collision detection)를 다시 실행하도록 요구해야 합니다. 이는 작업이 시작된 후에 범위가 조용히 확장되는 것보다 훨씬 안전합니다.
Git보다 먼저 충돌을 감지하기
후보(candidate)가 큐에 들어오는 즉시 저렴한 비용의 충돌 검사(collision check)를 실행해야 합니다. 이 검사는 해당 후보의 클레임을 대기 중이거나 최근에 적용된 모든 후보와 비교해야 합니다.
function collisionRisk(a: MergeCandidate, b: MergeCandidate) {
const sharedPath = a.claims.some((p) => b.claims.includes(p));
const sameContract = a.claims.some((c) => b.claims.includes(c));
...
}
실제 구현에서는 혼합된 문자열 배열 대신 구조화된 필드(structured fields)를 사용해야 합니다. 간단하게 시작한 다음, 가치가 높은 신호들을 추가하세요:
- 변경된 내보내기 심볼 또는 API 스키마 이름;
- 데이터베이스 마이그레이션 및 생성 클라이언트(generated-client) 변경 사항;
- package-lock, 의존성(dependency), 또는 빌드 설정(build-config) 편집;
- 기존 테스트, 테스트 러너(test runners), 또는 커버리지 규칙에 대한 수정;
- 기능 플래그 이름 및 환경 변수;
- 이전 후보가 이 후보의
baseSha 이후에 변경한 파일.
위험한 쌍(risky pair)은 needs_review로 표시하거나 순서를 강제해야 합니다. 자동으로 거부해서는 안 됩니다. 큐는 처벌 시스템이 아니라 조정 도구입니다.
일회성 통합 작업 트리(disposable integration worktree)에서의 리베이스(Rebase)
에이전트의 작업 디렉토리(working directory)에서 후보를 절대 리베이스하거나 병합하지 마십시오. 보호된 브랜치로부터 새로운 통합 작업 트리(integration worktree)를 생성해야 합니다. 이렇게 하면 모든 큐 시도가 재현 가능해지고, 실패한 충돌 해결 과정이 원래 제안으로부터 멀리 떨어지게 됩니다.
git fetch origin main
git worktree add --detach ../integrate-task-184 origin/main
cd ../integrate-task-184
...
체리픽 충돌(cherry-pick conflicts)이 발생하면 작업 트리(worktree)를 보존하고, 충돌하는 경로, 현재 베이스 SHA, 그리고 충돌 출력을 기록해야 합니다. 원래 에이전트가 이 제한된 복구 작업을 받도록 하거나, 사람에게 라우팅하세요. 별도의 “병합 에이전트(merge agent)”가 원본 작업 계약에 대한 접근 권한 없이 광범위한 제품 결정을 내리게 해서는 안 됩니다.
깨끗하게 시도할 경우, 정확히 통합된 트리(integrated tree)를 커밋하거나 보호된 브랜치(protected branch)를 일반적인 풀 리퀘스트(pull request) 프로세스를 통해 패스트 포워드(fast-forward)하세요. 새로운 베이스 SHA를 기록합니다. 다음 후보는 그 새로운 상태에 대해 유효성 검사를 수행해야 합니다.
하나의 비싼 CI 게이트 대신 계층적 검사 사용하기
매우 작은 변경 사항마다 전체 스위트(full suite)를 재실행하는 것은 종종 너무 느립니다. 검증을 건너뛰는 것은 더 나쁩니다. 레이어(layers)를 사용하고, 그 레이어를 후보 수신 시 명시적으로 지정하세요.
| 레이어 | 예시 | 요구할 때 |
|---|
| 디프 정책 (Diff policy) | 비밀 파일 없음, 비활성화된 테스트 없음, 변경된 CI 명령어 없음 | 모든 후보 |
| ... | | |
디프 정책은 코딩 에이전트에게 특별한 주의가 필요합니다. 테스트가 통과했다는 것은 약한 증거일 수 있습니다. 만약 동일한 후보가 단언(assertion)을 삭제하거나, skip을 추가하거나, 테스트 명령어를 변경했거나, 실패를 숨기는 피처(fixture)를 수정했다면 더욱 그렇습니다. 해당 편집 사항들을 검토 대상으로 표시하고, 악의적인 의도를 가정하지 마십시오.
policy:
block_if_changed:
- .github/workflows/**
...
이것을 리포지토리(repository)에 맞게 조정해야 합니다. 테스트 변경 사항에 대한 전면적인 금지는 에이전트가 커버리지(coverage)를 추가하는 것을 불가능하게 만듭니다. 유용한 구분점은 새로운 테스트를 추가하는 것과 기존의 단언이나 정확성을 증명하는 명령어를 수정하는 것 사이입니다.
의미론적 검사를 구체화하기
의미론적 충돌 감지(Semantic collision detection)는 마법 같은 AI 리뷰어 호출처럼 들릴 수 있습니다. 기존 도구가 생성할 수 있는 사실들부터 시작하세요:
- 생성된 OpenAPI, GraphQL, protobuf 또는 TypeScript 선언부의 차이(diff)를 비교합니다.
- 마이그레이션(migration)이 대기 중인 다른 마이그레이션이 사용하는 테이블을 변경하는 경우 실패합니다.
- 변경된 코드와 다른 패키지에서 작은 계약 테스트(contract test)를 실행합니다.
- 이름이 변경된 공개 심볼(public symbol)에 대한 검색 기반 검사를 실행합니다.
- 리뷰어(인간 또는 에이전트)에게 고정 질문("현재 어떤 기존 소비자(consumer)가 다르게 동작할까요?")에 답하도록 요청합니다.
다음은 리뷰어가 긴 에이전트 기록을 다시 읽지 않고도 증거를 볼 수 있게 해주는 간결한 영수증(receipt)입니다:
{
"candidate": "task-184",
"head": "a13e9d2",
...
}
여기서 병합 큐(merge queue)는 자동 병합 스위치와 다릅니다. 녹색 배지(green badge)로 변환하는 대신 불확실성을 유지합니다.
베이스가 이동할 때 발생하는 상황 결정하기
병렬 에이전트와 함께 베이스(base)는 자주 이동할 것입니다. 큐가 바빠지기 전에 이 정책을 정의하세요:
- 검증 후 문서만 변경된 경우, 위험 정책(risk policy)이 허용하는 경우 집중 테스트 증거를 재사용합니다.
- 주장된 경로(claimed path)나 계약(contract)이 변경된 경우, 깨끗한 통합 작업 트리(integration worktree)에서 후보(candidate)를 다시 실행합니다.
- 후보가 리베이스(rebase)에 두 번 실패한 경우, 자동 재시도를 중단하고 더 작은 작업 또는 명시적인 충돌 해결 결정(conflict-resolution decision)을 요청합니다.
- 마이그레이션, 인증(auth), 결제(billing), 배포(deployment), 또는 보안 파일이 변경된 경우, 테스트 상태와 관계없이 인간의 승인을 요구합니다.
이는 일반적인 실패 모드를 방지합니다. 즉, 아무도 원래 작업 가정이 만료되었음을 알아차리지 못하는 사이에 큐가 오래된 브랜치를 반복적으로 리베이스하여 깨끗해 보이는 상태로 만드는 경우입니다.
위험한 속도를 보상하지 않으면서 큐 측정하기
저장소별 몇 가지 운영 측정 지표를 추적합니다:
- 큐 대기 시간 및 통합 시간;
- 리베이스 실패율 및 의미론적 충돌률(semantic-collision rate);
- 병합 후 되돌리기 또는 재개방 비율;
- 인간 검토가 필요한 후보의 백분율;
- 검사 계층별 검증 비용.
‘자동 병합 비율(automerge percentage)’을 북극성 지표로 사용하지 마십시오. 건강한 큐는 때때로 작업을 중단시킵니다. 더 나은 결과는 잘 정의된 제안(well-scoped proposal)에서 신뢰할 수 있는 공유 브랜치까지의 경로를 단축하는 것입니다.
실용적인 도입 계획 (A practical rollout plan)
하나의 저장소와 두 개의 동시 에이전트부터 시작하십시오. 먼저 전용 작업 트리(dedicated worktrees), 불변 커밋 ID(immutable commit IDs), 단일 병합 연산자(single merge operator)를 추가합니다. 다음으로 경로 및 동작 주장(path and behavior claims)을 기록하고, 그 후에 신규 기반에 초점을 맞춘 테스트(fresh-base focused tests)를 추가합니다. 이러한 단계들이 안정화된 후에만 저위험 병합을 자동화해야 합니다.
수동 탈출구(manual escape hatch)를 유지하십시오. 개발자는 큐를 일시 중지하고, 폐기 가능한 통합 트리(disposable integration tree)를 검사하며, 명확한 결정을 내릴 수 있어야 합니다. 자동화는 그 결정 자체를 사라지게 할 때가 아니라, 감사하기 더 쉽게 만들 때 가치가 있습니다.
자주 묻는 질문 (FAQ)
한 코딩 에이전트에게 병합 큐가 필요한가요?
보통은 필요하지 않습니다. 한 에이전트와 한 명의 사람이 일반적인 풀 리퀘스트 검사(pull-request checks)를 사용할 수 있습니다. 독립적인 작업들이 시간적으로 겹치거나, 브랜치가 리뷰 전에 자주 오래되거나(stale), 에이전트가 팀이 안전하게 통합할 수 있는 속도보다 빠르게 변경 사항을 제안하는 경우에 큐를 추가하십시오.
병렬 코딩 에이전트에게 Git 작업 트리가 충분한가요?
아닙니다. 작업 트리는 동시 파일 쓰기를 방지하지만, 의도(intent)를 예약하거나, 미래의 병합 충돌을 해결하거나, 에이전트가 시작한 이후에 도착한 변경 사항과 브랜치를 검증하지는 않습니다. 이를 신규 통합 시도(fresh integration attempt)와 결합하십시오.
에이전트가 자체 병합 충돌을 해결해야 하나요?
원본 작업 계약(original task contract)과 현재의 디프(diff)를 받으면 좁고 설명이 잘 된 충돌은 해결할 수 있습니다. 해결 과정에서 공개 계약(public contract), 마이그레이션(migration), 권한 규칙(permission rule) 또는 제품 동작(product behavior)이 변경될 때는 에스컬레이트해야 합니다. 깔끔한 병합이 의도가 보존되었음을 증명하는 것은 아닙니다.
의미론적 병합 충돌은 어떻게 감지하나요?
계약과 생성된 아티팩트(artifacts)를 비교하고, 소비 패키지(consuming packages)에서 테스트를 실행하며, 변경된 내보내기 항목(exports) 및 라우트를 검사하고, 영향을 받는 소비자(affected consumers)에 대한 고정된 리뷰어 질문을 요구하십시오. 이러한 표적화된 확인 사항들을 Git의 텍스트 충돌 감지 기능과 결합하십시오.
검사 통과 후 에이전트 변경 사항을 자동으로 병합할 수 있나요?
네, 명확하게 위험도가 낮은 경로(low-risk paths)이며 깨끗한 최신 기반 검증(fresh-base validation)이 이루어졌고 충돌 신호가 없는 경우에 한합니다. 민감한 파일이나 불확실한 변경 사항에는 인간의 검토를 유지해야 합니다. 자동 병합은 단순히 명령 실행 결과로 나오는 기본값이 아니라, 증거에 의해 뒷받침되는 정책적 결정이어야 합니다.
실패한 통합 작업 트리(integration worktree)는 어떻게 처리해야 하나요?
충돌 출력(conflict output)과 검증 아티팩트(validation artifacts)를 보존할 만큼 충분히 오래 유지한 다음, 병합됨(merged), 거부됨(rejected), 또는 포기됨(abandoned)과 같은 기록된 상태를 사용하여 정리해야 합니다. 단순히 나이가 들었다는 이유만으로 삭제해서는 안 됩니다. 에이전트나 검토자가 여전히 그 증거가 필요할 수 있기 때문입니다.