Agent Mentor Learn
루프에서 그래프로: 에이전트 시스템을 위한 오케스트레이션 엔지니어링 · 3 / 6강

레슨 3: 병렬화: 섹셔닝과 투표

학습 목표:

  • 병렬화의 두 변종인 섹셔닝과 투표를 구분하고, 각각이 무엇을 푸는지 이해하며, '출력은 프로그램적으로 집계된다'가 긋는 경계 지키기
  • Promise.all과 직접 만든 동시 실행 풀로 섹셔닝을 구현하고, 집계가 페이로드가 아니라 참조를 넘기게 하기
  • 팬아웃의 세 가지 비용(결과가 컨텍스트를 범람시키는 것, 실제 제품의 동시 실행 상한, 투표의 N배 토큰)을 계산하고, 그것으로 어떤 제안을 병렬화할지 판단하기

전제: 레슨 1과 2를 마쳤고, 레슨 2의 runAgent() 래퍼를 갖고 있어야 합니다 | 이전: << 레슨 2: 이어 붙이고, 갈라 보내기: 체이닝과 라우팅 | 다음: 레슨 4 >>

문서 12개, 하나의 체인, 한 시간의 대기열

레슨 2의 체인은 이제 돌아갑니다. 개요 → 게이트 → 초안 → 게이트 → 용어 점검. 프롬프트를 리뷰용으로 갈아 끼우면(핵심 짚기, 수정 제안, 용어 검증) 체인의 형태는 바뀌지 않습니다. 문서 하나에 돌려 보면 6~7분입니다.

그러다 제품팀이 디렉터리 하나를 책상에 올려놓습니다. 문서 12개, 각각 같은 리뷰를 거쳐야 합니다.

for 루프를 쓰고, 시작시키고, 커피를 내리러 갑니다. 한 시간 뒤에 돌아옵니다. 로그는 문서 9에서 멈춰 있습니다. 문서 10이 핵심을 짚고 있는 중입니다.

그 한 시간 동안 기계는 대부분의 시간을 기다리는 데 썼습니다. 문서 2의 요청을 보내기 전에 문서 1의 API 응답을 기다리고, 문서 3의 차례가 오기 전에 문서 2가 네 단계를 다 끝내기를 기다립니다. 실용적인 질문을 하나 던져 보세요. 문서 3의 리뷰 결론이 문서 2 결과의 단어 하나에라도 의존합니까?

아닙니다. 서로 독립적인 문서 12개입니다. 그 리뷰 보고서들은 누가 먼저 끝나든 상관하지 않습니다. 체이닝에서는 기다림에 이유가 있었습니다. 다음 단계의 입력이 앞 단계의 출력이니까요. 여기에는 그런 이유가 없습니다. 이 12번의 실행이 줄을 선 것은 오직 for 루프가 줄을 세웠기 때문입니다.

이 시리즈의 코스 6은 팬아웃-집계 협업 패턴과 다관점 투표가 어떻게 작동하는지를 이미 다뤘습니다1. 이 레슨은 그것을 코드로 바꾸고 셈을 맞춥니다. 팬아웃은 공짜가 아닙니다. 속도도 진짜이고, 비용도 진짜입니다.

정의: 동시에 돌릴 수 있고, 출력은 프로그램적으로 집계된다

원문의 표현에서 시작합시다. LLM은 때때로 하나의 작업을 동시에 수행하고 그 출력을 프로그램적으로 집계할 수 있습니다. 이 워크플로가 병렬화이며, 두 가지 핵심 변종이 있습니다1:

  • 섹셔닝: 작업을 병렬로 실행되는 독립적인 하위 작업으로 쪼개는 것1. 문서 12개를 리뷰하는 것이 섹셔닝입니다.
  • 투표: 다양한 출력을 얻기 위해 같은 작업을 여러 번 실행하는 것1. 세 관점이 각각 같은 카피를 평가하게 하는 것이 투표입니다.

