스웜 패턴

가 위에서 지휘한다면, 은 옆에서 넘긴다. 감독자 없이 끼리 일을 주고받는다.


핵심 발상 — 감독자를 지운다

슈퍼바이저 패턴에는 중앙 라우터가 있다. 그게 병목이 되거나 단일 실패점이 된다. 은 중앙을 없애고, 대신 각자가 “이 일은 내 일이 아니야” 라고 판단해 다른 에게 직접 넘긴다.

스웜은 회사 조직도 대신 “옆자리에게 토스” 모델이다.

이 토스 행위가 곧 다.


토폴로지 비교

다이어그램 로딩…

슈퍼바이저는 별 모양 — 중앙에 한 노드. 은 그래프 — 모든 가 잠재적으로 서로 호출 가능한 동등한 위치. 사이에 위계가 없다.

각 노드는 자신이 못 푸는 케이스를 어디로 넘길지만 안다. 전체 지도를 아는 건 없다.


OpenAI Swarm — 핸드오프 = 함수 반환값

OpenAI가 실험적으로 공개한 라이브러리의 핵심 트릭. 는 별도 메커니즘이 아니라 “도구가 다음 에이전트를 반환한다”는 관용구다.

from swarm import Swarm, Agent

def transfer_to_sales():
  return sales_agent  # 함수 반환값이 Agent → 다음 턴부터 sales 가 응대

triage_agent = Agent(
  name="Triage",
  instructions="판매 문의는 transfer_to_sales 를 호출한다.",
  functions=[transfer_to_sales],
)
sales_agent = Agent(name="Sales", instructions="판매 문의에 답한다.")

함수 반환값이 면, 런타임이 active agent 를 그 값으로 교체한다. 가 단 한 줄의 return 으로 표현된다.


핸드오프의 흐름

다이어그램 로딩…

사용자 입장에선 응답이 그냥 도착한다. 누가 답했는지는 알 필요 없다 — 이게 의 정의다. 백엔드에선 가 한 차례 바뀌었지만, 는 사용자에게 노출되지 않는다.


실제 실행

from swarm import Swarm
client = Swarm()
resp = client.run(
  agent=triage_agent,
  messages=[{"role": "user", "content": "결제 환불 가능한가요?"}],
)
print(resp.agent.name, resp.messages[-1]["content"])
# → "Refund Agent" "환불 절차는 ..."

client.run() 은 메시지·도구·를 통합해 한 사이클을 돈다. 마지막에 응답한 resp.agent 에 담긴다. 다음 턴에 같은 객체로 호출하면 대화가 이어진다. 가 자연스럽게 이어지는 이유다.


context_variables — 핸드오프 너머의 상태

핸드오프 직후, 다음 에이전트는 이전 대화 메시지를 그대로 본다. 하지만 구조화된 상태(고객 ID·세션 토큰 등)는 별도 채널이 필요하다. Swarm 은 context_variables 딕셔너리를 모든 에이전트에 같이 넘긴다.

def instructions(context_variables):
  name = context_variables.get("user_name", "고객")
  return f"너는 {name} 님을 응대하는 에이전트다."

agent = Agent(name="Sales", instructions=instructions)
client.run(agent=agent, messages=[...],
         context_variables={"user_name": "지수"})

instructions 를 함수로 주면 가 동적으로 결정된다. 끼리 상태를 공유하는 표준 통로다. 를 거쳐도 ctx 는 사라지지 않는다.


슈퍼바이저 vs 스웜

토폴로지별 모양그래프
라우팅 주체중앙 1명각 에이전트 자신
동시 실행자연스러움 (팬아웃)어렵다 (한 명씩 발화)
단일 실패점슈퍼바이저없음
디버깅쉬움 (선형)어려움 (분기 폭발)

스웜의 자유에는 그만큼의 비용이 있다. 한 가지 일을 끝까지 보는 가 없기 때문이다.


역할 전이의 위험 — 컨텍스트가 따라간다

핸드오프 후에도 이전 대화 메시지는 그대로 남는다. 즉 새 에이전트도 이전 시스템 프롬프트의 흔적을 본다. 이 일어나기 쉽다.

# 안티 패턴 — sales 에이전트가 refund 답을 흉내내기 시작
# 이유: 메시지 히스토리에 refund 톤이 누적되어 있음
# 완화: 핸드오프 시 컨텍스트 윈도 슬라이스
def transfer_to_refunds(messages):
  # 최근 user 메시지 1개만 넘긴다
  last_user = next(m for m in reversed(messages) if m["role"] == "user")
  return refund_agent, [last_user]

가 자연스러우려면 컨텍스트도 같이 다듬어야 한다. 마다 깨끗한 상태에서 출발하지 않으면 일관성이 깨진다. 후의 첫 발화 품질이 통째로 흔들린다.


원자적 핸드오프 — 끊김 없는 전환

이중 토스가 일어나면 어떻게 될까. 외부 시스템에 부수효과가 이미 일어났는데, 직후 새 에이전트가 그 사실을 모르고 같은 일을 다시 한다.

# 원자적 핸드오프 = 핸드오프 자체를 트랜잭션처럼 다룬다
class HandoffEnvelope:
  def __init__(self, next_agent, completed_actions, ctx):
      self.next_agent = next_agent
      self.completed_actions = completed_actions  # 이미 한 일 목록
      self.ctx = ctx

def transfer_to_refunds(state):
  return HandoffEnvelope(
      next_agent=refund_agent,
      completed_actions=state["actions"],  # 다음 에이전트가 보고 중복 회피
      ctx=state["ctx"],
  )

의 핵심은 “이미 한 일”의 명시적 인계다. 다음 는 이걸 보고 같은 부수효과를 일으키지 않는다. 를 함께 실으면 한 번 더 안전해진다.


스웜이 어울리는 곳

반면 한 질문에 여러 관점을 동시에 모아야 한다면 가 더 자연스럽다. 설계의 첫 결정이 토폴로지 선택인 이유다. 잘못 고르면 마다 어색한 일을 한다.


라이브러리 현황 노트

은 실험 라이브러리다. 프로덕션 권장은 아니다. 같은 아이디어를 프로덕션에 옮긴 후속이 OpenAI Agents SDK 다. 진영에선 핸드오프 패턴이 표준으로 굳어지는 중이다.

이름이 무엇이든, 를 함수 한 줄로 표현하는 미니멀리즘이 스웜의 정수다. 가 자기 함수만 보면 전체 그림이 작동한다.


다음 챕터

수평 를 봤다. 다음은 수직 트리 — 부모가 자식 팀을 위임하는 이다. 의 재귀 적용이라고도 볼 수 있다. 토폴로지의 세 번째 축을 다음 챕터에서 본다.