Community코딩 & 개발github.com

multi-agent-orchestrator

다중 에이전트 오케스트레이션 시스템 설계 도우미입니다. 에이전트 협업, 작업 할당, 메시지 라우팅, 상태 관리를 설계합니다.

multi-agent-orchestrator란 무엇인가요?

이 스킬은 다중 에이전트 협업 시스템을 설계하고 관리하기 위한 포괄적인 오케스트레이션 프레임워크를 제공합니다. SKY-lv/skylv-multi-agent-orchestrator는 작업 분해, 유향 비순환 그래프(DAG) 기반 스케줄링, 메시지 버스를 통한 에이전트 간 통신, 상태 머신을 통한 에이전트 상태 관리를 지원합니다. TypeScript로 구현된 이 스킬은 BaseAgent, Orchestrator, MessageBus, StateManager와 같은 핵심 구성 요소를 제공하여 개발자가 복잡한 다중 에이전트 시스템을 쉽게 구축할 수 있도록 합니다. 역할극(Role Play)과 토론(Debate)과 같은 일반적인 협업 패턴을 지원하며, OpenAI API와 같은 LLM을 활용하여 에이전트의 추론과 작업 수행을 가능하게 합니다. OpenClaw에서 실행되며, 단일 책임 원칙, 느슨한 결합, 시간 초과 제어, 회로 차단기, 관찰 가능성과 같은 모범 사례를 따릅니다.

지원 대상~Claude Code✓Codex CLI~Cursor
npx skills add SKY-lv/skylv-multi-agent-orchestrator

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

멀티 에이전트 오케스트레이터

기능 설명

다중 에이전트 협업 시스템을 설계하고 관리합니다.

아키텍처 패턴

┌─────────────┐
│ Orchestrator │ ← 작업 분해, 조정
└──────┬──────┘
       │
   ┌───┼───┐
   ▼   ▼   ▼
 ┌───┐┌───┐┌───┐
 │ A ││ B ││ C │ ← 전문 에이전트
 └───┘└───┘└───┘

핵심 구현

1. 에이전트 기본 클래스

interface AgentConfig {
  name: string;
  role: string;
  capabilities: string[];
  llm: LLMConfig;
  tools: Tool[];
  instructions: string;
}

class BaseAgent {
  protected config: AgentConfig;
  protected memory: AgentMemory;
  
  constructor(config: AgentConfig) {
    this.config = config;
    this.memory = new AgentMemory(config.name);
  }
  
  async think(task: Task): Promise<Response> {
    const context = await this.memory.buildContext(task.description);
    const prompt = this.buildPrompt(task, context);
    const response = await this.callLLM(prompt);
    await this.memory.add({ type: 'semantic', content: task.description + ' -> ' + response.content, importance: 8 });
    return response;
  }
  
  protected buildPrompt(task: Task, context: string): Message[] {
    return [
      { role: 'system', content: this.config.instructions },
      { role: 'system', content: context },
      { role: 'user', content: task.description }
    ];
  }
  
