개발 과정 · AI 에이전트
하네스를 실전에 적용하다: 병렬 에이전트 툴킷의 git 격리를 두 번의 적대적 리뷰로 다듬은 기록
'Agent = Model + Harness' 이론을 실제 Claude Code 툴킷에 적용한 케이스 스터디. 병렬 작업의 레이스 컨디션을 잡고, 두 번의 적대적 리뷰가 1차에서 놓친 검증 우회를 어떻게 찾아냈는지 — 하네스 엔지니어링의 실전 교훈.
하네스 엔지니어링의 원리는 글로 읽으면 명료하다. 그러나 진짜 시험은 그 원리를 실제로 돌아가는 도구 위에 얹었을 때 시작된다. 이 글은 하네스/루프 엔지니어링 지형도에서 정리한 개념들을 우리 프로젝트(claude-code-kit)에 실제로 적용하며 겪은 기록이다 — 그리고 두 번의 적대적 리뷰가 어떻게 우리가 “고쳤다고 믿은” 결함이 실제로는 남아 있었음을 밝혀냈는지에 대한 이야기다.
핵심 요약
병렬 에이전트의 안전은 "격리 진입"이 아니라 "병합 복귀 프로토콜"과 공유 상태의 원자성에서 갈린다.
이론이 말한 "worktree 격리 + 병합"은 필요조건일 뿐, 격리만으로는 충돌이 사라지지 않고 병합 시점으로 이연될 뿐이다.
그리고 가장 값진 교훈 — "작성자 에이전트는 오염돼 있다." 자기 검증은 자기가 닫았다고 믿은 구멍을 놓친다. 세션을 분리한 독립 리뷰만이 그것을 잡는다.
1. 왜 ‘병렬’이 하네스의 급소인가
이 툴킷은 여러 전문 에이전트를 병렬로 디스패치해 작업 속도를 끌어올린다. 각 에이전트가 파일을 동시에 수정하므로, 서로의 작업이 뒤섞이지 않도록 git worktree로 격리한 뒤 끝나면 다시 병합한다.
리서치가 확인한 업계의 수렴점도 정확히 이것이었다 — 병렬 에이전트 도구들은 예외 없이 worktree(또는 컨테이너) 격리 + 태스크별 브랜치 + 리뷰 후 병합이라는 같은 설계에 도달했다. 하지만 같은 리서치가 남긴 경고가 있었다.
격리는 필요조건일 뿐, 충분조건이 아니다. 런타임 격리·충돌 예측·통합 리뷰가 함께 있어야 한다.
우리 툴킷을 이 잣대로 진단하자, 정확히 그 “충분조건"이 비어 있었다.
2. 진단: 격리 진입은 있는데, 병합 복귀가 없다
첫째, 병합 프로토콜의 부재. 에이전트를 worktree로 “격리 진입"시키는 정책은 있었지만, 검증을 통과한 뒤 언제·어떻게 “복귀(병합)“하는지에 대한 규범이 없었다. 격리는 충돌을 없애는 게 아니라 병합 시점으로 미루는 것인데, 그 시점의 규칙이 없으면 미뤄둔 충돌이 한꺼번에 터진다.
둘째, 락 없는 공유 상태의 레이스 컨디션 3건. 병렬 세션이 같은 파일을 락 없이 읽고-고치고-쓰는 지점들이었다.
| 공유 상태 | 문제 | 결과 |
|---|---|---|
Stop 훅 재시도 카운터(/tmp 공유) | 세션 스코프 없이 repo 단위 공유 | 병렬 세션이 서로의 카운터를 덮어씀 |
| 피드백 원장(feedback ledger) | 병렬 리뷰 2개가 동시에 락 없이 기록 | lost-update — 한쪽 기록 소실 |
Work ID 채번(work.sh) | 번호 계산과 디렉토리 생성이 비원자적(TOCTOU) | 동시 생성 시 같은 ID 중복 발급 |
문제의 성격은 명확했다. “더 똑똑한 에이전트"로 풀 문제가 아니라, 하네스의 구조적 결함 — 즉 하네스 엔지니어링으로 봉인해야 할 실수였다.
3. 1차 구현과 1차 적대적 리뷰
수정의 뼈대는 리서치가 정리한 두 축, feedforward와 feedback을 그대로 따랐다.
- feedforward(사전 조향): 병합 복귀 규범을 명문화한 규칙 파일을 새로 만들어 세션 시작 시 에이전트에게 주입 — 검증 통과 후 복귀, 파일 소유권 분리, 충돌 시 전용 에이전트로 에스컬레이션.
- feedback(사후 관찰): 레이스 3건에 각각 세션 스코프 격리, 파일 락, 원자적 ID 선점을 적용하고 회귀 테스트로 봉인.
그리고 검증 원칙 중 하나 — “작성자 에이전트는 오염돼 있다” — 를 따라, 코드를 작성한 세션이 아니라 분리된 세션의 적대적 리뷰어에게 검증을 맡겼다. 1차 리뷰는 곧바로 값을 했다. 심각도 High 두 건.
- 세션 스코프 적용 순서 버그 — 카운터를 세션별로 격리하는 코드가 리셋 경로보다 늦게 실행돼, 정작 핵심 수정이 무력화되고 있었다. 고치려던 바로 그 결함을 새로 만든 셈이었다.
- 예측 가능한 임시 파일 경로 — 공유 호스트에서 심볼릭 링크를 심어 임의 파일을 덮어쓸 수 있는 취약점(CWE-59).
열 건의 지적을 모두 수정하고 재리뷰에서 ACCEPT를 얻었다. 여기서 멈췄다면, 겉보기엔 완결된 작업이었다.
4. 2차 리뷰가 밝혀낸 것: ‘고쳤다고 믿은’ 우회
멈추지 않고 한 번 더 — 이번엔 여러 에이전트가 서로 다른 각도에서 동시에 파고드는 멀티에이전트 리뷰를 돌렸다. 29개 에이전트가 각자 결함을 찾고, 찾은 것마다 독립 검증자가 적대적으로 재확인했다. 27개의 검증된 지적이 10개의 근본 원인으로 수렴했고, 그중 하나가 아팠다.
우리의 Stop 훅은 “파이프라인이 방금 검증을 마쳤으니 이중 검증하지 말라"는 신호로 **작업트리 상태의 지문(fingerprint)**을 남긴다. 1차 리뷰에서 이 지문이 새 파일을 놓친다는 지적을 받고 git status --porcelain을 추가해 “닫았다"고 판정했었다. 그런데 2차 리뷰가 물었다 — porcelain은 추적되지 않는(untracked) 파일의 ‘경로’만 기록하지, ‘내용’은 반영하지 않는다. 그렇다면?
이미 untracked 상태인
.py파일의 내용을 검증 이후에 수정하면, 지문은 바이트 단위로 동일하게 유지된다. 마커는 여전히 유효하다고 판정되고, Stop 훅은 검증을 통째로 건너뛴다. 검증되지 않은 깨진 코드가 안전망을 조용히 통과한다.
1차에서 “닫았다"고 선언한 바로 그 우회가, 실제로는 열려 있었다. 자기 작업을 방금 검증한 세션의 눈에는 보이지 않던 구멍이었다. 이것이 **“검증은 생성보다 쉽고, 검증자는 크지 않아도 되지만 반드시 달라야 한다”**는 원칙의 실증이다.
근본 재설계
미봉책 대신 지문의 정의를 바꿨다. git status의 출력이 아니라, 훅이 실제로 검증하는 파일 집합의 ‘내용’을 직접 해시하도록 — 지문의 대상과 검증의 대상을 구조적으로 일치시켰다. 그러자 untracked 내용 변경도 지문에 반영되어 우회가 차단됐고, 검증 대상이 아닌 파일(리뷰 결과 문서 등)의 변경은 지문에 영향을 주지 않아 마커가 불필요하게 무효화되지도 않았다.
2차 리뷰는 설계 수준의 모순도 하나 짚었다. 병합 규칙은 “메인 세션이 순차적으로 병합하라"고 적혀 있었는데, 정작 각 에이전트는 “검증이 끝나면 스스로 복귀하라"는 지시를 받고 있었다. 메인 세션에는 서브에이전트들의 복귀 타이밍을 직렬화할 수단이 없다 — 강제할 수 없는 규범이었다. 해법은 규칙을 실제로 통제 가능한 레버로 옮기는 것이었다. “순차 병합”(불가능)이 아니라 “순차 디스패치”(가능). 병렬 안전을 병합 시점이 아니라 디스패치 시점의 파일 분리로 확보하도록 다시 세웠다.
5. 교훈: 하네스는 ‘실수를 시스템으로 봉인’하는 일이다
“에이전트가 실수할 때마다, 다시는 그 실수를 못 하도록 시스템을 엔지니어링하라.” — Mitchell Hashimoto
우리가 한 모든 수정은 결국 이 형태였다. 레이스가 가능하던 자리에 락과 원자적 연산을 넣고, 규범이 없던 자리에 주입되는 규칙을 두고, 검증을 우회할 수 있던 자리에 검증 스코프와 지문을 일치시켰다. 그리고 그 과정을 봉인하는 회귀 테스트를 남겼다.
가장 값진 한 가지 — 한 번의 자기 검증은 자기가 만든 논리의 사각지대를 보지 못한다. 1차 리뷰가 ACCEPT를 준 뒤에도 멈추지 않고 다른 각도의 리뷰를 한 번 더 돌린 덕분에 배포 전에 진짜 우회를 잡았다. 검증은 “통과했다"는 주장이 아니라, 다른 눈으로 반복해서 반증을 시도한 뒤에도 살아남은 것이다.
자주 묻는 질문
Q. worktree로 격리했는데도 왜 충돌이 생기나?
worktree 격리는 각 에이전트가 작업하는 동안 파일을 분리해줄 뿐, 하나의 브랜치로 합쳐지는 순간의 규칙은 제공하지 않는다. “언제·어떤 순서로·충돌 시 누구에게 위임해 병합하는가"라는 복귀 프로토콜이 없으면 미뤄둔 충돌이 병합 때 한꺼번에 터진다.
Q. 리뷰를 한 번 받았는데 왜 또 받나?
코드를 작성한(혹은 방금 1차로 검증한) 주체는 자신이 세운 전제를 재확인할 뿐, 그 전제 자체를 의심하지 않는다. 실제로 1차 리뷰가 “닫았다"고 판정한 검증 우회가 2차 리뷰에서 여전히 열려 있었다. 검증자는 반드시 다른 각도·다른 세션이어야 사각지대가 드러난다.
결론: 모델이 아니라 루프를 고쳤다
이번 작업에서 우리는 더 강한 모델을 기다리지 않았다. 같은 모델이 같은 실수를 반복하지 못하도록 루프를 설계하고, 검증하고, 정교화했을 뿐이다. 레이스는 락으로, 규범의 공백은 주입되는 규칙으로, 검증 우회는 스코프 일치로 봉인했다 — 그리고 그 전 과정을 두 번의 적대적 리뷰로 다른 눈에 통과시켰다.
모델은 평준화되고, 하네스는 차별화된다. 그리고 그 하네스의 품질은, 자기 자신조차 의심하는 검증 루프를 얼마나 정직하게 돌리느냐로 결정된다.
참고 자료
- 하네스 & 루프 엔지니어링 — 2026 중반 지형도 — 이 글의 개념적 토대
- Effective harnesses for long-running agents — Anthropic Engineering
- Harness engineering for coding agent users — Birgitta Böckeler (feedforward/feedback 프레임)
- My AI Adoption Journey — Mitchell Hashimoto