쓸 때: 쪼개진 하위 작업을 속도를 위해 병렬화할 수 있을 때, 또는 더 높은 확신도의 결과를 위해 여러 관점이나 시도가 필요할 때1. 건너뛰기 쉽지만 값어치 있는 덧붙임이 하나 있습니다 — 고려 사항이 여럿인 복잡한 작업에서는 각 고려 사항을 별개의 LLM 호출이 다루어 개별 측면에 주의를 집중하게 할 때 LLM이 일반적으로 더 나은 성능을 냅니다1. 옮겨 말하면, 섹셔닝과 투표는 단순한 시간 절약이 아닙니다. '법무, 보안, 브랜드'를 하나의 프롬프트에 욱여넣는 것과 세 번의 호출이 각각 하나씩 보는 것은 품질이 다릅니다.

못 박아 둘 반 문장이 하나 더 있습니다. 출력은 프로그램적으로 집계된다. 팬아웃된 결과가 돌아온 뒤 판정하고 걸러 내고 요약하는 것은 여러분의 코드이지, 12개 보고서를 전부 읽고 요약을 쓰는 또 한 번의 모델 호출이 아닙니다. 모델이 집계하게 하는 것은 다른 패턴입니다. 레슨 4의 오케스트레이터가 정확히 그 일을 합니다. 여기서 선을 분명히 그어 둡시다. 현행 Claude 플랫폼 문서는 병렬화를 멀티 에이전트 오케스트레이션 아래에 열거합니다. 독립적인 하위 작업을 동시에 팬아웃하고(여러 출처 검색, 별개의 파일 분석) 코디네이터가 결과를 종합하게 하는 것2 — 그 버전에서는 집계를 코디네이터가 하는 반면, 이 레슨은 프로그램적 집계 버전을 씁니다1. 같은 단어라도 누가 집계하는가는 서로 다른 두 가지입니다.

섹셔닝: for 루프를 Promise.all로 갈아 끼우기

직렬 버전은 이렇게 생겼고, 총 시간은 12개의 합입니다:

섹셔닝 버전은 한 줄을 바꾸고, 총 시간은 가장 느린 하나에 가까워집니다:

runAgent()는 여전히 레슨 2와 같은 래퍼 자리에 있습니다. 완전한 하네스 루프 하나이고, 내부에서 stop_reason으로 분기합니다. 이 레슨의 스텁 응답은 모두 한 턴에 끝나므로(end_turn), 마지막 스크립트의 버전은 단순화를 위해 도구 가지를 생략합니다. 실제 client에 붙이거나 도구가 필요할 때는 레슨 2의 도구 디스패치 버전을 그대로 가져오면 됩니다. 병렬화는 이 루프 자체의 어느 한 줄도 바꾸지 않습니다. 그저 이 루프들이 줄을 서지 않게 할 뿐입니다.

집계는 다음 줄에서 일어나며, 이 코드가 합니다:

이 세 줄에는 두 번째 모델 호출이 없습니다. filter, reduce, 임계값 점검 — 모두 결정론적 코드입니다. 같은 12개 보고서가 들어가면 매번 같은 한 줄 결론이 나옵니다. 집계를 코드에 두는 이득이 그것입니다. 팬아웃된 12번의 호출은 비결정론적이고, 병합 단계는 결정론적입니다. 무언가 깨졌을 때 어느 쪽을 의심할지 알 수 있습니다.

Promise.all에는 미리 알아 둘 성깔이 있습니다. 프로미스 중 하나라도 거부되면 await 전체가 거부되고, 나머지 11개가 끝났더라도 그 결과를 가져올 수 없습니다. 문서 12개를 리뷰하다 문서 7이 500을 맞으면 배치 전체가 날아갑니다 — 나머지 11개는 헛돈 것입니다. 그 비용은 합리적이지 않습니다. Promise.allSettled로 바꾸거나, 이 레슨의 연습처럼 워커마다 try/catch로 감싸 실패를 레코드로 수집하세요. 모든 팬아웃 경로는 독립적으로 실패할 수 있어야 합니다.

병렬화가 속도만의 문제가 아닌 이유

