분산 Agent 시스템
단일 에이전트의 한계
섹션 제목: “단일 에이전트의 한계”단일 에이전트 시스템은 단순하고 디버깅이 쉽습니다. 하지만 규모가 커지면 한계에 부딪힙니다.
단일 에이전트 한계:- 컨텍스트 윈도우 한도 (대규모 코드베이스 처리 불가)- 직렬 실행 (병렬 작업 불가)- 단일 실패 지점 (에이전트 실패 시 전체 중단)- 특화 불가 (모든 작업을 동일한 에이전트가 처리)분산 에이전트 시스템은 이 문제를 마이크로서비스와 유사한 방식으로 해결합니다.
마이크로서비스 스타일 에이전트
섹션 제목: “마이크로서비스 스타일 에이전트”각 에이전트가 명확한 책임 영역을 갖고 독립적으로 동작합니다.
에이전트 서비스 분해 예시
섹션 제목: “에이전트 서비스 분해 예시”코드 생성 팀├── spec-agent: 요구사항 분석, 스펙 작성├── architect-agent: 설계 결정, 아키텍처 다이어그램├── coder-agent: 실제 코드 구현├── test-agent: 테스트 코드 작성└── reviewer-agent: 코드 리뷰, 피드백에이전트 서비스 인터페이스
섹션 제목: “에이전트 서비스 인터페이스”interface AgentServiceConfig { name: string version: string capabilities: string[] model: string maxConcurrency: number timeoutMs: number}
interface AgentTask { id: string type: string payload: Record<string, unknown> priority: 'low' | 'normal' | 'high' deadline?: Date}
interface AgentResult { taskId: string success: boolean output: unknown tokensUsed: number durationMs: number error?: string}
abstract class BaseAgentService { constructor(protected config: AgentServiceConfig) {}
abstract canHandle(task: AgentTask): boolean abstract execute(task: AgentTask): Promise<AgentResult>
async handleWithTimeout(task: AgentTask): Promise<AgentResult> { const timer = new Promise<AgentResult>((_, reject) => setTimeout( () => reject(new Error(`Task timeout after ${this.config.timeoutMs}ms`)), this.config.timeoutMs ) ) return Promise.race([this.execute(task), timer]) }}이벤트 드리븐 아키텍처
섹션 제목: “이벤트 드리븐 아키텍처”에이전트들이 직접 통신하는 대신 이벤트 버스를 통해 느슨하게 결합됩니다.
이벤트 버스 패턴
섹션 제목: “이벤트 버스 패턴”개발자 작업 요청 ↓ EventBus.publish('task.created', taskData) ↓ ┌───────────────────────────────────┐ │ Event Bus │ └───────────────────────────────────┘ ↓ ↓ ↓spec-agent coder-agent reviewer-agent(구독 중) (구독 중) (구독 중)
각 에이전트가 관련 이벤트만 처리이벤트 버스 구현
섹션 제목: “이벤트 버스 구현”type EventHandler<T = unknown> = (event: AgentEvent<T>) => Promise<void>
interface AgentEvent<T = unknown> { id: string type: string payload: T timestamp: Date source: string correlationId?: string}
class AgentEventBus { private handlers: Map<string, EventHandler[]> = new Map() private eventLog: AgentEvent[] = []
subscribe<T>(eventType: string, handler: EventHandler<T>): () => void { const existing = this.handlers.get(eventType) ?? [] this.handlers.set(eventType, [...existing, handler as EventHandler])
// 구독 취소 함수 반환 return () => { const current = this.handlers.get(eventType) ?? [] this.handlers.set( eventType, current.filter(h => h !== handler) ) } }
async publish<T>(eventType: string, payload: T, source: string): Promise<void> { const event: AgentEvent<T> = { id: crypto.randomUUID(), type: eventType, payload, timestamp: new Date(), source }
this.eventLog.push(event as AgentEvent)
const handlers = this.handlers.get(eventType) ?? [] await Promise.all(handlers.map(h => h(event as AgentEvent))) }
getEventLog(filter?: { type?: string; since?: Date }): AgentEvent[] { return this.eventLog.filter(e => { if (filter?.type && e.type !== filter.type) return false if (filter?.since && e.timestamp < filter.since) return false return true }) }}오케스트레이터 패턴
섹션 제목: “오케스트레이터 패턴”복잡한 작업을 여러 에이전트에게 분배하고 결과를 집계하는 오케스트레이터가 필요합니다.
interface SubTask { agentType: string task: AgentTask dependsOn?: string[] // 다른 서브태스크 ID}
class AgentOrchestrator { constructor( private agents: Map<string, BaseAgentService>, private eventBus: AgentEventBus ) {}
async executeWorkflow(subtasks: SubTask[]): Promise<Map<string, AgentResult>> { const results = new Map<string, AgentResult>() const pending = new Map(subtasks.map(st => [st.task.id, st]))
while (pending.size > 0) { // 의존성이 모두 완료된 태스크 찾기 const ready = [...pending.values()].filter(st => (st.dependsOn ?? []).every(dep => results.has(dep)) )
if (ready.length === 0) { throw new Error('순환 의존성 감지: 실행 불가능한 워크플로우') }
// 준비된 태스크 병렬 실행 await Promise.all( ready.map(async st => { const agent = this.agents.get(st.agentType) if (!agent) throw new Error(`에이전트 미발견: ${st.agentType}`)
const result = await agent.handleWithTimeout(st.task) results.set(st.task.id, result) pending.delete(st.task.id)
await this.eventBus.publish('subtask.completed', result, 'orchestrator') }) ) }
return results }}분산 vs 중앙화 결정 가이드
섹션 제목: “분산 vs 중앙화 결정 가이드”| 기준 | 중앙화 선택 | 분산화 선택 |
|---|---|---|
| 팀 규모 | 1~5명 | 10명 이상 |
| 작업 복잡도 | 단순, 순차적 | 복잡, 병렬 가능 |
| 컨텍스트 크기 | 단일 컨텍스트로 충분 | 다수의 대용량 컨텍스트 필요 |
| 실패 허용도 | 단순 재시도로 충분 | 부분 실패 허용 필요 |
| 운영 복잡도 | 낮게 유지 선호 | 복잡도 수용 가능 |
A2A와 MCP의 역할 구분
섹션 제목: “A2A와 MCP의 역할 구분”MCP는 호스트가 외부 도구·리소스를 사용하는 인터페이스입니다. A2A는 독립 에이전트 사이에서 메시지·작업 상태·산출물을 교환하는 계약입니다. Agent Card로 기능과 연결 정보를 발견해도 해당 에이전트의 실행 권한이 자동으로 부여되지는 않습니다.
A2A 1.0은 Agent Card의 supportedInterfaces에 전송·프로토콜 버전을 기술하고 요청의 A2A-Version으로 의미를 선택합니다. 0.3의 kind 기반 메시지와 1.0의 표현을 섞지 말고 SDK·서버의 지원 버전을 확인합니다.
서비스 경계에는 인증 주체·사용자별 권한·작업 ID·취소·타임아웃·재시도 계약을 둡니다. 네트워크 재시도로 메시지가 다시 전달될 수 있으므로 변경 도구는 같은 작업 ID를 두 번 처리해도 효과가 중복되지 않도록 설계합니다. 메시지 버스의 저장만으로 정확히 한 번의 외부 효과가 보장되지는 않습니다.
다음 스케일링 코드는 배포 구조 예시입니다. 실제 프로토콜 연결에는 서버·클라이언트가 지원하는 A2A 버전과 전송을 함께 고정합니다.
스케일링 패턴
섹션 제목: “스케일링 패턴”수평 확장
섹션 제목: “수평 확장”동일한 에이전트를 여러 인스턴스로 실행합니다.
class AgentPool { private instances: BaseAgentService[] = [] private queue: AgentTask[] = [] private activeCount = 0
constructor( private factory: () => BaseAgentService, private maxInstances: number ) { // 초기 인스턴스 생성 for (let i = 0; i < Math.min(2, maxInstances); i++) { this.instances.push(factory()) } }
async submit(task: AgentTask): Promise<AgentResult> { if (this.activeCount < this.maxInstances) { return this.executeImmediate(task) } return this.enqueue(task) }
private async executeImmediate(task: AgentTask): Promise<AgentResult> { this.activeCount++ const agent = this.instances[this.activeCount - 1] ?? this.factory()
try { return await agent.handleWithTimeout(task) } finally { this.activeCount-- this.processQueue() } }
private enqueue(task: AgentTask): Promise<AgentResult> { return new Promise((resolve, reject) => { this.queue.push({ ...task, _resolve: resolve, _reject: reject } as AgentTask & { _resolve: (r: AgentResult) => void _reject: (e: Error) => void }) }) }
private processQueue(): void { const next = this.queue.shift() as (AgentTask & { _resolve?: (r: AgentResult) => void }) | undefined if (next && next._resolve) { this.executeImmediate(next).then(next._resolve) } }}분산 에이전트 시스템은 단일 에이전트의 컨텍스트 한계와 직렬 실행 제약을 극복합니다. 이벤트 드리븐 아키텍처로 에이전트 간 느슨한 결합을 유지하고, 오케스트레이터 패턴으로 복잡한 워크플로우를 관리하세요. 하지만 분산 시스템의 복잡성은 실제로 필요한 팀에게만 정당화됩니다. 단순함을 기본값으로 유지하세요.