오케스트라처럼 에이전트 지휘하기: agents-concerto의 작동 방식
요약
코딩 에이전트의 무분별한 코드 변경과 신뢰할 수 없는 테스트 문제를 해결하기 위해 설계된 멀티 에이전트 오케스트레이터 'agents-concerto'를 소개합니다. 이 시스템은 지휘자와 연주자 역할을 분리하여 인간의 검토를 거친 고품질의 오픈 PR을 생성하는 데 집중합니다.
핵심 포인트
- 지휘자와 연주자 역할을 분리하여 에이전트 간 상호 검증 체계 구축
- 설정 기반(Config-driven) 및 기술 스택에 무관한 파이프라인 설계
- 작성자와 승인자를 분리하는 양자 권한 체계 적용
- Git 워크트리 격리를 통해 사용자 환경 보호
- Given-When-Then 형식의 테스트 사양을 통한 명확한 수락 기준 정의
몇 달 동안 저는 코딩 에이전트(coding agents)와 동일한 문제로 골머리를 앓았습니다. 세션이 끝나면 수많은 변경 사항이 쏟아져 나오는데, 검토할 때가 되면 무엇을 왜 건드렸는지 더 이상 알 수 없게 되는 것이었습니다.
에이전트는 작성하고, 에이전트는 검증하며, 에이전트는 스스로를 축하합니다. 인간은 눈을 감은 채 서명할 뿐입니다.
그리고 에이전트가 테스트를 작성했을 때는 상황이 더 악화되었습니다. 아무것도 증명하지 못하는 '통과(green)'된 테스트들 말입니다. 테스트 대상인 바로 그 코드의 모의 객체(Mocks), 프라이빗 필드(private fields)에 대한 단언(assertions), 부수적인 구조의 스냅샷(snapshots) 같은 것들 말이죠.
테스트 스위트(Suite)는 통과했지만, 신뢰도는 제로였습니다.
그래서 저는 Claude Code 상에서 동작하는 멀티 에이전트 오케스트레이터(multi-agent orchestrator)인 agents-concerto를 구축했습니다. 이름은 문자 그대로입니다. **지휘자(conductor)**가 있고 **연주자(players)**가 있습니다. 지휘자는 절대 악기를 만지지 않습니다. 그리고 머지(merge)는 여전히 인간의 몫입니다.
이 저장소(repo)는 애플리케이션이 아닙니다. 이것은 오케스트레이션 브레인(orchestration brain)입니다. 하나의 CLAUDE.md, 마크다운(Markdown) 형식의 네 가지 에이전트, 그리고 네 개의 Bash 스크립트로 구성됩니다. 이 프로젝트의 결과물은 **인간의 검토를 받을 준비가 된 오픈 PR(open PRs)**입니다.
굽히지 않는 7가지 규칙
- 조건문이 아닌 설정 기반(Config-driven). 대상 저장소의 수는
config.md에 있는 리스트의 길이와 같습니다. 저장소가 하나든 열다섯 개든 동일한 코드 경로를 따릅니다. - 기술이 아닌 파이프라인 기능에 따른 에이전트. 하나의 범용 구현체(implementer)가 어떤 스택이든 처리합니다. 결코
backend-dev/frontend-dev로 나누지 않습니다. - 양자 권한 체계(Two-party authority). 코드를 작성하는 에이전트는 승인(approve)하거나 머지(merge)할 수 없습니다.
- 워크트리 격리(Worktree isolation). 코드를 건드리는 모든 에이전트는 자신만의 git 워크트리(worktree)에서 실행되며, 사용자의 체크아웃(checkout) 상태를 절대 건드리지 않습니다.
- 역할이 아닌 복잡도에 따른 모델 선정. 구현체는 하나입니다. 모델은 호출될 때마다 복잡도 계층(complexity tier)에 따라 선택됩니다.
- "PR 준비 완료"에서 멈춤. 자동 머지(auto-merge)는 없습니다. 머지 권한은 요청조차 되지 않습니다.
- 탈출구가 있는 사이클 제한. 검토(review)→수정(fix) 사이클에 유한한 제한을 두며, 제한에 도달하면 인간이 개입하여 종료합니다.
규칙 3번은 몸소 체감하게 되는 규칙입니다. 작성자가 승인자가 아닐 때, 그 판결은 비로소 의미를 갖습니다.
수락 기준(Acceptance criterion)이 작업의 단위다
이 부분이 시스템을 가장 많이 변화시킨 부분입니다: 기준(criterion)은 인간을 위한 메모가 아니라, 테스트 사양(test spec)입니다.
Shaping(/shape, 또는 /run 내 인라인)은 위시리스트를 방출하는 것을 거부합니다. 기준(Criteria)은 주어진(Given)-했을 때(When)-그러므로(Then) 형식으로 나옵니다. 즉, given <context>, when <action>, then <observable result>이며, 이 구조는 계층적 계약(tiered contract) 안에 존재합니다. 여기서 ## Task와 ## Acceptance criteria는 필수 항목이고, ## Scope / ## Non-goals는 신호가 추가될 때만 포함되는 선택 항목입니다.
주어진-했을 때-그러므로 기준은 이미 컨텍스트(context), 트리거(trigger), 그리고 관찰 가능한 결과(observable result)를 명시합니다. 이것 자체가 테스트입니다. 게다가
모든 기준은 출력(outputs), 렌더링된 UI, 영속화된 상태(persisted state), HTTP 응답, 방출된 이벤트(emitted events)와 같은 **관찰 가능한 동작(observable behavior)**을 단언(asserting)하는 최소 하나 이상의 테스트가 됩니다. 내부 구현(internals)은 절대 다루지 않습니다. 프라이빗 필드(private fields), 내부 헬퍼(internal helpers)에 대한 모킹(mocking), 혹은 특정 함수가 호출되었는지 확인하는 작업은 포함되지 않습니다. 테스트는 공개 진입점(public entry point)으로부터 구동되며, 사용자의 관점에서 명명됩니다(it("shows an error when the email already exists")). 그리고 네트워크, 시계(clock), 제3자 서비스(third parties)와 같은 진정으로 외부적인 의존성(external dependencies)만이 모킹됩니다. 테스트 대상이 되는 코드 자체는 절대 모킹하지 않습니다.
이 모든 것은 하나의 규칙으로 압축됩니다:
만약 동작을 보존하는 리팩터링(refactor)이 테스트를 깨뜨린다면, 그 테스트가 잘못된 것이다.
이것이 바로 Tidy First와 BDD(행동 주도 개발, Behavior-Driven Development)가 함께 있어야 하는 이유입니다. 구조적 커밋(structural commit)은 정의상 동작을 변경하지 않습니다. 따라서 그 과정에서 테스트가 깨진다면, 해당 작업이 리팩터링이 아니었거나 테스트가 내부 구현에 결합(coupled)되어 있었음을 의미합니다. 모든 PR(Pull Request)은 이 두 가지를 동시에 증명합니다.
리뷰어의 게이트 (The reviewer's gates)
기준에 따른 정확성, 테스트 통과(green), Tidy First 규율(혼합된 커밋은 NEEDS_FIXES로 명명됨), 그리고 BDD 게이트를 확인합니다. 즉, 테스트가 없는 기준이 있거나 내부 구현을 단언하는 테스트가 있다면 PR은 차단됩니다.
마지막 게이트에는 제가 가장 중요한 결정이라고 생각하는 수식어가 붙습니다: 이는 디프(diff)에서 추가되거나 수정된 테스트에만 적용됩니다. PR이 건드리지 않은 기존 테스트는 심사 대상이 아닙니다. 이 규칙이 없다면, 단 세 줄의 변경사항이 전체 테스트 스위트를 다시 작성하라는 요구와 함께 차단될 수 있으며, 구현자는 기존 테스트를 수정하려다 작업 범위가 확장(scope creep)되는 상황을 겪게 됩니다. 이 표준은 앞으로 나아가는 방향에 적용됩니다. 오래된 기술 부채는 실제로 해당 코드를 건드릴 때 갚아나가는 것입니다.
최종 판결은 path:line을 인용하는 통합된 코멘트와 구조화된 출력으로 제공됩니다. 그리고 CLEAN은 "승인됨(approved)"이 아니라 **"사람이 리뷰할 준비가 됨"**을 의미합니다. 리뷰어는 공식적으로 승인하지 않습니다. 그럴 수 없습니다.
흐름 (The flow)
Task ──► /run ──► Step 0: 내용이 모호하다면 인라인으로 구체화
▼
Step 1 설정 로드 + 실행 로그 열기 (RUN_ID)
...
서브 태스크는 플랜 순서대로 실행되지 않습니다. **웨이브(wave)**란 현재 차단되지 않은 모든 것을 의미하며, 웨이브는 병렬로 실행되고 각 서브 태스크는 자체 작업 트리(worktree)에서 작동합니다.
자동 병합되는 것이 없기 때문에 충돌 워커(conflict worker)가 필요하지 않으며, 그러한 워커도 필요 없습니다. 두 브랜치가 같은 파일을 건드려도 병합 시점에만 충돌이 발생하며, 이때 사람이 순서를 결정합니다.
여기서는 두 개의 설정 필드가 의도적으로 직교(orthogonal)하게 유지됩니다: task_source는 태스크가 읽혀지는 곳(none, github, gitlab, linear, jira)이며, 각 레포지토리 자체의 host는 PR이 개설되는 곳입니다. 따라서 단일 실행으로 Linear에서 읽고 동시에 GitHub와 GitLab에 PR을 개설할 수 있습니다.
경계 설정 (Boundaries, enforced)
불변성(invariants)은 프롬프트 안에만 존재하는 것이 아닙니다. .claude/settings.json 파일이 도구 수준에서 이를 거부합니다:
gh pr merge ← 절대 병합 금지
gh pr review ← --approve를 끌고 감
git merge ← 로컬에서 절대 병합 금지
...
거부된 명령어는 우회할 장애물이 아니라 의도적인 경계입니다. 이 중 하나에 도달한 에이전트는 서브 태스크를 ready-for-human으로 격상시키고 실행은 계속됩니다.
알아두면 좋은 점: 에이전트 팀을 운영하는 비용은 단일 Claude Code 세션의 4~6배 정도입니다. 모든 실행은 해당 알림과 함께 PR, 사이클, 그리고 에스컬레이션 횟수를 요약하여 기록합니다.
사용해 보기 (Trying it)
claude plugin marketplace add moruno21/agents-concerto
claude plugin install agents-concerto@moruno-plugins
/agents-concerto:setup
/agents-concerto:run <태스크 설명>
이것을 만들면서 배운 것 (What building it taught me)
가장 큰 가치는 코드를 작성하는 에이전트 자체에 있는 것이 아닙니다. 그것은 그 주변의 경계에 있습니다. 작업 트리(worktree). 쓰기 접근 권한이 없는 리뷰어. 사이클 제한(cycle cap). 거부 규칙(deny rules).
그리고 테스트와 함께라면, 그 경계는 단순한 권한이 아니라 **진입점(entry point)**입니다.
에이전트들이 연주합니다. 인간이 지휘봉을 잡습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기