병렬화가 '같은 일을 더 일찍'뿐이라면 성능 요령이지 레슨 하나를 받을 것은 아닙니다. 진짜 이유는 컨텍스트 쪽에 있습니다.

Anthropic이 자기네 멀티 에이전트 리서치 시스템을 돌아본 글은 직설적입니다. 검색의 본질은 압축입니다. 방대한 말뭉치에서 통찰을 증류하는 것이죠. 서브에이전트는 자기 컨텍스트 윈도를 갖고 병렬로 작동하면서 질문의 서로 다른 측면을 동시에 탐색하고, 가장 중요한 토큰을 리드 리서치 에이전트를 위해 응축함으로써 압축을 돕습니다. 각 서브에이전트는 또한 관심사의 분리를 제공합니다 — 서로 다른 도구, 프롬프트, 탐색 궤적 — 그리고 그것이 경로 의존성을 줄이고 철저하고 독립적인 조사를 가능하게 합니다3.

저 두 문장을 뜯어 봅시다. 팬아웃은 적어도 세 가지를 사 줍니다:

  1. 윈도 용량. 그들은 이 아키텍처적 판단을 결론으로 적었습니다. 별개의 컨텍스트 윈도를 가진 에이전트들에게 작업을 분산하는 것이 병렬 추론을 위한 용량을 더한다3. 레슨 1에서 다룬 이야기입니다. 진짜로 천장에 부딪히는 것은 윈도 크기가 아니라 '하나의 루프'라는 형태입니다. 팬아웃은 윈도를 늘려서가 아니라 여러 개를 열어서 단일 윈도의 한계를 우회합니다.
  2. 관심사의 분리. 서로 다른 도구와 프롬프트를 지닌 서브에이전트 셋은 자연히 서로를 오염시키지 않습니다.
  3. 경로 의존성 감소. 하나의 루프 안에서는 3단계의 판단이 2단계의 표현에 치우칩니다. 독립적인 궤적 셋은 같은 치우침을 공유하지 않습니다.

현행 플랫폼 문서도 같은 방향을 제시합니다. 여러 에이전트가 자기만의 격리된 컨텍스트로 병렬로 행동할 수 있고, 이것이 출력 품질을 높이는 데 도움이 되며 완료까지의 시간도 개선할 수 있습니다2. 품질이 먼저 나온다는 데 주목하세요.

속도 쪽으로는 숫자를 하나 주었는데, 함께 옮겨야 할 맥락이 있습니다. 그들의 초기 에이전트는 순차 검색을 수행했고 그것은 고통스러울 만큼 느렸습니다. 속도를 위해 그들은 두 종류의 병렬화를 도입했습니다. (1) 리드 에이전트가 서브에이전트 3~5개를 직렬이 아니라 병렬로 띄우고, (2) 서브에이전트가 도구를 3개 이상 병렬로 씁니다. 이 변경이 복잡한 질의에 대해 리서치 시간을 최대 90%까지 줄였습니다3.

이 숫자는 세 개의 한정 조건과 함께 써야 합니다. 이것은 레이턴시 숫자이지 품질 숫자가 아닙니다. 복잡한 질의에 한정됩니다(단순한 질의에는 병렬화할 여지가 별로 없습니다). 그들 자신의 시스템에서 나왔습니다. forPromise.all로 갈아 끼워 얼마나 아낄지는 여러분의 하위 작업 중 얼마나가 정말로 독립적인지, 각 경로가 얼마나 느린지, 동시 실행이 어디서 병목에 걸리는지에 달려 있습니다 — 이 레슨의 나머지가 그 이야기입니다.

첫 번째 비용: 집계가 아껴 둔 컨텍스트를 도로 먹는다

팬아웃할 때는 모두가 '한 번에 몇 갈래가 도는가'를 봅니다. 사고는 대개 돌아오는 길에서 납니다.

Claude Code의 서브에이전트 문서는 이 비용을 테이블에 올려놓습니다. 서브에이전트가 완료하면 그 결과가 메인 대화로 돌아옵니다. 각자 상세한 결과를 돌려주는 서브에이전트를 여럿 돌리면 상당한 컨텍스트를 소모할 수 있습니다4. 서브에이전트가 존재하는 이유는 메인 대화의 컨텍스트를 보호하기 위해서인데 — 탐색과 구현을 메인 대화 바깥에 두는 것4 — 돌아오는 것이 너무 무거워지면 보호가 뒤집힙니다.

