대부분의 팀에는 멀티 에이전트 시스템이 필요하지 않다. 단일 에이전트보다 확실한 성과 차이를 보이는 세 가지 상황과 효과적인 구현 방법을 살펴본다.
멀티 에이전트 시스템(multi-agent system)은 여러 LLM 인스턴스가 각자의 대화 컨텍스트를 가지고 실행되며, 코드로 조율되는 아키텍처다. 에이전트 스웜(agent swarm), 기능 기반 시스템, 메시지 버스 아키텍처 등 다양한 조율 패턴이 존재하지만, 이 글에서는 오케스트레이터-서브에이전트 패턴에 집중한다. 이 패턴은 리드 에이전트가 특정 하위 작업을 담당하는 전문화된 서브에이전트를 생성하고 관리하는 계층 구조 모델로, 조율 방식이 직관적이고 멀티 에이전트 시스템을 처음 도입하는 팀에게 좋은 출발점이 된다. 다른 패턴들은 다음 글에서 자세히 다룰 예정이다.
오늘날 멀티 에이전트 시스템은 단일 에이전트가 더 나은 성능을 낼 수 있는 상황에서도 무분별하게 적용되는 경우가 많다. 물론 모델 성능이 향상되면서 이 판단 기준도 계속 변하고 있다. Anthropic에서도 수개월을 투자해 정교한 멀티 에이전트 아키텍처를 구축했지만, 정작 단일 에이전트의 프롬프트를 개선하는 것만으로 동일한 결과를 얻을 수 있었다는 사실을 뒤늦게 발견한 팀들을 여럿 봐왔다.
멀티 에이전트 시스템을 구축하고 실제 프로덕션 환경에 배포하는 팀들과 협력해온 경험을 바탕으로, 우리는 멀티 에이전트가 단일 에이전트를 일관되게 앞서는 세 가지 상황을 파악했다. 컨텍스트 오염(context pollution)으로 성능이 저하될 때, 작업을 병렬로 실행할 수 있을 때, 그리고 전문화가 도구 선택이나 작업 집중도를 높일 때다. 이 세 가지 상황을 벗어나면 조율에 드는 비용이 대체로 그 이점을 초과한다.
이 글에서는 단일 에이전트의 한계를 인식하는 방법, 멀티 에이전트 시스템이 빛을 발하는 세 가지 시나리오를 파악하는 방법, 그리고 흔한 구현 실수를 피하는 방법을 공유한다.
적절한 도구를 갖춘 잘 설계된 단일 에이전트는 대부분의 개발자가 예상하는 것보다 훨씬 많은 것을 해낼 수 있다.
멀티 에이전트 시스템은 필연적으로 오버헤드를 수반한다. 에이전트를 하나 추가할 때마다 잠재적 장애 지점이 하나 늘고, 유지보수해야 할 프롬프트가 생기며, 예측하기 어려운 동작이 발생할 가능성도 높아진다.
계획, 실행, 검토, 반복을 각각 담당하는 에이전트로 구성된 정교한 멀티 에이전트 시스템을 구축했다가, 인계 시마다 컨텍스트가 유실되고 실제 작업보다 조율에 더 많은 토큰을 소모한다는 사실을 뒤늦게 깨달은 팀들도 있었다. 우리의 테스트에 따르면, 멀티 에이전트 구현은 동일한 작업을 처리할 때 단일 에이전트 방식보다 토큰을 3~10배 더 사용한다. 이 오버헤드는 에이전트 간 컨텍스트 중복, 에이전트 간 조율 메시지, 인계 시 결과 요약에서 비롯된다.
멀티 에이전트 아키텍처는 단일 에이전트로 극복할 수 없는 특정 제약을 해소할 때 비로소 가치를 발휘한다. 따라서 추가 비용을 정당화할 수 있는 명확한 이점이 있는 경우에만 멀티 에이전트 아키텍처를 선택해야 한다.
아래의 패턴들은 이러한 투자 대비 긍정적인 성과가 일관되게 관찰된 사례들이다.
대형 언어 모델은 유한한 컨텍스트 윈도우를 가지며, 컨텍스트가 커질수록 응답 품질이 저하될 수 있다. 에이전트가 이전 하위 작업에서 수집한 정보 중 이후 작업과 무관한 내용이 컨텍스트에 쌓이면 컨텍스트 오염이 발생한다. 서브에이전트는 각자 자신의 작업에만 집중하는 독립적인 컨텍스트에서 동작하기 때문에 이러한 오염을 차단할 수 있다.
기술적 문제를 진단하는 동시에 주문 내역을 조회해야 하는 고객 지원 에이전트를 생각해보자. 주문 조회를 할 때마다 수천 개의 토큰이 컨텍스트에 추가된다면, 기술 문제를 추론하는 에이전트의 능력은 점점 떨어진다.
단일 에이전트 방식:
# Single agent accumulates everything in context
conversation_history = [
{"role": "user", "content": "My order #12345 isn't working"},
{"role": "assistant", "content": "Let me check your order..."},
# Tool result adds 2000+ tokens of order history
{"role": "user", "content": "... (order details, past purchases, shipping info) ..."},
{"role": "assistant", "content": "Now let me diagnose the technical issue..."},
# Context is now polluted with order details the agent doesn't need
]
에이전트는 2,000개 이상의 무관한 주문 내역 토큰을 컨텍스트에 유지한 채로 기술적 문제를 추론해야 하며, 이로 인해 주의가 분산되고 응답 품질이 저하된다.
멀티 에이전트 방식:
from anthropic import Anthropic
client = Anthropic()
class OrderLookupAgent:
def lookup_order(self, order_id: str) -> dict:
# Separate agent with its own context
messages = [
{"role": "user", "content": f"Get essential details for order {order_id}"}
]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=messages,
tools=[get_order_details_tool]
)
# Returns only essential information
return extract_summary(response)
class SupportAgent:
def handle_issue(self, user_message: str):
if needs_order_info(user_message):
order_id = extract_order_id(user_message)
# Get only what's needed, not full history
order_summary = OrderLookupAgent().lookup_order(order_id)
# Inject compact summary, not full context
context = f"Order {order_id}: {order_summary['status']}, purchased {order_summary['date']}"
# Main agent context stays clean
messages = [
{"role": "user", "content": f"{context}\n\nUser issue: {user_message}"}
]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
messages=messages
)
return response
주문 조회 에이전트가 전체 주문 내역을 처리하고 요약본을 추출한다. 메인 에이전트는 실제로 필요한 50~100개의 토큰만 전달받아 집중력을 유지할 수 있다.
컨텍스트 격리는 다음 조건을 만족할 때 가장 효과적이다. 하위 작업이 대량의 컨텍스트(1,000 토큰 이상)를 생성하지만 그 대부분이 메인 작업과 무관할 때, 하위 작업의 범위가 명확하고 추출할 정보의 기준이 분명할 때, 그리고 사용 전 필터링이 필요한 조회 또는 검색 작업일 때다.
여러 에이전트를 병렬로 실행하면 단일 에이전트로는 커버할 수 없는 더 넓은 탐색 공간을 다룰 수 있다. 이 패턴은 특히 검색 및 리서치 작업에서 효과가 입증됐다.
Anthropic의 Research 기능이 이 방식을 사용한다. 리드 에이전트가 쿼리를 분석하고 여러 서브에이전트를 생성해 각기 다른 측면을 병렬로 조사한다. 각 서브에이전트는 독립적으로 검색을 수행한 뒤 핵심 내용을 정리해 반환한다. 멀티 에이전트 검색은 더 넓은 정보 공간을 탐색할 수 있어 단일 에이전트 방식 대비 정확도가 크게 향상된다.
핵심 구현 방식은 질문을 독립적인 측면으로 분해하고, 서브에이전트를 동시에 실행한 뒤, 결과를 종합하는 것이다.
import asyncio
from anthropic import AsyncAnthropic
client = AsyncAnthropic()
async def research_topic(query: str) -> dict:
# Lead agent breaks query into research facets
facets = await lead_agent.decompose_query(query)
# Spawn subagents to research each facet in parallel
tasks = [
research_subagent(facet)
for facet in facets
]
results = await asyncio.gather(*tasks)
# Lead agent synthesizes findings
return await lead_agent.synthesize(results)
async def research_subagent(facet: str) -> dict:
"""Each subagent has its own context window"""
messages = [
{"role": "user", "content": f"Research: {facet}"}
]
response = await client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=messages,
tools=[web_search, read_document]
)
return extract_findings(response)더 넓은 탐색 범위에는 비용이 따른다. 멀티 에이전트 시스템은 동일한 작업에서 단일 에이전트보다 토큰을 3~10배 더 소비한다. 에이전트마다 고유한 컨텍스트가 필요하고, 조율을 위한 에이전트 간 메시지가 오가며, 에이전트 간 결과를 전달할 때 요약이 필요하기 때문이다. 병렬 처리로 순차 실행 대비 총 실행 시간을 줄일 수 있지만, 전체 연산량이 대폭 증가하기 때문에 멀티 에이전트 시스템이 단일 에이전트보다 총 실행 시간이 더 오래 걸리는 경우가 많다.
병렬화의 핵심 이점은 속도가 아닌 철저함이다. 넓은 정보 공간을 탐색하거나 복잡한 질문의 다양한 각도를 조사해야 할 때, 병렬 에이전트는 컨텍스트 한계 안에서 작업하는 단일 에이전트보다 더 많은 범위를 커버할 수 있다. 그 대신 토큰 사용량이 늘고 총 실행 시간이 길어지지만, 더 포괄적인 결과를 얻을 수 있다.
작업에 따라 서로 다른 도구 세트, 시스템 프롬프트, 또는 전문 지식 영역이 필요한 경우가 있다. 하나의 에이전트에 수십 가지 도구를 모두 제공하는 대신, 각자의 역할에 맞는 도구 세트를 갖춘 전문화된 에이전트를 활용하면 신뢰성을 높일 수 있다.
에이전트에 너무 많은 도구가 주어지면 성능이 저하된다. 도구 전문화가 필요하다는 신호는 세 가지다.
작업에 따라 서로 충돌하는 페르소나, 제약 조건, 또는 지침이 요구되기도 한다. 고객 지원 에이전트는 공감과 인내가 필요하고, 코드 리뷰 에이전트는 정밀함과 비판적 시각이 요구된다. 준수 여부를 확인하는 에이전트는 규칙을 엄격히 따라야 하지만, 브레인스토밍 에이전트는 창의적 유연성이 필요하다. 하나의 에이전트가 서로 충돌하는 행동 방식 사이를 오가야 할 때는, 각기 맞춤화된 시스템 프롬프트를 갖춘 전문화된 에이전트로 분리하는 편이 더 일관된 결과를 낸다.
일부 작업은 범용 에이전트를 압도할 만큼 깊은 도메인 컨텍스트를 필요로 한다. 법률 분석 에이전트는 판례와 규제 체계에 관한 방대한 컨텍스트가 필요할 수 있고, 의학 리서치 에이전트는 임상 시험 방법론에 관한 전문 지식이 요구될 수 있다. 모든 도메인 컨텍스트를 단일 에이전트에 집어넣는 대신, 각자의 역할에 맞는 전문 지식을 보유한 에이전트로 분리하는 것이 효과적이다.
예시: 멀티 플랫폼 연동. CRM, 마케팅 자동화, 메시징 플랫폼을 아우르는 연동 시스템을 생각해보자. 각 플랫폼에는 10~15개의 관련 API 엔드포인트가 있다. 40개 이상의 도구를 가진 단일 에이전트는 플랫폼 간 유사한 작업을 혼동하며 도구 선택에 어려움을 겪는 경우가 많다. 집중된 도구 세트와 맞춤 프롬프트를 갖춘 전문화된 에이전트로 분리하면 이러한 선택 오류를 해소할 수 있다.
from anthropic import Anthropic
client = Anthropic()
# Specialized agents with focused toolsets and tailored prompts
class CRMAgent:
"""Handles customer relationship management operations"""
system_prompt = """You are a CRM specialist. You manage contacts,
opportunities, and account records. Always verify record ownership
before updates and maintain data integrity across related records."""
tools = [
crm_get_contacts,
crm_create_opportunity,
# 8-10 CRM-specific tools
]
class MarketingAgent:
"""Handles marketing automation operations"""
system_prompt = """You are a marketing automation specialist. You
manage campaigns, lead scoring, and email sequences. Prioritize
data hygiene and respect contact preferences."""
tools = [
marketing_get_campaigns,
marketing_create_lead,
# 8-10 marketing-specific tools
]
class OrchestratorAgent:
"""Routes requests to specialized agents"""
def execute(self, user_request: str):
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system="""You coordinate platform integrations. Route requests to the appropriate specialist:
- CRM: Contact records, opportunities, accounts, sales pipeline
- Marketing: Campaigns, lead nurturing, email sequences, scoring
- Messaging: Notifications, alerts, team communication""",
messages=[
{"role": "user", "content": user_request}
],
tools=[delegate_to_crm, delegate_to_marketing, delegate_to_messaging]
)
return response
이 패턴은 각자의 역할에 맞는 도구를 갖춘 전문가들이 모든 분야를 혼자 감당하려는 제너럴리스트보다 효과적으로 협업하는 방식과 닮아 있다. 다만 전문화는 라우팅 복잡성을 수반한다. 오케스트레이터가 요청을 올바르게 분류하고 적절한 에이전트에 위임해야 하며, 잘못된 라우팅은 곧 나쁜 결과로 이어진다. 또한 전문화된 에이전트가 늘어날수록 프롬프트 유지보수 부담도 커진다. 전문화는 도메인이 명확하게 분리되고 라우팅 결정이 모호하지 않을 때 가장 잘 작동한다.
일반적인 프레임워크 외에도, 단일 에이전트 패턴의 한계에 도달했음을 알리는 구체적인 신호들이 있다.
컨텍스트 한계에 근접. 에이전트가 일상적으로 대량의 컨텍스트를 사용하면서 성능이 저하된다면, 컨텍스트 압박이 병목일 수 있다. 다만 최근 컨텍스트 관리 기술의 발전(압축 기능 등)으로 이 한계가 점점 줄어들고 있으며, 단일 에이전트도 훨씬 긴 구간에 걸쳐 효과적인 메모리를 유지할 수 있게 됐다.
다수의 도구 관리. 에이전트에 도구가 15~20개 이상이 되면, 모델이 사용 가능한 옵션을 파악하는 데 상당한 컨텍스트와 주의를 소모한다. 멀티 에이전트 아키텍처로 전환하기 전에, Tool Search Tool 사용을 먼저 고려해볼 것을 권한다. 이 도구는 Claude가 모든 도구 정의를 사전에 로드하지 않고 필요할 때 동적으로 탐색할 수 있게 해준다. 도구 선택 정확도를 높이면서도 토큰 사용량을 최대 85%까지 줄일 수 있다.
병렬화 가능한 하위 작업. 여러 소스에 걸친 리서치나 여러 컴포넌트에 대한 테스트처럼, 작업이 자연스럽게 독립적인 단위로 분해될 때 병렬 서브에이전트가 실행 시간을 크게 단축할 수 있다.
이러한 임계치는 모델 성능이 향상되면서 계속 변할 것이다. 현재의 기준은 근본적인 제약이 아닌 실용적인 가이드라인으로 이해해야 한다.
멀티 에이전트 아키텍처를 도입할 때 가장 중요한 설계 결정은 에이전트 간 작업을 어떻게 나눌 것인가다. 많은 팀이 이 선택을 잘못해 멀티 에이전트 설계의 이점을 조율 오버헤드가 상쇄해버리는 상황을 자주 목격했다.
핵심은 작업을 분해할 때 문제 중심 관점이 아닌 컨텍스트 중심 관점을 취하는 것이다.
문제 중심 분해(역효과가 나는 경우가 많음). 작업 유형별로 나누는 방식(한 에이전트는 기능 작성, 다른 에이전트는 테스트 작성, 세 번째는 코드 리뷰)은 지속적인 조율 오버헤드를 만든다. 인계할 때마다 컨텍스트가 유실된다. 테스트 에이전트는 왜 특정 구현 결정이 내려졌는지 모르고, 코드 리뷰 에이전트는 탐색과 반복의 맥락을 알 수 없다.
컨텍스트 중심 분해(대체로 효과적). 컨텍스트 경계를 기준으로 분리하면, 특정 기능을 담당하는 에이전트가 해당 기능의 테스트도 함께 처리한다. 이미 필요한 컨텍스트를 갖고 있기 때문이다. 컨텍스트를 완전히 격리할 수 있을 때만 작업을 분리해야 한다.
이 원칙은 멀티 에이전트 시스템의 실패 유형을 관찰하면서 도출됐다. 에이전트를 문제 유형별로 나누면 "전화 게임(telephone game)"처럼 정보를 주고받게 되고, 인계가 반복될수록 정확도가 떨어진다. 소프트웨어 개발 역할(플래너, 구현자, 테스터, 리뷰어)로 전문화된 에이전트를 사용한 실험에서, 서브에이전트들은 실제 작업보다 조율에 더 많은 토큰을 소비했다.
효과적인 분해 경계의 예:
문제가 되는 분해 경계의 예:
도메인을 막론하고 꾸준히 효과를 발휘하는 멀티 에이전트 패턴이 하나 있다. 바로 검증 서브에이전트다. 메인 에이전트의 작업을 테스트하거나 검증하는 것만을 전담하는 에이전트다.
한 가지 짚어둘 점은, Claude Opus 4.5와 같은 더 강력한 오케스트레이터 모델은 별도의 검증 단계 없이 서브에이전트의 작업을 직접 평가하는 능력이 점점 높아지고 있다는 것이다. 그럼에도 검증 서브에이전트는 성능이 낮은 오케스트레이터를 사용할 때, 검증에 전문화된 도구가 필요할 때, 또는 워크플로우에 명시적인 검증 체크포인트를 두고 싶을 때 여전히 유용하다.
검증 서브에이전트가 효과적인 이유는 전화 게임 문제를 근본적으로 회피하기 때문이다. 검증은 본질적으로 최소한의 컨텍스트 전달만 요구하므로, 검증 에이전트는 시스템이 어떻게 구축됐는지 전체 이력을 알 필요 없이 블랙박스 테스트를 수행할 수 있다.
메인 에이전트가 작업 단위를 완료하면, 다음 단계로 넘어가기 전에 검증할 결과물, 명확한 성공 기준, 검증 수행에 필요한 도구를 갖춘 검증 서브에이전트를 생성한다.
검증 에이전트는 결과물이 왜 그렇게 만들어졌는지 이해할 필요가 없다. 결과물이 명시된 기준을 충족하는지만 판단하면 된다.
from anthropic import Anthropic
client = Anthropic()
class CodingAgent:
def implement_feature(self, requirements: str) -> dict:
"""Main agent implements the feature"""
messages = [
{"role": "user", "content": f"Implement: {requirements}"}
]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=messages,
tools=[read_file, write_file, list_directory]
)
return {
"code": response.content,
"files_changed": extract_files(response)
}
class VerificationAgent:
def verify_implementation(self, requirements: str, files_changed: list) -> dict:
"""Separate agent verifies the work"""
messages = [
{"role": "user", "content": f"""
Requirements: {requirements}
Files changed: {files_changed}
Run the test suite and verify:
1. All existing tests pass
2. New functionality works as specified
3. No obvious errors or security issues
You MUST run the complete test suite before marking as passed.
Do not mark as passing after only running a few tests.
Run: pytest --verbose
Only mark as PASSED if ALL tests pass with no failures.
"""}
]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=messages,
tools=[run_tests, execute_code, read_file]
)
return {
"passed": extract_pass_fail(response),
"issues": extract_issues(response)
}
def implement_with_verification(requirements: str, max_attempts: int = 3):
for attempt in range(max_attempts):
result = CodingAgent().implement_feature(requirements)
verification = VerificationAgent().verify_implementation(
requirements,
result['files_changed']
)
if verification['passed']:
return result
requirements += f"\n\nPrevious attempt failed: {verification['issues']}"
raise Exception(f"Failed verification after {max_attempts} attempts")검증 서브에이전트는 다음 영역에서 효과적이다.
검증 서브에이전트에서 가장 심각한 실패 유형은 충분한 테스트 없이 출력을 통과 처리하는 것이다. 검증 에이전트가 한두 가지 테스트만 실행하고 통과되는 것을 확인한 뒤 성공을 선언해버리는 경우다.
완화 전략은 다음과 같다.
멀티 에이전트 시스템은 강력하지만, 모든 상황에 적합한 것은 아니다. 여러 에이전트를 조율하는 복잡성을 추가하기 전에 다음 사항을 확인해야 한다.
우리의 조언은 이렇다. 작동하는 가장 단순한 방법에서 시작하고, 근거가 충분할 때만 복잡성을 더하라.
이 글은 멀티 에이전트 시스템을 다루는 시리즈의 첫 번째 글이다. 단일 에이전트 패턴에 대해서는 Building effective agents를, 컨텍스트 관리 전략에 대해서는 Effective context engineering for AI agents를 참고하기 바란다. 멀티 에이전트 리서치 시스템을 구축한 과정을 자세히 살펴보려면 How we built our multi-agent research system을 확인하면 된다.
Cara Phillips가 작성하고, Paul Chen, Andy Schumeister, Brad Abrams, Theo Chu가 기여했다.