  protected async callLLM(messages: Message[]): Promise<Response> {
    const res = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` },
      body: JSON.stringify({ model: this.config.llm.model, messages, tools: this.config.tools.map(t => t.definition) })
    });
    return res.json();
  }
}

2. 오케스트레이터

interface TaskResult {
  agentId: string;
  status: 'pending' | 'running' | 'done' | 'failed';
  output?: string;
  dependencies: string[];
  startTime?: number;
  endTime?: number;
}

class Orchestrator {
  private agents: Map<string, BaseAgent> = new Map();
  private taskGraph: DAG<Task>;
  
  constructor(private llmRouter: LLMRouter) {}
  
  registerAgent(agent: BaseAgent) {
    this.agents.set(agent.config.name, agent);
  }
  
  async execute(goal: string): Promise<string> {
    // 1. 작업 분해
    const plan = await this.decompose(goal);
    
    // 2. DAG 구축
    this.taskGraph = this.buildDAG(plan);
    
    // 3. 실행 스케줄링
    const results = await this.schedule();
    
    // 4. 결과 취합
    return this.summarize(goal, results);
  }
  
  private async decompose(goal: string): Promise<Task[]> {
    const response = await this.llmRouter.route({
      prompt: `다음 목표를 실행 가능한 하위 작업으로 분해하고 JSON 배열로 반환하세요:
      
목표: ${goal}

요구사항:
- 각 하위 작업은 하나의 에이전트만 담당합니다.
- 작업 간 의존 관계를 명시합니다.
- 반환 형식: [{"id":"t1","description":"...","agent":"researcher","depends":[]},...]`,
      system: '당신은 작업 분해 전문가입니다.'
    });
    
    return JSON.parse(response.content);
  }
  
  private async schedule(): Promise<Map<string, TaskResult>> {
    const results = new Map<string, TaskResult>();
    const pending = new Set(this.taskGraph.nodes);
    const running: Promise<void>[] = [];
    const maxConcurrent = 3;
    
    while (pending.size > 0 || running.length > 0) {
      // 병렬 실행 가능한 작업 시작
      while (running.length < maxConcurrent) {
        const next = this.findNextRunnable(pending, results);
        if (!next) break;
        
        pending.delete(next.id);
        const p = this.runTask(next, results).catch(console.error);
        running.push(p);
      }
      
      // 하나가 완료될 때까지 대기
      await Promise.race(running);
      running.splice(running.findIndex(p => false), 1);
    }
    
    return results;
  }
  
  private async runTask(task: Task, results: Map<string, TaskResult>) {
    results.set(task.id, { agentId: task.agent, status: 'running', dependencies: task.depends || [], startTime: Date.now() });
    
    try {
      // 의존 작업 완료 대기
      for (const depId of task.depends || []) {
        const dep = results.get(depId);
        if (dep?.status !== 'done') {
          await this.waitFor(depId, results);
        }
      }
      
      const agent = this.agents.get(task.agent);
      const context = this.buildContext(task, results);
      const response = await agent.think({ id: task.id, description: task.description, context });
      
      results.set(task.id, { ...results.get(task.id)!, status: 'done', output: response.content, endTime: Date.now() });
    } catch (error) {
      results.set(task.id, { ...results.get(task.id)!, status: 'failed', output: String(error), endTime: Date.now() });
    }
  }
  
  private buildContext(task: Task, results: Map<string, TaskResult>): string {
    return (task.depends || []).map(depId => {
      const dep = results.get(depId);
      return dep?.output || '';
    }).join('\n\n');
  }
}

3. 메시지 버스

class MessageBus {
  private subscriptions = new Map<string, Subscriber[]>();
  
  publish(channel: string, message: Message) {
    const subs = this.subscriptions.get(channel) || [];
    for (const sub of subs) {
      sub.handler(message);
    }
  }
  
  subscribe(channel: string, handler: (msg: Message) => void): () => void {
    if (!this.subscriptions.has(channel)) {
      this.subscriptions.set(channel, []);
    }
    const sub = { id: crypto.randomUUID(), handler };
    this.subscriptions.get(channel)!.push(sub);
    return () => this.unsubscribe(channel, sub.id);
  }
  
  unsubscribe(channel: string, subId: string) {
    const subs = this.subscriptions.get(channel) || [];
    const idx = subs.findIndex(s => s.id === subId);
    if (idx >= 0) subs.splice(idx, 1);
  }
}

// 메시지 유형
interface Message {
  id: string;
  type: 'request' | 'response' | 'broadcast' | 'event';
  from: string;
  to?: string;
  content: any;
  timestamp: number;
}

4. 상태 머신

type AgentState = 'idle' | 'thinking' | 'waiting' | 'acting' | 'error';

interface AgentSession {
  id: string;
  agentId: string;
  state: AgentState;
  currentTask?: string;
  history: Turn[];
  sharedContext: Record<string, any>;
}

class StateManager {
  private sessions = new Map<string, AgentSession>();
  
  transition(sessionId: string, newState: AgentState) {
    const session = this.sessions.get(sessionId);
    if (!session) return;
    
    const oldState = session.state;
    session.state = newState;
    
    // 상태 전환 훅
    this.onTransition(sessionId, oldState, newState);
  }
  
  // 상태 전환 규칙
  private canTransition(from: AgentState, to: AgentState): boolean {
    const rules: Record<AgentState, AgentState[]> = {
      idle: ['thinking'],
      thinking: ['waiting', 'acting', 'error', 'idle'],
      waiting: ['thinking', 'error', 'idle'],
      acting: ['thinking', 'error', 'idle'],
      error: ['idle', 'thinking']
    };
    return rules[from]?.includes(to) || false;
  }
}

통신 패턴

패턴설명적용 사례
브로드캐스트모든 에이전트가 수신전역 알림
점대점지정된 에이전트가 수신작업 할당
발행/구독주제별 배포이벤트 기반
블랙보드공유 지식 공간협업 추론

일반적인 패턴

역할극 (Role Play)

class RolePlayOrchestrator extends Orchestrator {
  async execute(goal: string) {
    // 역할 할당
    const planner = this.getAgent('planner');
    const executor = this.getAgent('executor');
    const critic = this.getAgent('critic');
    
    const plan = await planner.think({ description: goal });
    const result = await executor.think({ description: plan.output, context: '' });
    const review = await critic.think({ description: result.output, context: '' });
    
    return review.output;
  }
}

토론 (Debate)

async debate(topic: string, rounds = 3) {
  const pro = this.getAgent('pro');
  const con = this.getAgent('con');
  const judge = this.getAgent('judge');
  
  let context = '';
  for (let i = 0; i < rounds; i++) {
    const proArg = await pro.think({ description: `찬성 측 주장 (${i+1}라운드): ${topic}`, context });
    context += `\n찬성: ${proArg.output}`;
    
    const conArg = await con.think({ description: `반대 측 주장 (${i+1}라운드): ${topic}`, context });
    context += `\n반대: ${conArg.output}`;
  }
  
  return judge.think({ description: `판결: ${topic}`, context });
}

모범 사례

  1. 단일 책임: 각 에이전트는 명확한 전문 영역을 가져야 합니다.
  2. 느슨한 결합: 메시지 버스를 통해 통신하여 직접적인 의존성을 피하세요.
  3. 타임아웃 제어: 에이전트가 멈추는 것을 방지합니다.
  4. 차단 메커니즘: 실패 횟수가 너무 많으면 자동으로 성능을 저하시킵니다.
  5. 관측 가능성: 완전한 로그와 추적을 제공합니다.

사용법

  1. 스킬을 설치하세요.
  2. 필요에 따라 설정하세요.
  3. OpenClaw로 실행하세요.

관련 스킬