같은 회고가 처방을 주었고, 구체적으로 주었습니다. 서브에이전트가 모든 것을 리드 에이전트를 통해 소통하도록 요구하는 대신, 전문화된 에이전트가 독립적으로 지속되는 출력을 만들 수 있는 아티팩트 시스템을 구현하세요. 서브에이전트는 도구를 호출해 자기 작업을 외부 시스템에 저장한 다음, 가벼운 참조를 코디네이터에게 넘깁니다3.

이것을 코드를 쓸 때의 주문(呪文)으로 옮기면, 페이로드가 아니라 참조를 넘겨라입니다.

차이가 얼마나 클까요? 이 레슨의 연습 스크립트가 실제 숫자를 줍니다. 디스크에 놓인 출력 8개는 합쳐서 1,793바이트인데, 집계로 돌아오는 것은 643바이트뿐이며, 이 비율은 문서가 커질수록 빠르게 벌어집니다. 보고서가 열 배 길어져도 넘어가는 것은 여전히 한 줄 요약과 경로 하나입니다. 전문이 필요한 쪽이 그 경로에서 읽으면 됩니다.

이 길은 덤으로 다른 것도 사 줍니다. 워크플로 문서는 런타임이 실행이 진행되는 대로 각 에이전트의 결과를 추적하며 그것이 같은 세션 안에서 실행을 재개 가능하게 만든다고 언급합니다. 작업을 여러 작은 에이전트로 팬아웃하는 워크플로는 그래서 하나의 긴 에이전트보다 더 많은 진행을 보전합니다5. 출력이 바깥에 착지하고 한 줄씩 기록이 남는다는 것 — 기록을 남기는 부분은 이 시리즈 코스 11의 관측 가능성과 같은 원리입니다. '중간에 끊겨도 처음부터 다시 하지 않는다'는 부분은 코스 9의 영역, '긴 작업이 중단을 견디게 만들기'입니다.

두 번째 비용: 동시 실행은 결코 무제한이 아니다

Promise.all(docs.map(...))을 쓰는 순간, 여러분은 사실 '동시 실행 수 = 배열 길이'라고 말한 것입니다. 배열이 12면 괜찮습니다. 배열이 200이면 이야기가 다릅니다.

실제 제품 셋의 상한을 보세요:

  • Claude Code: 기본적으로, 한 세션에서 서브에이전트 20개가 돌고 있을 때 Agent 도구로 하나를 더 띄우면 Concurrent subagent limit reached로 실패하며, 그 에러 메시지는 Claude에게 재시도하지 말라고 명시적으로 말합니다4.
  • Claude Code 워크플로 런타임: 최대 16개의 동시 에이전트, Claude Code가 쓸 수 있는 CPU가 적으면(CPU가 제한된 컨테이너 안을 포함해) 더 적습니다5. 실행당 총 1,000개 에이전트5.
  • Managed Agents: 최대 25개의 동시 스레드가 지원됩니다. 코디네이터는 로스터에 있는 하나의 에이전트를 여러 벌 호출해, 하나의 에이전트에 결부된 여러 스레드를 만들 수 있습니다2.

서로 다른 세 팀, 서로 다른 세 구현이 모두 상한을 두었고, 게다가 숫자가 크지도 않습니다. 이 사실 자체가 교재입니다. 상한 없는 팬아웃은 최적화가 아니라 사고입니다. (레슨 4에서 실제 사고 사례를 다룹니다 — 초기 에이전트는 단순한 질의에 서브에이전트 50개를 띄우곤 했습니다3. 그때 가면 '상한이 얼마인가'보다 '몇 개를 띄울지 누가 정하는가'가 더 까다롭다는 것을 알게 될 것입니다.)

가장 값싼 조이기는 배치입니다:

