AI 에이전트 오케스트레이션 패턴 — 무엇을 언제 조합할 것인가
파이프라인·병렬 분산·계층적·반성-수정 오케스트레이션 패턴과 Anthropic Claude 기반 에이전트 하네스 구현, 단일 대 멀티 에이전트 선택 기준을 정리한다.
2026-08-14 · 최초 발행 2026-03-29
단일 LLM 호출로 해결할 수 없는 복잡한 작업이 늘어나면서, 여러 AI 에이전트를 조율하는 오케스트레이션 아키텍처가 실무의 핵심 과제로 부상하고 있다. 에이전트 하나가 실패하면 전체 파이프라인이 멈추고, 에이전트 간 컨텍스트 공유가 잘못되면 일관성 없는 결과가 쏟아지며, 비용은 기하급수적으로 늘어난다. 잘 설계된 멀티 에이전트 시스템은 이러한 함정을 피하면서 단일 에이전트로는 불가능한 수준의 작업을 수행한다.
조율이 담당하는 역할들
오케스트레이션(orchestration)은 여러 에이전트가 공동 목표를 향해 협력할 때 작업 분배, 상태 관리, 오류 처리, 결과 통합을 담당하는 조율 레이어다. 오케스트레이터 자체도 LLM 기반 에이전트일 수 있고, 결정론적 코드일 수도 있다.
오케스트레이터(Orchestrator)는 전체 작업을 분해하고 서브에이전트에게 위임하며 결과를 통합한다. 워커 에이전트(Worker Agent)는 코드 작성·검색·검증처럼 특정 역할에 특화된 실행 에이전트로 단일 책임을 갖는다. 공유 메모리(Shared Memory)는 에이전트 간 컨텍스트와 중간 결과를 공유하는 저장소이고, 도구(Tools)는 웹 검색·코드 실행·데이터베이스 쿼리처럼 에이전트가 외부 시스템과 상호작용하는 함수다.
순서대로, 동시에, 계층으로, 되돌아보며
각 에이전트가 이전 에이전트의 출력을 받아 처리하는 선형 체인 구조가 파이프라인(Pipeline) 패턴이다. 구현이 단순하고 디버깅이 쉽지만, 병렬 처리가 불가능하고 중간 단계가 실패하면 전체가 중단된다.
문서 번역 파이프라인, 데이터 ETL 처리, 순서가 중요한 멀티스텝 작업이 적합한 사례다.
오케스트레이터가 동일하거나 유사한 작업을 여러 워커에게 동시에 분배하는 것이 병렬 분산(Parallel Fan-out) 패턴이다. 처리 시간을 대폭 줄일 수 있지만 결과 통합 로직이 복잡해진다.
대용량 문서 분석, 멀티소스 리서치, 병렬 코드 생성이 이 패턴에 맞는다.
오케스트레이터가 서브오케스트레이터를 두고, 서브오케스트레이터가 다시 워커를 관리하는 트리 구조가 계층적 오케스트레이션(Hierarchical Orchestration)이다. 복잡도를 계층별로 분리할 수 있지만 레이턴시와 비용이 늘어난다.
대규모 소프트웨어 개발, 복잡한 리서치 프로젝트, 엔터프라이즈 워크플로우 자동화가 대표 사례다.
생성 에이전트와 비평 에이전트가 쌍을 이뤄 품질을 반복적으로 향상시키는 것이 반성-수정(Reflection-Revision) 패턴이다. Anthropic의 Constitutional AI 접근법에서 파생됐다.
고품질 콘텐츠 생성, 코드 리뷰 및 개선, 복잡한 계획 수립에 적합하다.
직접 짜보면 이런 모습이다
import anthropic
import asyncio
from typing import List, Dict, Any
client = anthropic.Anthropic()
class AgentOrchestrator:
def __init__(self, model: str = "claude-opus-4-5"):
self.model = model
self.shared_memory: Dict[str, Any] = {}
async def run_worker(
self,
agent_name: str,
system_prompt: str,
task: str,
tools: List[Dict] = None
) -> str:
"""단일 워커 에이전트 실행"""
messages = [{"role": "user", "content": task}]
response = client.messages.create(
model=self.model,
max_tokens=4096,
system=system_prompt,
messages=messages,
tools=tools or []
)
result = response.content[0].text
self.shared_memory[agent_name] = result
return result
async def parallel_fanout(
self,
tasks: List[Dict[str, str]]
) -> List[str]:
"""병렬 분산 패턴 실행"""
coroutines = [
self.run_worker(
agent_name=task["name"],
system_prompt=task["system"],
task=task["prompt"]
)
for task in tasks
]
return await asyncio.gather(*coroutines)
def orchestrate(self, goal: str) -> str:
"""메인 오케스트레이션 로직"""
# 1단계: 작업 분해
decomposition_prompt = f"""
다음 목표를 독립적인 서브태스크로 분해하세요: {goal}
각 태스크를 JSON 배열로 반환하세요.
"""
subtasks_json = self.run_worker(
"decomposer",
"당신은 복잡한 작업을 독립적 서브태스크로 분해하는 전문가입니다.",
decomposition_prompt
)
# 2단계: 병렬 실행 (생략 - 실제 구현 시 subtasks_json 파싱 필요)
# 3단계: 결과 통합
return subtasks_json
언제 여러 에이전트가 필요한가
| 기준 | 단일 에이전트 | 멀티 에이전트 |
|---|---|---|
| 작업 복잡도 | 단순~중간 | 복잡 |
| 병렬 처리 필요 | 불필요 | 필요 |
| 전문화 필요 | 낮음 | 높음 |
| 비용 | 낮음 | 높음 |
| 레이턴시 | 낮음 | 높을 수 있음 |
| 디버깅 난이도 | 쉬움 | 복잡 |
| 컨텍스트 한계 극복 | 어려움 | 가능 |
핵심 원칙은 단일 에이전트로 해결 가능하면 멀티 에이전트를 쓰지 말라는 것이다. 복잡성은 버그와 비용을 함께 늘린다.
설계할 때 걸려 넘어지는 지점들
Reflection 패턴에서 종료 조건을 명확히 정의하지 않으면 에이전트가 무한히 수정을 반복할 수 있다. 최대 반복 횟수(max_iterations)를 항상 설정해야 한다. 에이전트 간 컨텍스트를 공유할 때 불필요한 정보가 섞이면 성능이 저하되는 컨텍스트 오염이 생기므로, 각 에이전트에게 필요한 최소한의 컨텍스트만 전달하는 원칙을 지켜야 한다. 오케스트레이터가 서브태스크를 계속 생성하는 경우 토큰 비용이 예상을 크게 초과할 수 있어, 작업 시작 전 최대 토큰 예산을 설정하고 추적해야 한다.
AI 에이전트 오케스트레이션은 강력하지만 그만큼 복잡성 비용도 높다. 파이프라인, 병렬 분산, 계층적 오케스트레이션, 반성-수정 패턴 각각은 적합한 문제 유형이 존재하며, 이를 혼합해 사용하는 것이 실무에서의 일반적인 접근이다. 무엇보다 시스템을 작게 시작해 단일 에이전트가 처리하는 것을 확인한 후, 필요한 경우에만 멀티 에이전트 구조로 점진적으로 확장하는 전략이 장기적으로 가장 안정적이다.