두 개의 코딩 에이전트가 동일한 이슈를 편집할 때 머지 충돌이 발생하지 않는 이유: git refs의 작동 원리
요약
두 개의 AI 코딩 에이전트가 동일한 이슈를 편집할 때 발생하는 머지 충돌 문제를 해결하기 위한 'grite'를 소개합니다. git ref를 활용한 추가 전용(append-only) 로그와 CRDT 머징 방식을 통해 서버 없이도 충돌 없는 협업 환경을 구축합니다.
핵심 포인트
- git ref(refs/grite/wal)를 활용해 이슈를 불변 이벤트 로그로 관리
- CRDT(Conflict-free Replicated Data Type)를 적용하여 머지 충돌 방지
- CBOR 인코딩과 BLAKE2b 해시를 통한 콘텐츠 주소 지정 방식 사용
- 상태가 코드와 함께 이동하여 브랜치 및 머지 작업과 완벽히 동기화
- 별도의 서버나 데이터베이스 없이 git 저장소만으로 협업 가능
동일한 저장소(repo)에서 두 개의 AI 코딩 에이전트를 실행하면 가장 먼저 깨지는 것은 코드가 아닙니다. 바로 협업(coordination)입니다. 에이전트 A는 인증(auth) 리팩토링을 시작합니다. 병렬로 실행 중인 에이전트 B는 이를 전혀 모른 채 동일한 작업을 시작합니다. 각 에이전트는 빈 컨텍스트 윈도우(context window)와 함께 새로 부팅되기 때문에 지난 세션에서 무엇을 했는지 기억하지 못합니다. 일반적인 해결책들은 문제보다 더 나쁩니다. 저장소 내의 상태 파일(state file)은 모든 디프(diff)를 오염시키고 머지(merge) 시 충돌을 일으키며, 외부 이슈 트래커(issue tracker)를 사용하는 것은 로컬에서 처리되어야 할 작업에 대해 API 토큰, 속도 제한(rate limits), 그리고 네트워크에 대한 강력한 의존성을 의미합니다.
그래서 저는 grite를 만들었습니다. 이는 여러분의 git 저장소 내부에 추가 전용(append-only) 이벤트 로그로 존재하는 이슈 트래커이며, 결정론적 CRDT 머징(CRDT merging)을 통해 두 작성자가 절대 충돌하지 않도록 합니다. 서버도 없고, 데이터베이스도 없으며, 머지 충돌도 없습니다. 오직 git뿐입니다.
핵심 아이디어: 이슈는 이벤트이며, git refs는 로그입니다
Grite는 이슈를 워킹 트리(working tree)의 파일로 저장하지 않습니다. 대신 git ref인 refs/grite/wal 내부에 추가 전용 쓰기 전용 로그(write-ahead log)로 저장합니다. 생성, 댓글, 라벨 변경과 같은 모든 작업은 해당 로그에 추가되는 하나의 불변(immutable) CBOR 인코딩된 이벤트입니다. 여러분의 워킹 트리는 완전히 깨끗하게 유지됩니다. Grite가 작성하는 유일한 추적 파일은 AGENTS.md이며, 이는 에이전트들이 도구를 자동으로 발견할 수 있도록 의도된 것입니다.
상태가 git ref에 존재하기 때문에, 상태는 코드와 함께 이동합니다. 여러분이 브랜치(branch)를 나누면 함께 나뉩니다. 여러분이 머지(merge)하면 함께 머지됩니다. git push를 하면 동기화됩니다. 원격 저장소에 푸시할 수 있다면, 이슈를 동기화할 수 있습니다. 새로운 계정도, 새로운 인프라도, 새로 배워야 할 프로토콜도 없습니다.
작동 방식
깔끔하게 분리된 세 개의 레이어(layers)로 구성됩니다.
git WAL은 신뢰할 수 있는 원천 (source of truth)입니다. 이벤트는 CBOR 청크 (chunks)로 추가되며, 각 이벤트는 이벤트 본문의 BLAKE2b 해시인 콘텐츠 주소 지정 방식 (content-addressed)의 EventId로 식별됩니다. 이러한 콘텐츠 주소 지정 방식은 로그의 변조 여부를 쉽게 확인할 수 있게 (tamper-evident) 만듭니다. 이벤트의 단 1바이트만 변경해도 ID가 더 이상 일치하지 않게 되어 체인이 깨지기 때문입니다. 서명 (Signing)은 이벤트당 Ed25519를 선택적으로 사용할 수 있으므로, 어떤 행위자 (actor)가 무엇을 생성했는지 증명할 수 있습니다.
**구체화된 뷰 (materialized view)**는 이벤트 로그를 현재 이슈 상태로 투영 (project)하는 임베디드 키-값 저장소인 sled입니다. 이것이 실제로 쿼리하게 되는 캐시 (cache)입니다. 시작 시 WAL로부터 재구축(rebuild)된 후 점진적으로 업데이트됩니다. 이를 삭제해도 안전하며, 단순히 로그를 다시 재생 (replay)할 뿐입니다. 이 프로젝션 (projection)은 CRDT 의미론 (semantics)을 사용하는데, 스칼라 필드 (scalar fields)에는 마지막 쓰기 승리 (Last-Write-Wins) 방식을, 레이블 (labels)과 같은 항목에는 교환 가능한 집합 (commutative-set) 의미론을 적용합니다. 이것이 바로 충돌 없는 다중 에이전트 편집 (conflict-free multi-agent editing)의 핵심 비결입니다. 두 에이전트가 두 대의 머신에서 동일한 이슈를 편집하더라도, 두 변경 사항 모두 머지 (merge) 과정에서 살아남으며, 머지 순서와 상관없이 결과는 동일합니다.
**CLI 및 선택적 데몬 (daemon)**은 인터페이스 역할을 합니다. 데몬은 sled 뷰를 따뜻하게 유지 (warm)하고 병렬 읽기를 허용하면서 쓰기를 직렬화 (serialize)하지만, 정확성을 위해 필수적인 것은 아닙니다. CLI는 단독으로 작동하며 도움이 되는 경우에만 데몬을 자동으로 생성합니다.
에이전트가 세션당 수백 개의 쿼리를 실행할 때 중요한 수치는 다음과 같습니다:
| 작업 (Operation) | 시간 (Time) |
|---|---|
| 이슈 생성 (Issue create) | ~5ms (단일 이벤트 추가) |
| ... |
전체 WAL 재구축은 $O(n)$이므로 주기적인 스냅샷 (snapshots)이 존재합니다. 스냅샷은 클론 (clone) 후 모든 것을 다시 재생하는 콜드 재구축 (cold rebuild) 대신 델타 (delta)를 재생하도록 하여 시간을 단축합니다. 각 행위자 (actor)는 자신만의 격리된 sled 데이터베이스를 가지므로, 한 에이전트의 과도한 쿼리 부하가 다른 에이전트를 차단하지 않습니다.
사용법
사용자 경로는 일반적인 트래커와 유사하지만, 내부적으로 git 의미론을 따릅니다:
cd your-project
grite init # 에이전트 발견 가능성을 위한 AGENTS.md 생성
...
에이전트 경로(agent path)에서 설계의 진가가 드러납니다. 에이전트가 락(lock)을 점유하면 두 번째 에이전트는 다른 작업을 선택하게 되며, 학습한 내용을 다음 세션에서 읽을 수 있는 영구적인 메모리 이슈(durable memory issue)로 기록합니다.
# 에이전트 부팅, 동기화 및 작업 가져오기
grite sync --pull
grite issue list --label "agent:todo" --json
...
모든 명령은 --json을 지원하므로, 에이전트는 출력을 스크래핑(scraping)하는 대신 파싱(parse)할 수 있습니다. 분산 락(distributed lock)에는 TTL 임대(TTL lease)가 있어, 에이전트가 충돌하여 종료되더라도 리소스를 영원히 점유하지 않습니다. 컨텍스트 추출(Context extraction)은 10개 언어(Rust, Python, TypeScript, JavaScript, Go, Java, C, C++, Ruby, Elixir)에 대해 tree-sitter를 기반으로 작동하므로, 에이전트는 모호한 메모 대신 실제 심볼 컨텍스트(symbol context)가 포함된 이슈를 생성할 수 있습니다.
적합하지 않은 경우
정직함이 핵심이기에, grite가 적절한 도구가 아닌 경우를 말씀드리겠습니다.
이 도구는 터미널 및 git 네이티브(terminal-and-git native)입니다. 호스팅된 웹 UI, 칸반 보드(kanban board), @멘션 알림, 이메일 요약 기능은 없습니다. 만약 팀이 대시보드가 있는 브라우저 기반 트래커를 사용하거나 비기술적 이해관계자(non-technical stakeholders)와 함께 일한다면, grite는 원시적으로 느껴질 것입니다. 이 도구는 이미 터미널에서 생활하는 사람들과 에이전트를 위해 구축되었습니다.
설계상 리포지토리 로컬(repo-local) 방식입니다. 리포지토리 간 이슈(Cross-repo issues), 수십 개의 서비스에 걸친 조직 전체의 백로그(org-wide backlog), 또는 외부 사용자가 보고를 제출하는 공개 버그 트래커(public bug tracker) 등은 grite의 용도가 아닙니다. 이슈는 해당 이슈가 설명하는 리포지토리 범위 내로 제한됩니다.
CRDT 머지(merge)는 결정론적(deterministic)이지만, 지능적이지는 않습니다. 마지막 쓰기 승리(Last-Write-Wins) 방식은 두 에이전트가 충돌하는 제목을 설정할 경우 타임스탬프에 따라 하나가 승리함을 의미합니다. 이는 정확하며 충돌이 발생하지 않지만, 인간의 판단은 아닙니다. 두 편집 내용을 인간이 실제로 조정(reconcile)하기를 원하는 필드의 경우, 자동 머지는 원하는 방식이 아닐 수 있습니다.
또한 git 2.38+ 버전이 필요합니다. 데몬(daemon)은 선택 사항이지만, git 의존성은 필수입니다.
요점 (Takeaways)
- 병렬 에이전트(parallel agents)를 위한 조정 상태(Coordination state)는 서버가 아닌 코드 옆에 위치해야 합니다. Git refs를 사용하면 동기화(sync), 이력(history), 오프라인(offline) 기능을 무료로 얻을 수 있습니다.
- 추가 전용(append-only) 이벤트 로그와 재구축 가능한 캐시(rebuildable cache)의 결합은 깔끔한 분리입니다. 로그는 진실(truth)이며, sled 뷰는 일회성 속도(disposable speed)를 제공합니다.
- CRDT Last-Write-Wins(LWW)는 "두 에이전트가 하나의 이슈를 편집하는 상황"을 머지 충돌(merge conflict)이 아닌 아무 일도 일어나지 않은 상태로 만듭니다. 이는 결정론적(deterministic)이며, 이는 재현성(reproducibility)을 위해 정확히 원하는 특성입니다. 또한 단순(dumb)하며, 이는 여전히 인간이 개입하기를 원하는 지점과 정확히 일치합니다.
코드, 데이터 모델, 그리고 해싱 테스트 벡터(hashing test vectors)는 여기에서 확인할 수 있습니다:
https://github.com/neul-labs/grite
만약 단일 저장소(repo)에 대해 하나 이상의 코딩 에이전트를 실행하고 있다면, 현재 어떻게 이들을 조정하고 있는지 알고 싶습니다. 직접 테스트해 보시고(Kick the tyres), 이슈(issues) 제기는 언제든 환영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기