돌아가긴 하지만 물통 효과가 있습니다. 각 배치는 가장 느린 것이 끝나야 다음을 시작합니다. 문서 셋 중 하나가 유독 길면 나머지 두 경로는 그냥 기다립니다.

동시 실행 풀에는 이 문제가 없습니다. N개의 '레인'을 고정해 두고, 각 레인은 현재 작업을 끝내는 즉시 공유 커서에서 다음 항목을 집어 오므로, 언제나 N개가 진행 중입니다:

열 줄 남짓, 의존성 없음. cursor++는 단일 스레드 JavaScript에서 안전합니다. 두 await 사이의 동기 코드는 중간에 끼어들 수 없습니다. 두 레인이 같은 인덱스를 집어 갈 위험이 없습니다. 연습에서는 여기에 try/catch를 더해 한 경로의 실패가 배치 전체를 끌어내리지 않게 할 것입니다.

limit은 얼마여야 할까요? 보편적인 답은 없습니다. 여러분의 API 할당량, 하류 서비스의 수용력, 한 경로의 소요 시간이 만나는 지점입니다. 다만 구체적인 숫자를 적어 넣는 것과 적어 넣지 않는 것은 서로 다른 두 종류의 엔지니어링입니다.

세 번째 비용: 투표는 N배의 토큰을 치른다

투표의 정의는 한 문장입니다. 다양한 출력을 얻기 위해 같은 작업을 여러 번 실행하는 것1. 코드는 짧습니다:

여기서도 집계는 코드가 합니다 — filter에 임계값 하나. 몇 표를 임계값으로 삼을지는 제품 결정이고, 하드코딩되어 있고, 언제든 바꿀 수 있고, 감사 가능합니다. 모델의 즉흥에 맡길 것이 아닙니다. 고위험 상황에서는 임계값을 '한 표 거부권'으로 조일 수 있고, 저위험 상황에서는 세 표가 모두 막아야 하도록 요구할 수 있습니다.

셈은 단순합니다. N번 투표하면 N배의 토큰을 치릅니다. 이 돈을 레슨 1의 배수 옆에 놓아 보세요. 그들의 데이터에서 에이전트는 채팅 상호작용의 약 4배, 멀티 에이전트 시스템은 약 15배의 토큰을 씁니다. 그래서 멀티 에이전트 시스템은 이 성능 향상의 비용을 감당할 만큼 가치가 높은 작업을 필요로 합니다3. 세 관점 투표의 가격표는 단일 에이전트의 그 4배에 다시 3을 곱한 것입니다. 15배에 3을 곱하지 마세요 — 그 15배에는 이미 팬아웃의 셈이 들어 있습니다.

그러니 투표는 '마음 편하려고 몇 번 더 돌리는 것'이 아닙니다. 구체적인 무언가를 사 와야 합니다. 워크플로 문서가 더 분명합니다. 계획을 코드로 옮기면 워크플로가 단지 에이전트를 더 돌리는 것이 아니라 반복 가능한 품질 패턴을 적용할 수 있게 됩니다. 독립적인 에이전트들이 서로의 발견을 보고 전에 적대적으로 리뷰하게 하거나, 여러 각도에서 계획을 초안 잡아 서로 견주게 할 수 있고, 그래서 한 번의 통과보다 더 믿을 만한 결과를 얻습니다5.

'독립적인 에이전트가 서로를 리뷰한다'는 이 시리즈 코스 10과 같은 규칙입니다. 작업자는 자기 일을 판정하지 않는다. 같은 컨텍스트 안에서 모델에게 자기 점검을 시키면 대개 방금 자기가 낸 출력을 옹호합니다. 독립적인 컨텍스트 경로로 바꾸고 프롬프트를 바꿔야 비로소 문제를 정말로 잡아낼지도 모릅니다. 투표와 상호 리뷰가 사 오는 것은 '다수결'이 아니라 독립성입니다.

