PetV3 에이전트 공장 2: 병렬 작업보다 충돌 없는 착지가 먼저였다
PetV3 자동화 시스템 시리즈 2/4
앞 글에서 PetV3 자동화의 목표를 “에이전트를 많이 돌리는 것”이 아니라 “틀린 성공을 만들지 않는 것”이라고 정의했다. 그 목표를 Git 위에서 구현한 핵심은 세 가지다.
격리된 worktree → 겹침을 보이는 ledger → 중앙 land gate
워커는 각자 자기 작업장을 가진다
공유 working tree에서 여러 에이전트가 일하면 가장 먼저 생기는 유혹은 stash다. 하지만 stash는 누가 만든 것인지 모르는 변경까지 시야에서 치운다. PetV3에서도 혼합 main을 보호하려고 broad stash를 쓰려다가, 사용자 작업을 함께 옮길 수 있다는 이유로 중단했다.
대신 워커마다 branch와 worktree를 하나씩 준다.
work/command-split /var/tmp/petv3-w-cmd
work/reply-p0 /var/tmp/petv3-w-p0
work/dirty-guard /var/tmp/petv3-w-dirty
워커는 자기 브랜치에서는 커밋할 수 있지만 main에는 올리지 않는다. 기다려야 끝나는 작업도 배분하지 않는다. 다른 작업의 결과가 필요하다면 애초에 닫힌 작업 단위가 아니라는 뜻이다.
이 구조 덕분에 “코드를 잘 짰는가”와 “병합 순서가 맞는가”를 분리할 수 있었다.
ledger는 잠금이 아니라 레이더다
작업을 시작하기 전에 워커는 수정할 파일을 신고한다.
scripts/ledger.sh claim work/reply-p0 \
crates/petv3-native/src/connection.rs \
crates/petv3-overlay/src/reply.rs
중요한 점은 ledger가 두 번째 워커를 거부하지 않는다는 것이다. 같은 파일을 잡은 워커가 둘이라면 그 사실을 기록하고 경고한다. 잠금으로 막으면 두 번째 워커는 신고를 포기하고, 관제자는 오히려 충돌을 볼 수 없게 된다.
즉 ledger의 질문은 “누가 이 파일을 소유하는가?”가 아니다.
“지금 어느 의도가 같은 파일에서 만나고 있는가?”
텍스트 충돌이 발생하면 자동으로 한쪽을 선택하지 않는다. 중앙 관제자가 양쪽 변경의 의도를 읽고 중재한다.
착지는 하나의 트랜잭션이다
PetV3의 land.sh는 main으로 가는 유일한 통로다. 대략 다음 순서를 강제한다.
- 워커 브랜치 끝에서 대상 게이트 실행
- 시작 main HEAD와 clean 상태 재확인
- 충돌 없는 merge
- 병합 결과에서 같은 게이트 재실행
- main HEAD가 단독으로 링크되는지 확인
마지막 관문은 실제 사고에서 나왔다. 한 번은 좁은 패키지 게이트를 통과했지만, 다른 crate가 새 enum variant를 처리하지 못해 전체 HEAD가 빌드되지 않았다. 그래서 일부러 같은 결함을 넣은 시험 브랜치를 만들어 확인했다. branch gate와 merge gate는 통과했지만 HEAD 단독 링크가 실패했고, 스크립트가 안전하게 시작점으로 되돌렸다.
관문이 존재한다는 사실보다, 관문을 일부러 깨뜨려 실제로 잡는지 확인한 것이 더 중요했다.
빌드 캐시도 작업 범위다
격리는 소스 디렉터리만 나눈다고 끝나지 않았다. 서로 다른 archive가 같은 Cargo target을 공유하자 이전 바이너리에 박힌 CARGO_MANIFEST_DIR 때문에 다음 게이트가 이미 삭제된 경로를 읽는 일이 생겼다.
현재 착지 한 번은 다음을 함께 격리한다.
- branch source archive
- merged source archive
- branch/merged Cargo target
- rustc용
TMPDIR - 단계별 로그와 실패 진단 디렉터리
성공하면 자기 실행 루트만 지운다. 실패하면 진단을 남기며, 보존 실패가 상한에 도달하면 자동 삭제 대신 새 착지를 거부한다.
기록 파일도 충돌하도록 만들지 않는다
처음에는 history 색인에 워커가 줄을 하나씩 추가했다. 항목 파일은 분리됐지만 색인 한 줄에서 다시 충돌했다. 순번도 각자 “다음 번호”를 골라 여러 번 겹쳤다.
그래서 history는 YYYY-MM-DD-slug.md 한 파일에 한 사건만 기록하고, 색인은 스크립트가 생성한다. 착지 커밋도 사람이 복사하지 않고 Git에서 계산한다.
이 작은 규칙은 문서도 코드와 같은 병렬성 문제를 가진다는 사실에서 나왔다.
두 번째 결론
멀티 에이전트의 처리량은 워커 수가 아니라 안전하게 착지한 닫힌 작업 수로 재야 한다. 빠른 워커 열 개보다, 검증한 snapshot이 그대로 main에 도착하는 파이프라인 하나가 먼저다.
다음 편에서는 이 작업장을 사람이 한눈에 읽고 조절하도록 만든 터미널 대시보드를 다룬다.