스웜 패턴
가 위에서 지휘한다면, 은 옆에서 넘긴다. 감독자 없이 끼리 일을 주고받는다.
핵심 발상 — 감독자를 지운다
슈퍼바이저 패턴에는 중앙 라우터가 있다. 그게 병목이 되거나 단일 실패점이 된다. 은 중앙을 없애고, 대신 각자가 “이 일은 내 일이 아니야” 라고 판단해 다른 에게 직접 넘긴다.
스웜은 회사 조직도 대신 “옆자리에게 토스” 모델이다.
이 토스 행위가 곧 다.
토폴로지 비교
슈퍼바이저는 별 모양 — 중앙에 한 노드. 은 그래프 — 모든 가 잠재적으로 서로 호출 가능한 동등한 위치. 사이에 위계가 없다.
각 노드는 자신이 못 푸는 케이스를 어디로 넘길지만 안다. 전체 지도를 아는 건 없다.
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"],
)의 핵심은 “이미 한 일”의 명시적 인계다. 다음 는 이걸 보고 같은 부수효과를 일으키지 않는다. 를 함께 실으면 한 번 더 안전해진다.
스웜이 어울리는 곳
- 고객 응대 (Triage → Sales/Refund/Tech) — 한 사람의 발화 흐름.
- 게임형 NPC — 동등한 캐릭터들이 상호 호출.
- 작업이 본질적으로 직렬이고, 다음 단계를 현재 에이전트가 직접 결정하는 경우.
반면 한 질문에 여러 관점을 동시에 모아야 한다면 가 더 자연스럽다. 설계의 첫 결정이 토폴로지 선택인 이유다. 잘못 고르면 마다 어색한 일을 한다.
라이브러리 현황 노트
은 실험 라이브러리다. 프로덕션 권장은 아니다. 같은 아이디어를 프로덕션에 옮긴 후속이 OpenAI Agents SDK 다. 진영에선 핸드오프 패턴이 표준으로 굳어지는 중이다.
이름이 무엇이든, 를 함수 한 줄로 표현하는 미니멀리즘이 스웜의 정수다. 가 자기 함수만 보면 전체 그림이 작동한다.
다음 챕터
수평 를 봤다. 다음은 수직 트리 — 부모가 자식 팀을 위임하는 이다. 의 재귀 적용이라고도 볼 수 있다. 토폴로지의 세 번째 축을 다음 챕터에서 본다.