경계를 하나 적어 둡니다. 하나의 모델에 세 번 호출한 것은 세 명의 독립적인 심사자가 아닙니다. 그들은 같은 훈련 편향을 공유합니다. 투표는 샘플링 잡음과 한 번의 통과에서 생긴 주의의 빈틈을 걸러 낼 수 있습니다. 체계적 편향은 걸러 내지 못합니다. 투표만 하면 진실이 나오는 장치로 여기지 마세요.

잣대: 독립성은 전제 조건이지 선택이 아니다

이 레슨의 모든 이득은 반복해서 등장했고 따로 꺼내 볼 값어치가 있는 하나의 전제 위에 놓여 있습니다. 하위 작업이 정말로 서로 의존하지 않아야 한다는 것입니다.

Claude Code 문서는 여러 서브에이전트가 동시에 조사하는 것을 논할 때 한 문장을 특별히 덧붙입니다. 각 서브에이전트가 자기 영역을 독립적으로 탐색하고, 그다음 Claude가 발견을 종합합니다. 이것은 조사 경로가 서로 의존하지 않을 때 가장 잘 작동합니다4. 그 역방향 조건은 멀티 에이전트 회고에 쓰여 있습니다. 어떤 도메인은 모든 에이전트가 같은 컨텍스트를 공유하기를 요구하거나 에이전트 사이의 의존을 많이 수반하며, 이런 도메인은 오늘날 멀티 에이전트 시스템에 잘 맞지 않습니다. 예를 들어 대부분의 코딩 작업은 조사보다 진짜로 병렬화 가능한 작업이 적고, LLM 에이전트는 다른 에이전트를 실시간으로 조율하고 위임하는 데 아직 능숙하지 않습니다3.

어떤 제안을 병렬화해야 할지 판단하려면 질문 하나를 던지세요. 경로 2가 무엇을 할지 알기 위해 경로 1의 결론을 기다려야 합니까?

  • 기다려야 함 → 이것은 병렬화의 형태가 아닙니다. 앞 단계의 출력이 다음 단계의 입력인 것, 그것이 레슨 2의 체이닝입니다.
  • 기다릴 필요 없음 → 섹셔닝.
  • 같은 것에 대해 독립적인 판단 여럿을 원함 → 투표.

첫 경우가 가장 흐려지기 쉽습니다. 의존이 있는데 '어떻게든 되겠지' 하고 팬아웃을 밀어붙이는 것이죠. 결과는 여러 에이전트 경로가 서로를 모른 채 각자 결론을 쓰는 것입니다. 집계 단계에서 여러분이 손으로 모순을 다듬게 되고 — 아낀 시간은 전부 다듬는 데 쓰이며, 게다가 토큰을 더 냈습니다.

경계: 이 레슨의 분해는 미리 정의된 것이다

단어 하나를 못 박아 둡시다. 이것이 레슨 4의 입구입니다.

이 레슨의 모든 예시에서 누가 하위 작업을 정의했습니까? 여러분입니다. 문서 12개는 여러분이 디렉터리에서 읽어 왔습니다. 세 관점은 여러분이 배열에 하드코딩했습니다. 코드가 돌기 전에 몇 갈래가 팬아웃될지, 각각이 무엇을 할지가 모두 정해져 있습니다. 이것을 미리 정의된(predefined) 분해라 부릅니다.

그 반대는 모델이 몇 부분으로 나눌지와 각각이 무엇을 할지를 정하는 것입니다. 원 출처는 이 차이를 두 패턴을 가르는 핵심 분수령으로 다룹니다. 오케스트레이터-워커 워크플로에서는 중앙 LLM이 작업을 동적으로 분해하고, 워커 LLM에게 위임하고, 그 결과를 종합합니다1. 지형적으로는 병렬화와 비슷하지만, 핵심 차이는 그 유연성입니다 — 하위 작업이 미리 정의되지 않고, 구체적인 입력에 근거해 오케스트레이터가 정합니다1.

그러니 둘의 경계는 '한 번에 몇 갈래가 도는가'가 아니라 '그 배열을 누가 썼는가'입니다. 배열이 여러분 것이면 이 레슨입니다. 배열이 모델에 의해 그 자리에서 생성되면 다음 레슨입니다. 지난 레슨의 라우팅은 이미 결정 하나를 모델에게 넘겼습니다(어느 가지로 갈지). 다음 레슨은 더 큰 덩어리를 넘깁니다. 분해 그 자체입니다.

