계층형 패턴

를 재귀적으로 적용하면 이 된다. CEO 아래 매니저, 매니저 아래 워커 — 회사 조직도를 그대로 옮긴 구조.


슈퍼바이저의 재귀

슈퍼바이저는 한 명이 디스패치한다. 은 그 슈퍼바이저 밑에 또 슈퍼바이저가 있다. 트리 구조다.

계층형 = 슈퍼바이저 × 깊이.

각 내부 노드는 이자 동시에 위쪽의 자식이다. 잎 노드만 순수 워커다. 이 팀 안에 팀을 품은 모양.


트리 한 장

다이어그램 로딩…

CEO 가 큰 목표를 받으면 — 자기가 직접 하지 않는다. 는 목표를 쪼개 매니저들에게 던지고, 매니저는 다시 잘게 쪼개 에게 넘긴다. 결과는 역방향으로 모아진다.

위에서 아래로 명령, 아래에서 위로 결과 — 이게 이다.


부모-자식 책임 분할

노드무엇을 하는가
루트목표를 받고 큰 도메인으로 쪼갠다
중간 ()도메인 내 서브태스크 생성·라우팅
잎 ()도구 호출·LLM 호출로 실제 작업

중요한 원칙 — 부모는 자식의 일을 직접 하지 않는다. 부모가 잎 일을 흉내내면 가 다시 시작된다. 의 분업이 무너지는 가장 흔한 실수다.


트리를 코드로

from dataclasses import dataclass, field

@dataclass
class Agent:
  name: str
  role: str
  children: list["Agent"] = field(default_factory=list)

# 조직도 만들기
backend = Agent(name="Backend", role="백엔드")
frontend = Agent(name="Frontend", role="프론트엔드")
eng_mgr  = Agent(name="EngMgr",  role="엔지니어링 매니저",
               children=[backend, frontend])
ceo      = Agent(name="CEO",     role="CEO", children=[eng_mgr])

데이터 구조만 보면 그냥 트리다. 특별한 라이브러리가 필요 없다 — 의 본질은 안의 팀이고, 그 표현은 일반 트리로 충분하다. 가 자식 목록을 들고 있는 것만으로 시작이다.


재귀 실행

async def run(self, task: str) -> str:
  if self.children:
      subtasks = await self._plan(task)
      results = await asyncio.gather(*[
          c.run(sub) for c, sub in zip(self.children, subtasks)
      ])
      return await self._merge(task, results)
  # 잎: 실제 LLM 호출
  r = await client.messages.create(
      model="claude-sonnet-4-6", max_tokens=400,
      system=f"너는 {self.role} 담당이다.",
      messages=[{"role": "user", "content": task}],
  )
  return r.content[0].text

같은 run 함수가 자기 자신을 호출한다. 의 재귀 적용이라는 말이 코드로 보인다. 는 자식의 run 만 부를 줄 알면 된다.


깊이의 비용

깊이LLM 호출 수 (각 노드 fan-out=3)누적 지연
1 (잎)1t
21 + 3 = 42t
31 + 3 + 9 = 133t
41 + 3 + 9 + 27 = 404t

깊이 4면 호출 수가 40번이다. 비용은 기하급수, 지연은 깊이에 선형. 은 강력하지만 그만큼 무겁다. 설계에서 깊이는 마지막 손잡이다. 을 평탄화할 수 있다면 그게 보통 정답.


에스컬레이션

자식이 실패하면 어떻게 되는가. 그냥 에러 던지고 끝내면 트리 전체가 무너진다. 은 “내가 못 푼 일을 위에 올린다”는 절차다.

다이어그램 로딩…

이 신뢰도 낮은 답을 내면 가 받아 재계획한다. 도 못 풀면 사람에게 — 이게 와 합쳐지는 지점이다.


에스컬레이션 정책 코드

@dataclass
class EscalationPolicy:
  max_retries: int = 2
  confidence_floor: float = 0.6
  escalate_keywords: tuple = ("법적", "환불", "보안 침해")

  def should_escalate(self, reason: str, retries: int,
                      confidence: float) -> bool:
      if retries >= self.max_retries: return True
      if confidence < self.confidence_floor: return True
      return any(k in reason for k in self.escalate_keywords)

정책은 데이터로. 룰을 코드 곳곳에 흩어 놓지 말고 한 객체로 모은다. 사람 검토자가 한 파일만 보고 트리의 정책을 이해할 수 있어야 한다. 마다 다른 정책을 줄 수도 있다.


프레임워크 매핑

프레임워크표현 방법
Process.hierarchical — 매니저 에이전트 자동 생성
로 트리 표현
AutoGenGroupChat 안의 GroupChat 중첩

이름은 다르지만 자료구조는 같다 — 노드와 자식 리스트. 의 본질이 자료구조에 있다는 증거.


언제 계층형을 쓰는가

반대로 — 작업이 본질적으로 직렬이거나, 한 명이 끝까지 보는 게 자연스럽다면 이나 그냥 면 충분하다. 은 깊이를 살 만큼의 복잡도가 있을 때만.


계층형의 함정

  1. 추상화 비용 — 매니저 노드가 정작 아무 부가가치 없이 자식 결과를 그대로 패스스루.
  2. — 트리를 따라 정보가 위로 흐르며 압축·요약이 반복되면 디테일이 소실.
  3. 루프 — 부모가 자식에게 다시 던지는 무한 핑퐁.
  4. 의 깊이 = 디버깅 난이도 — 어느 레벨에서 잘못됐는지 찾기 어렵다. 마다 로그를 남기는 게 필수.

다음 챕터

을 봤다. 명령이 위에서 아래로 흐른다. 이제 같은 레벨끼리 서로 비평하고 합의하는 패턴 — 토론과 반성이다. 위계가 아닌 평행 검토로 정답률을 끌어올리는 길. 의 또 다른 정확도 손잡이를 다음 챕터에서 본다.