AWS가 꽤 흥미로운 레퍼런스 아키텍처를 공개했다. [Strands Agents로 구현한 레스토랑 동적 가격 책정 솔루션](https://aws.amazon.com/blogs/industries/build-a-dynamic-pricing-solution-for-restaurants-using-agentic-ai-strands-agents/)이다. 오케스트레이터가 수요, 날씨, 지역 행사, 경쟁사 가격을 담당하는 전문 에이전트에게 작업을 나눠 준다. 각 에이전트는 MCP 서버를 통해 담당 데이터 소스와 통신하고, 상위 스웜은 결과를 모아 최종 가격을 정한다. 다이어그램은 군더더기 없이 깔끔하다. 화살표는 모두 앞으로 흐르고, 어디에서도 문제가 생기지 않는다.
하지만 저 구조를 실제로 운영하면 비용이 얼마나 들지 궁금했다. 그래서 같은 구조를 서핑 스쿨 예약 도우미에 적용한 뒤 Bedrock에서 33번 실행하고, 에이전트 사이의 모든 전달 과정을 로그로 남겼다.
예전에 쓴 [에이전트 하나로 부족할 때만 멀티 에이전트 시스템을 설계하라](/blog/2025-12-12-designing-robust-multi-agent-systems.html)를 읽었다면, 이 글은 실제 측정값으로 쓴 후속편이라고 보면 된다. “도입해야 하는가”보다 “직접 돌려 보니 이만큼 들었다”에 초점을 맞췄다.
## 같은 패턴, 서핑 스쿨로
서핑 스쿨과 레스토랑의 운영 구조는 의외로 닮았다. 수요가 계속 달라지고, 날씨 같은 외부 요인이 수요에 영향을 주며, 어떤 상황에서도 어겨서는 안 되는 제약이 있다. 레스토랑은 원가보다 싸게 팔 수 없고, 서핑 스쿨은 너울이나 돌풍이 위험한 날 초보자를 바다에 내보내서는 안 된다.
그래서 다섯 전문 에이전트의 역할을 다음과 같이 정했다.
```
orchestrator
└── swarm
conditions_agent → get_surf_conditions (Open-Meteo Marine: 스웰 높이/주기)
weather_agent → get_weather (Open-Meteo Forecast: 바람, 돌풍, 기온)
availability_agent → get_instructor_availability (로컬 샘플 데이터, DynamoDB 역할)
safety_agent → (도구 없음: 앞선 결과를 바탕으로 판단하고 안전상 거부권 행사)
pricing_agent → get_base_pricing (할증/할인 적용, 최저 가격 보장)
```
해양 상태와 날씨에는 실제 데이터를 사용했다. [Open-Meteo](https://open-meteo.com/)가 API 키 없이 너울과 바람 데이터를 제공하므로 `conditions_agent`와 `weather_agent`가 이 데이터를 직접 가져온다. 강사 일정과 기본 가격에는 로컬 JSON 샘플을 사용했다. AWS 사례에서 DynamoDB가 맡은 역할을 대신하는 데이터다. 안전 규칙은 `safety_agent`의 프롬프트에 적고 `pricing_agent`의 프롬프트에도 한 번 더 명시했다. 가격을 높이거나 낮출 유인이 안전 판단을 뒤집지 못하게 하려는 장치다. AWS 사례와 마찬가지로 “정책을 프롬프트에 넣는다”는 전제를 따른 셈이다.
Strands 구성은 AWS 예제와 거의 같다.
```python
from strands import Agent, tool
from strands.models import BedrockModel
from strands.multiagent import Swarm
swarm = Swarm(
build_specialists(), # 위의 5개 agent, 순서대로
max_handoffs=6,
max_iterations=6,
execution_timeout=300.0,
node_timeout=90.0,
)
result = swarm(f"Recommend surf lesson slots and pricing for {location} on {day}. lat={lat} lon={lon}")
```
여기서 작업 전달은 코드로 정해져 있지 않다. 각 전문 에이전트의 프롬프트 끝에 “다음에는 `next_agent`로 넘겨라”라고 적어 두면, 모델이 `handoff_to_agent` 도구를 호출할지 스스로 판단한다. 이 차이가 중요하다. 다이어그램과 실제 동작이 어긋나기 시작하는 지점이기 때문이다.
실행 결과를 공정하게 비교하려고 Open-Meteo의 실제 스냅샷 하나를 고정했다. 이 스냅샷을 바탕으로 높은 너울, 강한 돌풍, 춥고 잔잔한 날, 초보자 안전 기준에 걸친 날, 특정 시각의 관측값이 빠진 경우 등 11가지 스트레스 시나리오를 만들었다. 각 시나리오는 temperature를 0으로 설정해 세 번씩 실행했다. 같은 입력과 같은 초기 데이터로 Bedrock을 총 33번 호출했으며, 모델은 `claude-sonnet-4-6`을 사용했다.
## 다이어그램과 33번의 실행 결과는 달랐다
실제 성적표는 다음과 같다.
```
실행 횟수: 33 (시나리오 11개 x 3회)
완주: 27 (81.8%)
실패: 6 (18.2%)
정확한 5단계 경로: 27 (81.8%)
지연 시간: 평균 136.7초 | 중앙값 132.5초 | p95 159.6초 | 범위 115-186초
실행당 토큰: 평균 65,730 | 중앙값 67,476 | p95 76,275
배치 총 토큰: 2,169,078 (입력 1.81M / 출력 356k)
추정 배치 비용: 약 $10.78 ($3/M 입력, $15/M 출력 기준)
AWS 사용량 제한: 0
타임아웃: 0
```
핵심은 이렇다. **약 다섯 번에 한 번은 다이어그램에 나온 경로를 끝까지 완료하지 못했고, AWS 서비스 때문에 실패한 경우는 단 한 건도 없었다.** 사용량 제한도, 시간 초과도 발생하지 않았다. 모든 실패는 스웜 내부에서 일어났고, 아키텍처 다이어그램만 봐서는 알 수 없는 지점에 집중됐다.
### 1. 뒤쪽 에이전트일수록 비용과 실패 위험이 커졌다
다이어그램에서는 모든 전문 에이전트가 같은 크기의 상자로 그려진다. 로그에서 확인한 부담은 균등하지 않았다. 각 에이전트는 앞선 에이전트의 출력을 모두 읽고, 다음 에이전트에 전달할 내용으로 다시 작성한다. 이렇게 문맥이 단계마다 쌓이면서 뒤쪽 에이전트일수록 더 많은 데이터를 처리한다. 대표적인 실행 한 건의 토큰 사용량은 다음과 같다.
```
conditions_agent 9,286 토큰
weather_agent 11,267
availability_agent 13,255
safety_agent 16,303 <- 출력 토큰 3,676, 가장 큰 생성량
pricing_agent 16,481
```
실패 6건 중 4건은 `safety_agent`가 다음 에이전트에 넘길 응답을 만들다가 생성 토큰 한도인 4,000개에 도달한 경우였다. 한 번은 승인한 시간대 21개를 나열하는 도중에 한도를 넘었다. 가장 중요한 안전 거부권을 가진 에이전트가 가장 쉽게 멈추는 에이전트이기도 했다. 처리 순서가 뒤쪽이라 앞선 결과를 모두 다시 전달해야 했기 때문이다. 왼쪽에서 오른쪽으로 매끄럽게 이어지는 화살표만으로는 이런 위험을 알아차리기 어렵다.
### 2. temperature를 0으로 설정해도 같은 결과가 나오지 않았다
모든 시나리오를 고정된 입력과 temperature 0으로 세 번씩 실행했다. 그런데 다섯 시나리오는 한 번 실패하고 두 번 성공했다. 스냅샷과 초기 데이터, 프롬프트가 모두 같았지만 실패 지점은 실행할 때마다 달라졌다. 한 번은 *에이전트가 거치는 경로 자체가* 바뀌었다. `cold-calm` 시나리오의 첫 번째 실행은 유효한 시간대 8개를 골라 가격 책정까지 마친 뒤, 제어권을 앞 단계인 `conditions_agent`에 다시 넘겼다. 결국 순환에 빠져 최대 반복 횟수를 소진했다. 두 번째와 세 번째 실행은 문제없이 끝났다.
temperature 0은 모델이 다음 토큰을 고르는 무작위성을 낮출 뿐이다. 도구 호출 형식, 전달할 응답의 길이, 다음 에이전트를 고르는 판단까지 고정하지는 않는다. 멀티 에이전트 스웜에는 모델이 결정하는 지점이 많아서 temperature 0만으로 실행 결과를 재현할 수 없다. 스웜을 검증할 때는 한 번의 성공을 믿지 말고 같은 조건에서 여러 번 실행해야 한다.
### 3. 다이어그램에는 도구 호출 오류와 SDK의 제약이 드러나지 않는다
다음 문제는 다이어그램이 아니라 로그에서만 확인할 수 있었다.
- **인자가 없는 도구의 입력을 해석하지 못했다.** `get_base_pricing()`처럼 인자를 받지 않는 도구를 호출할 때마다 `failed to parse tool input json, defaulting to empty dict`라는 로그가 남았다. Strands가 빈 객체인 `{}`를 대신 넣어 실행을 이어 갔기 때문에 최종 응답은 대부분 검증을 통과했다. 하지만 거의 모든 실행에서 도구 호출 형식이 잘못됐다는 사실은 겉으로 드러나지 않았다.
- **Strands 1.52.0의 `SwarmResult`에는 `final_response`가 없었다.** AWS 예제는 `result.final_response`를 읽지만, 실제로 사용한 버전에는 해당 필드가 없었다. 대신 `result.results`에서 가격 책정 노드의 결과를 직접 꺼내야 했다. 공개된 예제와 배포된 SDK의 인터페이스가 이미 달랐다.
- **Bedrock이 어시스턴트 사전 채우기가 포함된 대화를 거부했다.** `pricing_agent`의 실패 한 건은 어시스턴트 메시지로 끝난 대화를 Bedrock이 이어서 처리하지 않았기 때문에 발생했다. 이는 SDK와 모델 사이의 대화 형식에 관한 제약이며, 아키텍처 다이어그램만으로는 예상하기 어렵다.
- **실행마다 출력 구조가 달라졌다.** 같은 에이전트가 어떤 때는 `slots`를 목록으로 반환하고, 어떤 때는 `approved_slots`를 포함한 객체로 반환했다. 결과를 검사하려면 예상 가능한 여러 형식을 하나로 통일하는 검증기를 별도로 만들어야 했다. 검증기는 결과를 관찰하고 판정할 뿐, 모델의 추천 내용을 고치지는 않는다.
### 4. 전달 내용을 줄여도 여전히 비쌌다
처음 만든 버전은 시간대별 관측값을 통째로 다음 에이전트에 넘겼다. 한 번 실행할 때 스웜에서 토큰 108,827개를 사용했고 285초가 걸렸다. 전달할 내용을 수업 가능 시간대로 압축하고 추천 개수를 8개로 제한하자, 같은 스냅샷에서 토큰 사용량은 64,781개, 실행 시간은 125초로 줄었다. 다섯 에이전트를 거치는 흐름은 그대로였다. 프롬프트만 다듬어도 큰 폭으로 개선됐지만, 전체 실행의 평균은 여전히 토큰 65,730개와 136초였다. “오늘 수업료를 얼마로 정할까”라는 질문에 답하기에는 다섯 에이전트 스웜이 지나치게 비싼 방식이었다.
## 안전 거부권은 실제로 작동했을까
긍정적인 결과부터 보자. 검증기가 해석할 수 있었던 모든 추천에서 초보자 안전 기준이나 최저 가격을 위반한 사례는 한 건도 없었다. 높은 너울과 강한 돌풍 시나리오에서도 끝까지 완료된 결과는 위험한 초보자 수업 시간을 모두 제외했다. 적어도 겉으로는 프롬프트에 적은 정책이 지켜졌다.
하지만 이 결과만으로 프롬프트가 안전을 확실히 보장한다고 결론 내릴 수는 없다. 6번의 실행은 쓸 만한 결과를 내지 못했고, 그중 5번은 출력 형식 오류로 분류됐다. `missing-hour` 시나리오의 두 번째 실행은 관측값이 없는 시각을 추천했다. 검증기는 이를 안전하다고 판정하지 않고 `unverifiable`, 즉 검증 불가로 분류했다. 검증기는 최종 결과에서 추출한 시간대만 확인하며, 자연어로 작성된 모든 설명이나 중간 전달 내용까지 검사하지는 않는다.
따라서 결론은 좁게 잡아야 한다. *이번 실험에서 끝까지 완료돼 검증할 수 있었던 추천에서는 측정 가능한 위반이 없었다.* 이것은 “프롬프트만으로 안전을 보장할 수 있다”는 뜻이 아니다. 안전이 중요한 서비스라면 스웜의 결과를 결정론적인 코드로 다시 검사하고, 기준을 어기면 코드가 거부해야 한다. 처리 부담이 가장 큰 마지막 에이전트가 안전 규칙을 정확히 반복해 주리라고 기대해서는 안 된다.
## 그렇다면 레스토랑 스웜을 만들어야 할까
구조를 익히는 목적이라면 만들어 볼 만하다. 실제로 동작한다. 다섯 전문 에이전트가 Bedrock에서 협업하고 실시간 해양 데이터를 가져와, 안전 기준과 최저 가격을 지킨 추천을 33번 중 27번 만들었다. Strands의 `Swarm` 기능을 이용하면 반나절 만에도 기본 구조를 구현할 수 있다.
다만 실제 서비스에 배포하기 전에는 비용과 위험을 먼저 측정해야 한다.
- **평균뿐 아니라 최악의 경우까지 예산에 반영해야 한다.** 추천 한 건에 평균 약 6만 5천 토큰과 136초가 들었다. 실패도 처리할 문맥이 가장 많이 쌓이는 마지막 단계에 집중됐다.
- **temperature 0이 같은 스웜 동작을 보장한다고 생각해서는 안 된다.** 같은 조건에서 반복해 실행하고 결과의 편차를 확인해야 한다.
- **반드시 지켜야 할 제약은 프롬프트가 아니라 코드로 강제해야 한다.** 안전 거부권과 최저 가격은 같은 입력에 항상 같은 결과를 내는 후처리 코드가 판단해야 한다. 스웜은 제안하고, 코드는 결정해야 한다.
- **처음부터 에이전트 사이의 모든 전달 과정을 기록해야 한다.** 에이전트가 거친 경로, 에이전트별 토큰 사용량, 도구 호출, 검증 결과, 실패 유형이 필요하다. 이번 실험에서 가장 유용했던 자료도 실행 로그였고, AWS 다이어그램에 빠져 있던 정보도 바로 이것이었다.
깔끔한 아키텍처 다이어그램은 가능성을 보여 줄 뿐, 운영 현실까지 보여 주지는 않는다. 실패율 18.2%, 앞 단계로 되돌아가는 순환, 마지막 에이전트의 토큰 한도 초과, 33번 실행에 든 10.78달러가 실제 엔지니어링에서 마주한 결과다. 고객이 사용하는 서비스에 이 구조를 적용한다면 관리형 실행 환경은 [AgentCore Runtime](/blog/2026-08-06-agentcore-runtime-instances.html)에 올릴 수 있다. 하지만 레퍼런스 다이어그램에 생략된 비용과 실패까지 운영 계획에 포함해야 한다. “처음부터 스웜이 적합한 도구인가”라는 질문은 [에이전트 하나로 부족할 때만 멀티 에이전트 시스템을 설계하라](/blog/2025-12-12-designing-robust-multi-agent-systems.html)에서 더 자세히 다뤘다. 프레임워크를 고르는 단계라면 [Bedrock에서 Strands와 LangGraph 비교하기](/blog/2026-02-28-Strands-vs-LangGraph.html)도 함께 참고할 만하다.
다이어그램은 실패하지 않는다. 실제 스웜은 실패한다. 어디서 어떻게 실패하는지 알 수 있도록 반드시 로그를 남겨야 한다.
AWS가 꽤 흥미로운 레퍼런스 아키텍처를 공개했다. Strands Agents로 구현한 레스토랑 동적 가격 책정 솔루션이다. 오케스트레이터가 수요, 날씨, 지역 행사, 경쟁사 가격을 담당하는 전문 에이전트에게 작업을 나눠 준다. 각 에이전트는 MCP 서버를 통해 담당 데이터 소스와 통신하고, 상위 스웜은 결과를 모아 최종 가격을 정한다. 다이어그램은 군더더기 없이 깔끔하다. 화살표는 모두 앞으로 흐르고, 어디에서도 문제가 생기지 않는다.
하지만 저 구조를 실제로 운영하면 비용이 얼마나 들지 궁금했다. 그래서 같은 구조를 서핑 스쿨 예약 도우미에 적용한 뒤 Bedrock에서 33번 실행하고, 에이전트 사이의 모든 전달 과정을 로그로 남겼다.
서핑 스쿨과 레스토랑의 운영 구조는 의외로 닮았다. 수요가 계속 달라지고, 날씨 같은 외부 요인이 수요에 영향을 주며, 어떤 상황에서도 어겨서는 안 되는 제약이 있다. 레스토랑은 원가보다 싸게 팔 수 없고, 서핑 스쿨은 너울이나 돌풍이 위험한 날 초보자를 바다에 내보내서는 안 된다.
그래서 다섯 전문 에이전트의 역할을 다음과 같이 정했다.
orchestrator
└── swarm
conditions_agent → get_surf_conditions (Open-Meteo Marine: 스웰 높이/주기)
weather_agent → get_weather (Open-Meteo Forecast: 바람, 돌풍, 기온)
availability_agent → get_instructor_availability (로컬 샘플 데이터, DynamoDB 역할)
safety_agent → (도구 없음: 앞선 결과를 바탕으로 판단하고 안전상 거부권 행사)
pricing_agent → get_base_pricing (할증/할인 적용, 최저 가격 보장)
해양 상태와 날씨에는 실제 데이터를 사용했다. Open-Meteo가 API 키 없이 너울과 바람 데이터를 제공하므로 conditions_agent와 weather_agent가 이 데이터를 직접 가져온다. 강사 일정과 기본 가격에는 로컬 JSON 샘플을 사용했다. AWS 사례에서 DynamoDB가 맡은 역할을 대신하는 데이터다. 안전 규칙은 safety_agent의 프롬프트에 적고 pricing_agent의 프롬프트에도 한 번 더 명시했다. 가격을 높이거나 낮출 유인이 안전 판단을 뒤집지 못하게 하려는 장치다. AWS 사례와 마찬가지로 “정책을 프롬프트에 넣는다”는 전제를 따른 셈이다.
Strands 구성은 AWS 예제와 거의 같다.
1
2
3
4
5
6
7
8
9
10
11
12
fromstrandsimportAgent,toolfromstrands.modelsimportBedrockModelfromstrands.multiagentimportSwarmswarm=Swarm(build_specialists(),# 위의 5개 agent, 순서대로max_handoffs=6,max_iterations=6,execution_timeout=300.0,node_timeout=90.0,)result=swarm(f"Recommend surf lesson slots and pricing for {location} on {day}. lat={lat} lon={lon}")
여기서 작업 전달은 코드로 정해져 있지 않다. 각 전문 에이전트의 프롬프트 끝에 “다음에는 next_agent로 넘겨라”라고 적어 두면, 모델이 handoff_to_agent 도구를 호출할지 스스로 판단한다. 이 차이가 중요하다. 다이어그램과 실제 동작이 어긋나기 시작하는 지점이기 때문이다.
실행 결과를 공정하게 비교하려고 Open-Meteo의 실제 스냅샷 하나를 고정했다. 이 스냅샷을 바탕으로 높은 너울, 강한 돌풍, 춥고 잔잔한 날, 초보자 안전 기준에 걸친 날, 특정 시각의 관측값이 빠진 경우 등 11가지 스트레스 시나리오를 만들었다. 각 시나리오는 temperature를 0으로 설정해 세 번씩 실행했다. 같은 입력과 같은 초기 데이터로 Bedrock을 총 33번 호출했으며, 모델은 claude-sonnet-4-6을 사용했다.
다이어그램과 33번의 실행 결과는 달랐다
실제 성적표는 다음과 같다.
실행 횟수: 33 (시나리오 11개 x 3회)
완주: 27 (81.8%)
실패: 6 (18.2%)
정확한 5단계 경로: 27 (81.8%)
지연 시간: 평균 136.7초 | 중앙값 132.5초 | p95 159.6초 | 범위 115-186초
실행당 토큰: 평균 65,730 | 중앙값 67,476 | p95 76,275
배치 총 토큰: 2,169,078 (입력 1.81M / 출력 356k)
추정 배치 비용: 약 $10.78 ($3/M 입력, $15/M 출력 기준)
AWS 사용량 제한: 0
타임아웃: 0
핵심은 이렇다. 약 다섯 번에 한 번은 다이어그램에 나온 경로를 끝까지 완료하지 못했고, AWS 서비스 때문에 실패한 경우는 단 한 건도 없었다. 사용량 제한도, 시간 초과도 발생하지 않았다. 모든 실패는 스웜 내부에서 일어났고, 아키텍처 다이어그램만 봐서는 알 수 없는 지점에 집중됐다.
1. 뒤쪽 에이전트일수록 비용과 실패 위험이 커졌다
다이어그램에서는 모든 전문 에이전트가 같은 크기의 상자로 그려진다. 로그에서 확인한 부담은 균등하지 않았다. 각 에이전트는 앞선 에이전트의 출력을 모두 읽고, 다음 에이전트에 전달할 내용으로 다시 작성한다. 이렇게 문맥이 단계마다 쌓이면서 뒤쪽 에이전트일수록 더 많은 데이터를 처리한다. 대표적인 실행 한 건의 토큰 사용량은 다음과 같다.
conditions_agent 9,286 토큰
weather_agent 11,267
availability_agent 13,255
safety_agent 16,303 <- 출력 토큰 3,676, 가장 큰 생성량
pricing_agent 16,481
실패 6건 중 4건은 safety_agent가 다음 에이전트에 넘길 응답을 만들다가 생성 토큰 한도인 4,000개에 도달한 경우였다. 한 번은 승인한 시간대 21개를 나열하는 도중에 한도를 넘었다. 가장 중요한 안전 거부권을 가진 에이전트가 가장 쉽게 멈추는 에이전트이기도 했다. 처리 순서가 뒤쪽이라 앞선 결과를 모두 다시 전달해야 했기 때문이다. 왼쪽에서 오른쪽으로 매끄럽게 이어지는 화살표만으로는 이런 위험을 알아차리기 어렵다.
2. temperature를 0으로 설정해도 같은 결과가 나오지 않았다
모든 시나리오를 고정된 입력과 temperature 0으로 세 번씩 실행했다. 그런데 다섯 시나리오는 한 번 실패하고 두 번 성공했다. 스냅샷과 초기 데이터, 프롬프트가 모두 같았지만 실패 지점은 실행할 때마다 달라졌다. 한 번은 에이전트가 거치는 경로 자체가 바뀌었다. cold-calm 시나리오의 첫 번째 실행은 유효한 시간대 8개를 골라 가격 책정까지 마친 뒤, 제어권을 앞 단계인 conditions_agent에 다시 넘겼다. 결국 순환에 빠져 최대 반복 횟수를 소진했다. 두 번째와 세 번째 실행은 문제없이 끝났다.
temperature 0은 모델이 다음 토큰을 고르는 무작위성을 낮출 뿐이다. 도구 호출 형식, 전달할 응답의 길이, 다음 에이전트를 고르는 판단까지 고정하지는 않는다. 멀티 에이전트 스웜에는 모델이 결정하는 지점이 많아서 temperature 0만으로 실행 결과를 재현할 수 없다. 스웜을 검증할 때는 한 번의 성공을 믿지 말고 같은 조건에서 여러 번 실행해야 한다.
3. 다이어그램에는 도구 호출 오류와 SDK의 제약이 드러나지 않는다
다음 문제는 다이어그램이 아니라 로그에서만 확인할 수 있었다.
인자가 없는 도구의 입력을 해석하지 못했다.get_base_pricing()처럼 인자를 받지 않는 도구를 호출할 때마다 failed to parse tool input json, defaulting to empty dict라는 로그가 남았다. Strands가 빈 객체인 {}를 대신 넣어 실행을 이어 갔기 때문에 최종 응답은 대부분 검증을 통과했다. 하지만 거의 모든 실행에서 도구 호출 형식이 잘못됐다는 사실은 겉으로 드러나지 않았다.
Strands 1.52.0의 SwarmResult에는 final_response가 없었다. AWS 예제는 result.final_response를 읽지만, 실제로 사용한 버전에는 해당 필드가 없었다. 대신 result.results에서 가격 책정 노드의 결과를 직접 꺼내야 했다. 공개된 예제와 배포된 SDK의 인터페이스가 이미 달랐다.
Bedrock이 어시스턴트 사전 채우기가 포함된 대화를 거부했다.pricing_agent의 실패 한 건은 어시스턴트 메시지로 끝난 대화를 Bedrock이 이어서 처리하지 않았기 때문에 발생했다. 이는 SDK와 모델 사이의 대화 형식에 관한 제약이며, 아키텍처 다이어그램만으로는 예상하기 어렵다.
실행마다 출력 구조가 달라졌다. 같은 에이전트가 어떤 때는 slots를 목록으로 반환하고, 어떤 때는 approved_slots를 포함한 객체로 반환했다. 결과를 검사하려면 예상 가능한 여러 형식을 하나로 통일하는 검증기를 별도로 만들어야 했다. 검증기는 결과를 관찰하고 판정할 뿐, 모델의 추천 내용을 고치지는 않는다.
4. 전달 내용을 줄여도 여전히 비쌌다
처음 만든 버전은 시간대별 관측값을 통째로 다음 에이전트에 넘겼다. 한 번 실행할 때 스웜에서 토큰 108,827개를 사용했고 285초가 걸렸다. 전달할 내용을 수업 가능 시간대로 압축하고 추천 개수를 8개로 제한하자, 같은 스냅샷에서 토큰 사용량은 64,781개, 실행 시간은 125초로 줄었다. 다섯 에이전트를 거치는 흐름은 그대로였다. 프롬프트만 다듬어도 큰 폭으로 개선됐지만, 전체 실행의 평균은 여전히 토큰 65,730개와 136초였다. “오늘 수업료를 얼마로 정할까”라는 질문에 답하기에는 다섯 에이전트 스웜이 지나치게 비싼 방식이었다.
안전 거부권은 실제로 작동했을까
긍정적인 결과부터 보자. 검증기가 해석할 수 있었던 모든 추천에서 초보자 안전 기준이나 최저 가격을 위반한 사례는 한 건도 없었다. 높은 너울과 강한 돌풍 시나리오에서도 끝까지 완료된 결과는 위험한 초보자 수업 시간을 모두 제외했다. 적어도 겉으로는 프롬프트에 적은 정책이 지켜졌다.
하지만 이 결과만으로 프롬프트가 안전을 확실히 보장한다고 결론 내릴 수는 없다. 6번의 실행은 쓸 만한 결과를 내지 못했고, 그중 5번은 출력 형식 오류로 분류됐다. missing-hour 시나리오의 두 번째 실행은 관측값이 없는 시각을 추천했다. 검증기는 이를 안전하다고 판정하지 않고 unverifiable, 즉 검증 불가로 분류했다. 검증기는 최종 결과에서 추출한 시간대만 확인하며, 자연어로 작성된 모든 설명이나 중간 전달 내용까지 검사하지는 않는다.
따라서 결론은 좁게 잡아야 한다. 이번 실험에서 끝까지 완료돼 검증할 수 있었던 추천에서는 측정 가능한 위반이 없었다. 이것은 “프롬프트만으로 안전을 보장할 수 있다”는 뜻이 아니다. 안전이 중요한 서비스라면 스웜의 결과를 결정론적인 코드로 다시 검사하고, 기준을 어기면 코드가 거부해야 한다. 처리 부담이 가장 큰 마지막 에이전트가 안전 규칙을 정확히 반복해 주리라고 기대해서는 안 된다.
그렇다면 레스토랑 스웜을 만들어야 할까
구조를 익히는 목적이라면 만들어 볼 만하다. 실제로 동작한다. 다섯 전문 에이전트가 Bedrock에서 협업하고 실시간 해양 데이터를 가져와, 안전 기준과 최저 가격을 지킨 추천을 33번 중 27번 만들었다. Strands의 Swarm 기능을 이용하면 반나절 만에도 기본 구조를 구현할 수 있다.
다만 실제 서비스에 배포하기 전에는 비용과 위험을 먼저 측정해야 한다.
평균뿐 아니라 최악의 경우까지 예산에 반영해야 한다. 추천 한 건에 평균 약 6만 5천 토큰과 136초가 들었다. 실패도 처리할 문맥이 가장 많이 쌓이는 마지막 단계에 집중됐다.
temperature 0이 같은 스웜 동작을 보장한다고 생각해서는 안 된다. 같은 조건에서 반복해 실행하고 결과의 편차를 확인해야 한다.
반드시 지켜야 할 제약은 프롬프트가 아니라 코드로 강제해야 한다. 안전 거부권과 최저 가격은 같은 입력에 항상 같은 결과를 내는 후처리 코드가 판단해야 한다. 스웜은 제안하고, 코드는 결정해야 한다.
처음부터 에이전트 사이의 모든 전달 과정을 기록해야 한다. 에이전트가 거친 경로, 에이전트별 토큰 사용량, 도구 호출, 검증 결과, 실패 유형이 필요하다. 이번 실험에서 가장 유용했던 자료도 실행 로그였고, AWS 다이어그램에 빠져 있던 정보도 바로 이것이었다.
깔끔한 아키텍처 다이어그램은 가능성을 보여 줄 뿐, 운영 현실까지 보여 주지는 않는다. 실패율 18.2%, 앞 단계로 되돌아가는 순환, 마지막 에이전트의 토큰 한도 초과, 33번 실행에 든 10.78달러가 실제 엔지니어링에서 마주한 결과다. 고객이 사용하는 서비스에 이 구조를 적용한다면 관리형 실행 환경은 AgentCore Runtime에 올릴 수 있다. 하지만 레퍼런스 다이어그램에 생략된 비용과 실패까지 운영 계획에 포함해야 한다. “처음부터 스웜이 적합한 도구인가”라는 질문은 에이전트 하나로 부족할 때만 멀티 에이전트 시스템을 설계하라에서 더 자세히 다뤘다. 프레임워크를 고르는 단계라면 Bedrock에서 Strands와 LangGraph 비교하기도 함께 참고할 만하다.
다이어그램은 실패하지 않는다. 실제 스웜은 실패한다. 어디서 어떻게 실패하는지 알 수 있도록 반드시 로그를 남겨야 한다.