💻 연습

정리

  • 병렬화의 정의는 'LLM은 때때로 하나의 작업을 동시에 수행하고 그 출력을 프로그램적으로 집계할 수 있다'이고, 두 변종은 섹셔닝(작업을 병렬로 실행되는 독립적인 하위 작업으로 쪼개기)과 투표(다양한 출력을 얻기 위해 같은 작업을 여러 번 실행하기)다1
  • 그 조건은 쪼개진 하위 작업을 속도를 위해 병렬화할 수 있거나, 더 높은 확신도를 위해 여러 관점과 시도가 필요한 것이다. 고려 사항이 여럿인 복잡한 작업에서는 각각을 별개의 호출이 주의를 집중해 다루는 편이 일반적으로 더 낫다1
  • 팬아웃이 사 오는 것은 속도만이 아니다. 자기 컨텍스트 윈도를 갖고 병렬로 작동하는 서브에이전트가 탐색하고 가장 중요한 토큰을 응축해 돌려주며, 관심사의 분리(서로 다른 도구, 프롬프트, 탐색 궤적)와 더 적은 경로 의존성도 함께 온다3. 별개의 컨텍스트 윈도를 가진 에이전트들에게 작업을 분산하는 것이 병렬 추론을 위한 용량을 더한다3
  • 속도 향상 숫자에는 맥락이 붙는다. 그들의 Research 시스템은 2단 병렬화를 도입해(리드가 서브에이전트 3~5개를 병렬로 띄우고, 서브에이전트가 도구를 3개 이상 병렬로 씀) 복잡한 질의에 대해 리서치 시간을 최대 90%까지 줄였다3 — 이것은 그들 자신의 시스템에서 나온 레이턴시 숫자이지 품질 숫자가 아니다
  • 집계가 첫 번째 비용이다. 서브에이전트 결과는 메인 대화로 돌아오고, 각자 상세한 결과를 돌려주는 서브에이전트가 여럿이면 상당한 컨텍스트를 소모한다4. 해법은 아티팩트 시스템이다 — 서브에이전트가 출력을 외부 시스템에 저장하고 코디네이터에게는 가벼운 참조만 넘긴다3
  • 동시 실행은 결코 무제한이 아니다. Claude Code는 기본값이 서브에이전트 20개 동시 실행이고 넘으면 '재시도하지 말라'는 명시와 함께 실패한다4. 워크플로 런타임은 최대 16개의 동시 에이전트를 지원하며(CPU가 제한되면 더 적다) 실행당 1,000개로 상한이 걸린다5. Managed Agents는 최대 25개의 동시 스레드다2
  • 투표의 비용은 N배 토큰이고, 그 배수 안에 함께 놓아야 한다 — 그들의 데이터에서 에이전트는 채팅의 약 4배, 멀티 에이전트 시스템은 채팅의 약 15배다. 작업 가치가 그것을 감당할 만큼 높아야 한다3. 그것이 사 와야 하는 것은 반복 가능한 품질 패턴이다. 독립적인 에이전트가 적대적으로 리뷰하게 하거나, 여러 각도에서 초안을 잡아 견주게 하는 것처럼5
  • 독립성이 전제 조건이다. 여러 서브에이전트가 동시에 조사하는 것은 조사 경로가 서로 의존하지 않을 때 가장 잘 작동한다4. 공유 컨텍스트를 요구하거나 의존이 많은 도메인은 오늘날 잘 맞지 않는다3 — 의존이 있으면 체이닝의 형태로 돌아간다
  • 이 레슨의 분해는 전부 미리 정의된 것이다(배열을 여러분이 썼다). 하위 작업이 미리 정의되지 않고 구체적인 입력에 근거해 오케스트레이터가 정하는 것이 오케스트레이터-워커와 병렬화의 핵심 차이이며1, 그것이 다음 레슨의 주제다

