
Strands에서의 Swarm: 창발적 핸드오프(handoffs)를 통한 자율적 오케스트레이션
요약
Strands 프레임워크를 활용하여 여러 전문 에이전트가 자율적으로 협업하는 Swarm(군집) 오케스트레이션 패턴을 소개합니다. 공유된 컨텍스트와 작업 메모리를 통해 에이전트 간의 피드백 루프와 창발적 추론을 구현하는 방법을 게임 디자인 문서 생성 사례로 설명합니다.
핵심 포인트
- Swarm은 공유 컨텍스트를 통해 에이전트 간 자율적 조율을 가능하게 함
- 순차적/계층적 구조와 달리 에이전트 간 협상과 피드백 루프 지원
- 단계 순서가 불명확하거나 수정/되돌림이 필요한 복잡한 작업에 적합
- Gemma4 및 Gemini API를 활용한 로컬 및 API 실행 환경 테스트
Strands에서의 멀티 에이전트 오케스트레이션(multi-agent orchestration)에 관한 시리즈 기사의 연장선상에서, 이번 노트에서는 다섯 명의 전문 에이전트가 각각 비디오 게임의 서로 다른 차원을 설계하는 게임화된 예시를 통해 Swarm(군집)을 설명하고자 합니다. Gemma4 + Ollama 및 Gemini API를 사용하여 두 가지 방식으로 테스트되었습니다.
서론
Strands Agents 프레임워크를 사용하여 에이전트를 개발하고 이해하려면 기초 과정을 수강하거나 문서(내용이 방대합니다)를 읽어보실 것을 제안합니다. 기초는 중요합니다! 설계의 모든 책임을 AI에 위임하기를 기대하기보다, 우리 에이전트에게 어떻게 복잡성을 부여할지 아는 것이 필수적입니다 (관련 리소스는 이 노트 하단에 있습니다).
개념 증명(PoC)을 위해 결과를 분석할 수 있는 두 가지 로컬 실행 옵션을 사용했습니다:
- Docker에서 실행되는 Ollama + 소형 모델 gemma4:e2b-it-qat (용량 4GB 이상)
- Gemini API 사용. API 키는 무료로 생성할 수 있으며 "Gemini 2.5 Flash" 모델을 사용할 수 있습니다.
🎮 **Swarm 패턴을 개발하기 위해 채택한 유스케이스(use case)**는 "저주받은 성에서의 요리 로그라이크(roguelike)..."라는 전제로부터 게임 디자인 문서(GDD)를 생성하는 것입니다.
다섯 명의 전문 에이전트가 참여하며, 각 에이전트는 비디오 게임 설계의 서로 다른 차원인 메카닉(mechanics), 내러티브(narrative), 레벨(levels), 로어(lore; 이야기, 신화, 규칙, 과거 데이터) 및 게임 경험(game experience)을 담당합니다. 이들은 모두 협력하여 단순한 전제를 일관된 **게임 디자인 문서(GDD)**로 변환합니다.
선형적인 파이프라인(pipeline)과 달리, 에이전트들은 자신의 결정 사이의 마찰(내러티브와 모순되는 메카닉, 밸런스를 깨뜨리는 로어 규칙 등)을 감지할 수 있으며, 합의된 결과에 도달할 때까지 해당 에이전트와 대화를 다시 시작할 수 있습니다.
핵심 개념
➡ Swarm은 협업 에이전트 오케스트레이션 (orchestration) 패턴으로, 여러 에이전트가 복잡한 작업을 해결하기 위해 하나의 팀처럼 함께 작동합니다. 순차적(sequential)이거나 계층적(hierarchical)인 기존의 멀티에이전트 (multi-agent) 시스템과 달리, Swarm은 **공유된 컨텍스트 (context) 및 작업 메모리 (working memory)**를 통해 에이전트 간의 자율적인 조율을 가능하게 합니다.
Swarm은 다음과 같은 경우에 적합합니다:
- 단계의 순서를 미리 정의할 수 없는 경우: 생성되는 콘텐츠에 따라 어떤 에이전트가 개입해야 할지 미리 알 수 없을 때.
- 수정이나 되돌림(backtracking)이 발생할 수 있는 경우: 문제 해결을 위해 전문가들 사이의 피드백 루프(cycles of ida y vuelta)가 필요한 경우 (예: 한 에이전트의 결정이 다른 에이전트의 작업을 무효화하는 경우).
- 상충하는 다차원적 전문 지식이 존재하는 경우: 각 에이전트가 서로 다른 관점을 고수하며, 에이전트들이 고립되어 일하는 것이 아니라 서로 협상하는 과정 자체에서 가치가 창출될 때.
- 창발적 집단 추론 (emergent collective reasoning)이 필요한 경우: 최종 솔루션이 단일 에이전트(또는 인간)가 상위에서 모든 것을 조율하는 것이 아니라, 에이전트 간의 상호작용을 통해 나타날 때.
❗ 워크플로우가 고정되어 있고 예측 가능한 경우에는 이상적인 패턴이 아닙니다.
➡ 메모리 / 컨텍스트 (Memory / Context)
에이전트들은 공유된 작업 메모리와 전체 컨텍스트를 보유합니다. Swarm이 실행될 때, 전체 메시지 이력(이전 에이전트들의 제안, 반론 및 추론)은 제어권을 넘겨받는 새로운 에이전트에게 전달되는 핸드오프 (handoff) 과정을 거칩니다.
핸드오프 페이로드 (Handoff Payload): 누적된 이력 외에도, 에이전트가 핸드오프 도구(tool)를 실행할 때 왜 작업을 넘기는지, 그리고 상대방이 무엇을 수행하기를 기대하는지를 설명하는 명시적인 메시지나 노트(message / context)를 첨부할 수 있습니다.
➡ Nodos vs. Agentes
Swarm에서 각 에이전트(agent)는 Agent를 감싸는 "노드"(SwarmNode)로 등록됩니다. 이러한 구분은 중요합니다. 왜냐하면 스웜(swarm)은 "누가 누구인가"가 아니라 "어떤 노드가 다음에 실행되는가"의 관점에서 추론하기 때문입니다. 최종 결과물은 이를 node_history로 노출하며, 이는 대화가 아닌 실행된 노드들의 리스트입니다. 이를 통해 사전에 설계하지 않고도 실제로 실행된 실제 토폴로지(topology)를 나중에 재구성할 수 있습니다.
➡ 핸드오프(Handoff) 메커니즘은 마법 같은 컨텍스트 전환이 아닌 툴 호출(tool-calling)입니다
handoff_to_agent는 LLM 패러다임 외부의 프레임워크 특수 함수가 아니라는 점을 명시적으로 밝힐 가치가 있습니다. 이는 Strands에 의해 스웜의 각 에이전트에 자동으로 주입되는 또 하나의 툴(tool)이며, 다른 모든 툴과 동일한 함수 호출(function-calling) 메커니즘을 따릅니다. 그 결과, Swarm 패턴의 신뢰성은 기반 모델이 툴 호출(tool-calling)을 얼마나 잘 수행하느냐에 전적으로 달려 있습니다. 이는 Strands의 한계가 아니라 패턴의 구조적 의존성입니다. 따라서 이 글과 함께 유스케이스(use case)를 두 가지 방식으로 테스트했습니다: 소형 모델인 _Gemma4_와 Gemini 2.5 Flash 모델을 사용했습니다.
➡ 창발적 토폴로지 vs. 사전 정의된 토폴로지 ("왜 Swarm인가"에 대한 핵심 논거)
Swarm에서 토폴로지(topology)는 입력값이 아니라 결과물입니다. 이는 실행 시점에 비로소 발견되며, 동일한 전제 조건에서도 실행마다 달라질 수 있습니다 (예: 5개의 에이전트가 있지만 4개만 개입하는 경우). 이는 적응성 측면에서의 장점인 동시에 비결정론적(non-deterministic)인 위험 요소이기도 합니다.
스웜이 어디서 시작되는지는 지정해야 하며, 각 결정 지점은 누적된 컨텍스트(context)를 바탕으로 에이전트 스스로가 결정합니다. 다음은 유스케이스의 예시 토폴로지입니다:
mechanic_designer
↓
level_architect ←──────────┐
...
➡ 설계의 일부로서의 가드레일 (Guardrails)
max_handoffs: 스웜(swarm) 전체에서 에이전트 간에 허용되는 최대 전송 횟수입니다. 이는 강제 종료를 발생시키기 전까지의 모든 전송을 제한하는 것입니다. 완전한 실행 과정의 '단계'에 대한 글로벌 한계치입니다.
max_iterations: 허용되는 스웜 반복(노드 실행)의 최대 횟수입니다. 실제로는 max_handoffs와 함께 작동하여 통제되지 않는 실행으로부터 두 번째 안전망 역할을 합니다.
execution_timeout: 모든 에이전트와 전송을 합산한, 완전한 스웜 실행이 지속될 수 있는 총 시간(초)입니다. 이 시간을 초과하면, 한 턴 도중에 있더라도 Status.FAILED로 스웜이 종료됩니다.
node_timeout: 개별 에이전트가 단일 턴(모델 호출 및 응답 포함)에서 소요할 수 있는 최대 시간(초)입니다. 특정 노드가 정지하여 전체 글로벌 제한에 영향을 미치는 것을 방지합니다.
repetitive_handoff_detection_window: Strands가 반복적인 패턴을 감지하기 위해 분석하는 '창(window)'의 크기(최근 전송 횟수)입니다. 값이 6이면, 루프 여부를 결정하기 위해 최근 6개의 전송을 살펴봅니다.
min_unique_agents: 해당 창 내에서 흐름이
➡ 프롬프트 설계의 중요성
Strands는 핸드오프(handoff) 메커니즘(도구(tool), 훅(hook), 가드레일(guardrails))을 제공하지만, 이를 언제 사용할지는 결정하지 않습니다. 그 결정은 각 에이전트의 시스템 프롬프트(system prompt)에 달려 있습니다. 이 유스케이스(use case)에서 5개의 프롬프트 각각은 세 가지 사항을 명시적으로 정의합니다: 에이전트의 역할 및 평가 기준, 누구에게 어떤 조건 하에 제어권을 넘겨야 하는지, 그리고 문제가 해결된 후 동일한 이의를 다시 제기하는 것을 방지하기 위한 수렴 규칙("Convergence rule")입니다.
마지막 규칙(수렴 규칙)은 반드시 작성해야 했습니다. 초기 실행 시 일부 에이전트들이 동일한 이견을 서로 다른 단어로 여러 번 다시 제기하여, 가드레일(guardrails)이나 훅(hook)조차 정당한 협상과 구분할 수 없는 비생산적인 사이클(cycle)을 생성했기 때문입니다. 해결책은 프롬프트(prompt)를 통해, 새로운 사유가 나타나지 않는 한 첫 번째 회차 이후에는 각 에이전트가 수정을 수용하도록 명시적으로 지시하는 것이었습니다. 가드레일(guardrails)이 구조적 루프(structural loops)를 방지한다면, 이것은 의미론적 루프(semantic loops)를 해결합니다.
➡ 선택한 모델의 한계를 완화하기 위한 확장 지점으로서의 훅(Hooks)
Strands는 훅(hooks)을 통해 에이전트의 실행 사이클에 연결되어, 도구 호출 성공과 같은 이벤트를 가로챌 수 있습니다. 이번 유스케이스(use case)에서는 성공적인 핸드오프(handoff) 직후에 에이전트의 턴을 즉시 중단하도록 강제하는 StopAfterHandoffHook을 구현했습니다.
이 메커니즘은 프롬프트(prompt)만으로 동작을 보장하기 어려울 때 유용하며, 모델이 한 턴에 핸드오프(handoff) 도구를 두 번 이상 호출하는 것을 방지합니다. 이는 프롬프트 엔지니어링(prompt engineering)에 의존하지 않고 코드 수준에서 이루어지는 결정론적인(deterministic) 개입입니다.
➡ 평가 (Evaluaciones (Evals))
재사용 가능한 평가기(출력 기반, 도구 호출(tool-calls) 경로 기반, 실행 트레이스(execution traces) 기반 등)를 제공하는 strands-agents-evals 패키지가 존재합니다. 이 유스케이스(use case)에서는 두 가지 평가기를 구현했습니다:
- 경로 평가 (Trajectory Evaluation). 정확한 핸드오프(handoffs) 시퀀스를 요구하는 대신, 실행의 구조적 속성을 검증합니다.
- 진입점(entry point)이 올바르게 설정되었는지 확인합니다.
- 참여한 고유 에이전트의 수가 최소 기준을 충족하는지, 그리고 필수적인 에이전트(예:
mechanic_designer)가 개입했는지 확인합니다.
- 출력 평가 (Output Evaluation). 생성된 콘텐츠가 원래의 전제와 관련이 있는지 확인하는 기본적인 체크로서, 최종 GDD(Game Design Document)에 게임 도메인의 예상 키워드(예: "curse", "ingredient", "castle")가 포함되어 있는지 검증합니다.
⭐ 이러한 접근 방식은 에이전트가 정확한 결과(올바른 에이전트에게 위임되었는지 여부)를 내놓는지 평가할 수 있는 '도구로서의 에이전트(agents-as-tools)'와 같은 더 결정론적인(deterministic) 패턴과의 핵심적인 차이점을 반영합니다. 반면, Swarm에서는 고정된 시퀀스가 아니라 행동의 **형태(forma)**를 평가해야 합니다.
솔루션 설명
📦 GitHub 리포지토리: github.com/reinalau/strands-swarm
앞서 언급했듯이, 실행 및 행동 분석을 위해 두 가지 모델 옵션을 사용했지만, 그중 하나만 사용할 수도 있습니다:
a. Docker Desktop을 사용한 후, Ollama 이미지를 다운로드하고 gemma4:e2b-it-qat(용량 4GB 이상)를 설치합니다. 모든 Gemma 모델은 여기에서 찾을 수 있습니다.
# 영구 볼륨(persistent volume)과 함께 Ollama 서버 시작
docker run -d --name ollama -p 11434:11434 -v ollama_data:/root/.ollama ollama/ollama
...
b. 여기에서 무료 계층의 Gemini API 키를 생성했습니다. 이를 통해 Gemini 2.5 Flash 모델을 사용할 수 있습니다.
멀티에이전트 (multi-agent) 코드는 Python으로 작성되었으며, 프로젝트 구조는 로직을 더 설명하기 쉽게 설계되었습니다:
strands-swarm/
├── README.md
├── requirements.txt
...
로컬 실행 (Local Execution)
소스 코드 저장소를 클론하고, gemma 4 모델이 포함된 ollama Docker를 준비하거나 Gemini API 키를 생성했다면, 이제 환경을 구축합니다.
requirements.txt에서 확인할 요구 사항은 다음과 같습니다:
strands-agents[ollama]
strands-agents[gemini]
strands-agents-evals
python-dotenv
pytest
pip install -r requirements.txt
.env 파일에는 다음 설정이 필요합니다:
OLLAMA_HOST=http://localhost:11434
MODEL_NAME=gemma4:e2b-it-qat
MODEL_PROVIDER=gemini # ollama
GEMINI_API_KEY=tuapikeyOpcional
GEMINI_MODEL_NAME=gemini-2.5-flash
LOG_LEVEL=INFO
MODEL_TEMPERATURE=0.5
MODEL_MAX_TOKENS=3500
MODEL_NUM_CTX=4096
MAX_HANDOFFS=12
MAX_ITERATIONS=12
EXECUTION_TIMEOUT=5000
NODE_TIMEOUT=1500
REPETITIVE_HANDOFF_DETECTION_WINDOW=6
REPETITIVE_HANDOFF_MIN_UNIQUE_AGENTS=3
ENTRY_POINT_AGENT=mechanic_designer
LOG_DIR=logs
OUTPUT_DIR=outputs
cp .env.example .env
**테스트 (Tests)**는 2단계 접근 방식(tests/)으로 구성되어 있습니다:
1단계 (빠르고 결정론적인 테스트): 실제 모델 호출을 수행하지 않고 스웜 (swarm) 구조, 노드/제한 설정, 텍스트 추출 및 GDD (Game Design Document) 통합을 검증합니다. "Cooking roguelike in a cursed castle." 또는 "Test Premise"와 같은 시뮬레이션된 문자열을 사용합니다.
python -m pytest
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기