Claude Code 서브에이전트(Subagents)를 활용한 병렬 리팩터링: 실전 워크플로우
요약
Claude Code의 서브에이전트(Subagents)를 활용하여 대규모 리팩터링 작업을 병렬로 처리하는 실전 워크플로우를 소개합니다. 단일 에이전트의 순차적 처리 한계를 극복하기 위해 작업 영역을 분할하고 충돌을 방지하는 전략을 다룹니다.
핵심 포인트
- 서브에이전트를 통한 병렬 처리는 컨텍스트 구축 병목 현상을 해결함
- 작업 영역이 겹치지 않도록 디렉터리나 관심사별로 작업을 분할해야 함
- 중앙 타입 변경 등 '핫 파일'을 공유하는 작업은 병렬화에 부적합함
- 데이터 유실을 막기 위해 작업 할당 전 충돌 확인(conflict check) 필수
40개의 파일로 구성된 모듈을 단일 에이전트(single agent)가 리팩터링하는 방식은 여러분이 예상하는 대로 작동합니다. 즉, 첫 번째 파일을 읽고, 수정하고, 두 번째 파일을 읽고, 수정하는 식으로 직선적으로 진행됩니다. 여기서 병목 현상은 순차적인 컨텍스트 구축(context-building)입니다. 에이전트가 건드리는 모든 파일은 한 번에 하나씩, 하나의 컨텍스트 창(context window)을 거쳐야 합니다. 각 파일을 읽고, 추론하고, 수정하는 데 몇 분씩 걸린다면, 광범위한 이름 변경(rename)이나 인터페이스 변경은 여러분이 계속 지켜봐야 하는 길고 선형적인 과정이 되어버립니다.
서브에이전트(Subagents)는 이러한 작업의 형태를 바꿉니다. 하나의 에이전트가 트리를 따라 걷는 대신, 여러 개의 에이전트를 파견하여 각각 트리의 일부분을 담당하게 하고, 각자 자신만의 컨텍스트 창을 갖게 합니다. 오케스트레이팅 에이전트(orchestrating agent)가 계획을 보유하고, 서브에이전트들이 수정을 수행합니다. 작업 영역이 겹치지 않는다면, 이들은 동시에 실행됩니다. 이는 단순히 원시적인 속도의 문제가 아니라, 작업이 진정으로 독립적인 경우에 적용되는 병렬성(parallelism)에 관한 것이며, 이 차이가 핵심입니다.
병렬성이 실제로 도움이 되는 경우
모든 리팩터링이 깔끔하게 분할되는 것은 아닙니다. 결정적인 질문은 여러분의 작업 영역(slices)이 상태(state)를 공유하는지 여부입니다. 동일한 파일을 수정하는 두 개의 서브에이전트는 서로의 수정을 덮어씌울 것입니다. 왜냐하면 각 에이전트가 상대방이 파일을 쓰기 전에 파일을 읽었기 때문입니다. 오케스트레이터(orchestrator)는 auth.ts의 서로 다른 두 버전을 화해시킬 수 없으며, 둘 중 하나가 조용히 승리하게 됩니다.
따라서 워크플로우는 파견(dispatching)이 아니라 분할(partitioning)에서 시작됩니다. 병렬 작업 영역으로 적합한 후보는 다음과 같습니다:
- 디렉터리별.
src/components/,src/lib/,src/pages/는 파일을 공유하는 경우가 드뭅니다. 최상위 폴더당 하나의 서브에이전트를 배정하는 것이 안전한 기본값입니다. - 별개의 파일로 매핑되는 관심사별. "모든 테스트 파일을 업데이트하라"와 "소스 파일을 업데이트하라"는 서로 분리된 집합을 다룹니다.
- 기계적인 패턴별. 코드베이스 전체에서 임포트(import)를 변경하는 작업처럼 모든 수정 사항이 동일한 형태를 띠는 경우, 파일당 추론(reasoning)이 얕기 때문에 병렬화가 잘 이루어집니다.
부적절한 후보들은 하나의 '핫 파일(hot file)'을 공유합니다. 모든 모듈이 임포트(import)하는 중앙 타입(central type)을 변경하면, 모든 서브에이전트(subagent)가 해당 타입을 읽고 추론(reasoning)하려 할 것이며, 그중 여러 개는 해당 타입을 정의하는 파일을 수정하려 할 수도 있습니다. 이는 병렬화의 탈을 쓴 순차적 작업(sequential work)에 불과합니다.
작업을 할당하기 전에 빠른 충돌 확인(conflict check)을 수행하세요. 각 슬라이스(slice)가 건드릴 파일 목록을 나열하고, 두 개 이상의 슬라이스에 나타나는 파일이 있는지 확인하십시오. 만약 있다면, 작업을 분산(fanning out)하기 전에 오케스트레이터(orchestrator)에서 직접 해당 파일을 먼저 추출하여 수정하십시오. 서브에이전트들은 서로 진행 중인 수정 사항(in-flight edits)을 볼 수 없으므로, 중첩(overlap)이 발생하면 이는 '그럴 수도 있는' 문제가 아니라 '확실한' 데이터 유실(lost write)로 이어집니다.
우리가 실행하는 5단계 루프
우리는 오케스트레이터(orchestrator)가 통제권을 유지하고 서브에이전트(subagent)의 범위를 좁게 유지하는 루프를 정착시켰습니다. 범위를 좁게 설정한 서브에이전트를 사용하는 이유는, 작업이 완료되어 돌아왔을 때 실제로 검증할 수 있는 작은 규모의 잘 정의된 작업(well-scoped task)을 만들기 위함입니다.
1. 메인 에이전트(main agent)에서 먼저 조사하십시오. 코드를 건드리기 전에 오케스트레이터가 변경 사항을 매핑하도록 하십시오. 어떤 파일이 일치하는지, 의존성 엣지(dependency edges)가 어떤 모습인지, 공유 타입(shared types)이 어디에 있는지 파악해야 합니다. 이 조사가 바로 파티셔닝(partitioning)의 기준이 됩니다. 이 과정을 건너뛰면 작업 슬라이스가 중첩되는 결과로 이어집니다.
2. 파티셔닝 내용을 기록하십시오. 슬라이스 이름, 파일, 정확한 지침을 포함한 명시적인 목록을 생성하십시오. 두 개의 슬라이스가 모두 원하는 파일이 있다면, 나중이 아니라 지금 즉시 해결해야 할 플래그(flag)로 취급하십시오. 이 목록은 작업이 완료되어 돌아왔을 때 검토 체크리스트(review checklist) 역할도 합니다.
3. 오케스트레이터에서 공유 파일을 수정하십시오. 타입 정의(type definition), 설정(config), 모든 슬라이스가 임포트하는 배럴 익스포트(barrel export)와 같이 중앙에 위치한 모든 것은 작업을 분산하기 전에 메인 에이전트에 의해 단 한 번만 수정되어야 합니다. 이렇게 하면 서브에이전트들은 이미 올바르게 수정된 공유 인터페이스(shared surface)를 읽게 되며, 각자의 파일만 수정하게 됩니다.
4. 슬라이스당 하나의 서브에이전트를 할당하십시오. 각 에이전트에게는 독립적인 지침을 전달합니다. 즉, 담당할 파일, 수행할 변경 사항, 그리고 성공 확인 조건("모듈 타입 체크(typechecks) 통과", "이 테스트들이 통과함")을 포함해야 합니다. 실행 도중에 명확한 질문을 던져야 하는 서브에이전트가 있다면, 그것은 2단계에서 지침이 불충분하게 정의되었음을 의미합니다.
5. 한 곳에서 병합 및 검증하십시오. 슬라이스(slices)가 반환되면, 오케스트레이터(orchestrator)는 각 슬라이스별로 검증하는 것이 아니라 결합된 결과물에 대해 빌드와 전체 테스트 스위트(test suite)를 실행합니다. 하나의 슬라이스는 단독으로는 통과할 수 있지만, 다른 슬라이스가 변경한 통합 지점(integration point)을 깨뜨릴 수 있습니다. 유효한 유일한 검증은 병합된 트리(merged tree) 전체에 대해 수행되는 검증뿐입니다.
슬라이스 지침은 직설적이고 제한적이어야 합니다. "src/components 내에서,
variantprop을 정의하는 모든 컴포넌트의 prop 이름을tone으로 변경하고 prop type을 업데이트하십시오. src/components 외부의 파일은 건드리지 마십시오"는 좋은 슬라이스입니다. 반면 "컴포넌트를 현대화하십시오"는 좋지 않습니다. 이는 인접한 슬라이스들과 충돌하는 범위 확장(scope creep)을 유발합니다.
비용 및 실패 지점
병렬 서브에이전트(subagents)는 선형적인 단일 패스(linear pass)보다 더 많은 토큰을 소비합니다. 각 서브에이전트가 자신만의 컨텍스트(context)를 다시 구축해야 하기 때문입니다. 여러분은 토큰을 지불하는 대신 실제 소요 시간(wall-clock time)을 단축하고, 오케스트레이터의 컨텍스트를 깨끗하게 유지하는 이득을 얻습니다. 오케스트레이터는 40개의 파일을 한꺼번에 들고 있을 필요 없이, 계획(plan)과 슬라이스 경계(slice boundaries)만 유지하면 됩니다. 광범위하고 기계적인 변경 작업의 경우, 이러한 트레이드오프(trade-off)는 대개 가치가 있습니다. 하지만 깊고 상호 연결된 변경 작업의 경우에는 그렇지 않으며, 전체 의존성 체인(dependency chain)을 한 번에 파악할 수 있는 단일 에이전트를 사용하는 것이 더 낫습니다.
주의해야 할 실패 모드(failure mode)는 조용한 부분적 완료(silent partial completion)입니다. 서브에이전트가 자신의 슬라이스를 완료하고 성공을 보고하더라도, 조사(survey) 과정에서 포착하지 못한 파일—동적 임포트(dynamic import), 문자열로 빌드된 경로, 또는 분할된 디렉토리 외부의 파일—을 놓쳤을 수 있습니다. 병합된 트리 빌드는 이러한 문제의 대부분을 잡아내며, 전체 리포지토리(repo)에서 이전 심볼(symbol)을 대상으로 grep을 실행하면 나머지를 잡아낼 수 있습니다. 서브에이전트의 자기 보고(self-reports)가 아니라 검증 단계를 신뢰하십시오.
만약 터미널 기록 대신 눈앞에 차이점(diff)이 보이는 에디터에서 이 과정을 제어하고 싶다면, 에이전트 인식 IDE(agent-aware IDE)를 사용하면 병합 및 검토(merge-and-review) 단계가 덜 추상적으로 느껴질 것입니다. 각 슬라이스가 검토 가능한 변경 세트(change set)로 반영되는 것을 직접 확인할 수 있습니다.
솔직한 요약은 다음과 같습니다: 서브에이전트(subagents)는 속도를 높여주는 버튼이 아니라, 작업을 분할하는 도구입니다. 가치는 얼마나 많은 에이전트를 실행하느냐가 아니라, 작업을 얼마나 깔끔하게 나누느냐에서 나옵니다. 서로 겹치지 않는 슬라이스 경계(slice boundaries)를 설정하는 데 초기에 공을 들이고, 공유되는 접점(shared surface)은 직접 편집하며, 전체를 한 번에 검증하는 데 노력을 기울이십시오. 그렇게 한다면 병렬 리팩터링(parallel refactoring)은 차분하게 진행될 것입니다. 분할 과정을 생략한다면, 당신은 수동으로 경합 조건(race condition)을 실행하고 있는 셈이 될 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기