>> 레슨 4: 오케스트레이터-워커: 분해 자체를 동적으로 만들기

Footnotes

  1. Building Effective AI Agents — Anthropic Engineering — https://www.anthropic.com/engineering/building-effective-agents 2 3 4 5 6 7 8 9 10 11 12 13

  2. Multiagent orchestration — Claude API documentation (Managed Agents) — https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration 2 3 4

  3. How we built our multi-agent research system — Anthropic Engineering — https://www.anthropic.com/engineering/multi-agent-research-system 2 3 4 5 6 7 8 9 10 11 12 13

  4. Create custom subagents — Claude Code official documentation — https://code.claude.com/docs/en/sub-agents 2 3 4 5 6 7

  5. Orchestrate subagents at scale with dynamic workflows — Claude Code official documentation — https://code.claude.com/docs/en/workflows 2 3 4 5 6

연습

01

책상에 제안 다섯 개가 놓여 있습니다. 각각에 섹셔닝 / 투표 / 병렬화하면 안 됨 중 하나를 부여하고, 이유를 설명하세요. 섹셔닝이나 투표로 판정한 것에는 처리 방침 두 가지를 덧붙이세요. 동시 실행을 어떻게 설정할지, 집계를 어떻게 할지.

레벨 1: 팬아웃 제안 다섯 개, 형태를 판정하고 함정 찾기
  1. 서로 독립적인 제품 문서 12개, 각각 같은 리뷰 절차를 거쳐야 함.
  2. 곧 홈페이지에 올라갈 고위험 카피 하나, 법무·보안·브랜드 관점의 평가가 필요함.
  3. 백업 → 스키마 변경 → 데이터 백필, 세 단계를 엄격한 순서로 요구하는 데이터베이스 마이그레이션 스크립트.
  4. 모듈 40개에 기술 부채 평가가 필요한데, 평가 규칙이 각 모듈이 앞 모듈의 결론을 참조해 점차 통일된 기준으로 수렴할 것을 요구함.
  5. 사용자 입력 하나를 위반 콘텐츠인지 아닌지 분류해야 하고, 오분류 비용이 크며, 한 번의 호출보다 높은 확신도를 원함.
완료 기준 · 로컬에서 확인
02

실행 가능한 fanout.mjs를 쓰세요(Node 18+, 의존성 없음). 요구사항:

레벨 2: 상한이 있는 팬아웃을 써서 직접 돌려 보기
  • 내장 스텁 clientmessages.create를 흉내 내고, 오프라인이며, 항목마다 소요 시간과 결론이 하드코딩되어 있어 두 실행을 글자 단위로 비교할 수 있을 것
  • 항목 8개, 항목마다 runAgent() 호출 하나로 처리(레슨 2의 래퍼 형태를 재사용)
  • 동시 실행 풀을 직접 구현하고 상한을 3으로 둘 것. 동시에 도는 워커가 3을 넘지 않아야 하며, 한 경로의 실패가 배치 전체를 끌어내리지 않아야 함
  • 워커마다 출력을 out/ 디렉터리의 파일에 쓸 것. 집계는 파일 경로와 한 줄 요약만 모으고, 보고서 본문을 되가져오지 않을 것
  • 마지막에 요약 표(항목 / 소요 / 결론 / 결과 파일)를 출력하고, 디스크 바이트와 반환 바이트의 비교를 출력할 것
  • 종료 코드가 있는 판정 경로: 전부 성공 exit 0, 하나라도 실패하거나 결과 파일이 비어 있으면 exit 1
  • 돌린 뒤 동시 실행 상한을 3에서 8로 바꿔 다시 돌리고(코드를 고치든 환경 변수를 쓰든), 완료 순서는 바뀌지만 결과 집합은 바뀌지 않음을 확인할 것. 순서가 실제로 바뀌게 하려면 하드코딩한 소요 시간을 배열 순서대로 증가시키거나 전부 같게 하지 말 것(120/40/200/60/30/150/80/45처럼). 그러지 않으면 두 동시 실행 수에서 완료 순서가 우연히 같아집니다
완료 기준 · 로컬에서 확인