AWS 기술 블로그
삼성 계정 AIOps: AgentCore기반 멀티 에이전트 운영 자동화 여정
삼성계정(Samsung Account)은 전 세계 약 21억 사용자에게 삼성 디바이스와 서비스를 연결하는 통합 인증 시스템입니다. Samsung Wallet, Bixby, SmartThings, Samsung Health를 비롯한 수많은 서비스가 삼성계정으로 사용자와 연결되며, 글로벌 대규모 트래픽을 365일 24시간 무중단으로 처리합니다.
이런 초대규모 서비스를 운영한다는 것은, 그만큼 방대한 인프라와 끊임없는 운영 업무를 동반한다는 뜻입니다. 멀티 리전·다계정 환경에 걸친 수많은 마이크로서비스, 매일 쏟아지는 모니터링 알람, 장애가 발생할 때마다 여러 서비스를 가로지르며 원인을 추적해야 하는 영향도 분석, 그리고 주기적인 보안 점검과 비용 리포트까지. 서비스 규모가 커질수록 운영의 복잡도는 선형이 아니라 기하급수적으로 증가합니다.
이 글에서는 삼성계정 서비스의 운영(SRE) 조직이 이러한 운영 복잡도를 해결하기 위해 구축한 Agentic AIOps 시스템을 소개합니다. Amazon Bedrock AgentCore를 기반으로, 전문 에이전트들이 계층 구조로 협업하며 장애 분석·변경 추적·보안 점검·비용 최적화·반복 작업 지원을 자동화합니다. 단순히 “에이전트를 만들었다”가 아니라, 이를 하나의 운영 가능한 시스템으로 어떻게 구축하고 운영하고 있는지, 그리고 그 과정에서 마주한 한계와 이를 극복해 나갈 방향까지 함께 다룹니다.
이 글은 2부작으로 구성되어 있습니다. Part 1: 삼성계정 서비스 AIOps — 멀티 에이전트 기반 운영 자동화의 여정과 Part 2: 삼성계정 서비스 AIOps — 실전 활용과 자율성 확장을 향한 다음 단계를 함께 읽어보시길 권합니다.
문제와 목표
문제: 서비스가 성장할수록 늘어나는 운영 부담
삼성계정은 사용자와 연동 서비스가 꾸준히 늘며 지속적으로 성장해 왔습니다. 전 세계 사용자를 대상으로 높은 가용성과 안정성을 보장하기 위해 멀티 리전 액티브-액티브(Multi-Region Active/Active) 아키텍처로 운영되며, 새로운 기술 도입에 따라 다양한 스택의 애플리케이션과 솔루션이 공존합니다. 이런 환경에서 우리는 세 가지 구조적인 어려움에 직면했습니다.
- 운영 복잡도의 폭발 — 멀티 리전, 다계정, 수많은 마이크로서비스로 구성된 환경에서는 하나의 장애가 단일 지점에 머물지 않습니다. 데이터베이스 지표 이상이 애플리케이션 오류로, 다시 사용자 영향으로 연쇄되며, 원인은 여러 시스템과 계층에 걸쳐 얽혀 있습니다. 이렇게 얽힌 구조에서 원인을 정확히 짚어내는 일은 그 자체로 복잡하고 까다로운 과제였습니다.
- 쌓여가는 스택과 늘어나는 유지보수 — 서비스가 성장하며 새로운 기술과 솔루션이 계속 더해졌고, 그만큼 운영하고 유지보수해야 할 스택도 함께 쌓여 갔습니다. 다양한 스택이 공존하면서 변경 한 건의 영향도 분석은 더 복잡해졌고, 점검해야 할 보안 조치도 늘었습니다. 여기에 모니터링 메트릭 최적화, 리소스 최적화, 비용 관리까지 더해지며, 스택이 늘어날수록 운영 작업도 비례해 증가했습니다.
- 즉각적인 대응에 대한 요구 — 대고객 서비스에서는 장애나 이상이 곧바로 사용자 경험에 직결되기 때문에, 원인 분석부터 조치까지 매우 짧은 시간 안에 이뤄져야 합니다. 그러나 앞서 말한 복잡한 환경 속에서, 사람의 수동 대응만으로 이 속도를 일관되게 맞추기는 어려웠습니다.
목표: 에이전트 기반 운영 자율화, 그러나 신중하게
앞선 문제들에 대한 자연스러운 해답은 생성형 AI 에이전트였습니다. 자연어로 질문하면 여러 시스템을 대신 조회하고, 분석하고, 요약해 주는 조력자. 우리는 에이전트로 운영을 자율화하고, 사람이 더 가치 있는 일에 집중할 수 있는 환경을 만들고자 했습니다.
다만 운영 환경에 실제로 적용하려 하자 본질적인 제약과 마주쳤습니다. LLM은 비결정적입니다. 같은 질문에도 다른 답을 내놓을 수 있고, 드물게는 잘못된 판단을 합니다. 10,000번을 훌륭히 처리하더라도, 단 한 번의 오류가 운영 환경에서는 치명적인 사고로 이어질 수 있습니다. “분석”은 틀려도 사람이 검증하면 되지만, “실행”이 틀리면 돌이킬 수 없습니다.
그래서 우리는 처음부터 “완전 자율 운영”을 목표로 달려가지 않았습니다. 대신 완전한 자율 운영을 지향점으로 두되, 신뢰를 단계적으로 쌓아 올린다는 원칙 아래 다음 세 가지를 목표로 설정했습니다.
- 운영 자동화의 토대 마련 — 에이전트를 활용해 운영 복잡도·유지보수 부담·전문성 분산이라는 구조적 문제를 해소합니다. 장애 분석, 보안 점검, 변경 관리, 자원 생성까지 운영 업무 전반을 에이전트가 함께 수행함으로써, 운영자가 단순 반복 업무에서 벗어나 더 생산적인 업무에 집중할 수 있는 환경을 만듭니다.
- 에이전트를 안정적으로 운영하는 체계 확보 — 에이전트를 한 번 만들고 끝내는 데 그치지 않고, 응답 품질을 평가하고 동작을 관측하며 이상을 탐지하고 통제하는 체계를 갖춥니다. 이로써 멀티 에이전트 시스템을 지속적으로 활용하면서도 안정적으로 관리·운영합니다.
- 자율성과 안전성의 균형 확립 — 운영자와 에이전트의 역할 경계를 명확히 나눕니다. 분석·요약은 에이전트에게 맡기되 실제 변경처럼 위험이 큰 작업은 운영자의 승인을 거치도록 하여, 운영 환경에 영향을 주는 작업도 사고 없이 안전하게 수행합니다.
시스템 아키텍처
우리가 만든 것은 똑똑한 에이전트 하나가 아니라, 매일 신뢰할 수 있게 동작해야 하는 운영 시스템입니다. 그래서 아키텍처를 설계할 때 가장 중요하게 본 것은 모델의 성능이 아니라 구조였습니다.
SRE 업무는 장애 대응, 데이터베이스 점검, 보안, 비용, 인프라 변경까지 도메인마다 필요한 지식과 도구가 완전히 다릅니다. 단일 에이전트가 이 모든 영역을 감당할 수 없었고, 책임을 계층으로 나눠야 했습니다. 동시에 에이전트는 로컬이 아닌 인프라 위에서 항상 동작해야 했고, 서비스가 성장하면서 새 에이전트를 계속 붙여 나갈 수 있도록 구조화된 설계가 필요했습니다.
아래에 전체 아키텍처를 먼저 소개하고, 이어서 이 구조를 만든 세 가지 설계 원칙인 분리, 수렴, 확장을 차례로 설명합니다.
![]()
그림 1. 삼성계정 AIOps 시스템 전체 아키텍처
분리: 도메인으로 나눈 계층형 멀티 에이전트
운영 업무는 장애 대응, 데이터베이스 모니터링, 보안 점검, 비용 분석, 인프라 변경 등 성격이 전혀 다른 영역으로 나뉩니다. 처음에는 하나의 강력한 에이전트가 모든 요청을 처리하게 하는 단일 구조를 검토했지만, 곧 한계가 분명해졌습니다. 다루는 도구와 도메인 지식이 늘어날수록 단일 에이전트의 판단 정확도가 떨어졌고, 어떤 요청을 어떻게 처리할지에 대한 라우팅이 복잡해졌습니다.
그래서 우리는 책임을 계층으로 나눈 3계층 구조를 택했습니다. Orchestrator → 도메인 Supervisor → Sub-agent로 이어지는 구조입니다. 최상위 Orchestrator는 요청을 분석해 알맞은 도메인 Supervisor에게 넘기고, 각 Supervisor는 자신의 도메인 안에서 실제 작업을 수행하는 Sub-agent를 호출합니다. 현재 운영 도메인은 여섯 개로 나뉘어 있습니다.
각 도메인 Supervisor와 그 아래에서 실제 작업을 수행하는 Sub-agent의 역할은 다음과 같습니다. 맨 아래의 General Assistant와 System은 도메인 운영 업무를 담당하는 Supervisor들과 달리, Orchestrator에 직접 연결된 에이전트군으로 시스템 자체를 관리하고 보조하는 역할을 합니다.

표 1. 도메인별 Supervisor 및 Sub-agent 구성
수렴: 두 개의 입구, 하나의 처리 경로
운영팀은 Slack과 웹 콘솔 두 가지 방식으로 시스템과 상호작용합니다. 두 입구가 서로 다르지만, 우리는 이 둘이 동일한 Orchestrator로 수렴하도록 설계했습니다. 어느 채널로 요청하든 동일한 분석 로직과 처리 규칙이 적용되어야 하기 때문입니다. 단일 Orchestrator로 수렴하면 세션 관리, 가드레일, 도메인 라우팅, 장기 메모리가 한곳에서 일괄 적용되고, 에이전트 추가나 정책 변경이 생겨도 입구가 아닌 Orchestrator 한 곳만 수정하면 됩니다.

그림 2. 두 입구가 단일 Orchestrator로 수렴·일괄 처리
확장: 부품을 더하듯 에이전트를 늘리는 구조
멀티 에이전트 시스템에서 에이전트는 계속 늘어납니다. 에이전트를 추가할 때마다 모델 설정, 계정 접근 등 공통 처리를 반복해서 구현해야 한다면 유지보수는 금방 한계에 부딪힙니다.
그래서 우리는 공통 기능을 하나의 모듈로 분리했습니다. 모델 호출·메모리·계정 접근은 모든 에이전트가 같은 공유 모듈을 가져다 씁니다. 새 에이전트를 만들 때 개발자는 공통 배선을 다시 짜지 않고 도메인 로직에만 집중하면 됩니다. 모델을 교체하거나 공통 정책을 바꿀 때도 공유 모듈 한 곳만 수정하면 모든 에이전트에 일괄 적용됩니다.
에이전트 간 호출 방식도 통일했습니다. 모든 에이전트는 레지스트리에서 이름으로 실행 주소를 조회해 동일한 인터페이스로 호출합니다. 새 에이전트는 레지스트리에 한 번 등록하면 시스템 전체가 곧바로 인식합니다.
# 모든 에이전트는 이름 → 실행 주소를 레지스트리에서 조회해 동일하게 호출된다
def get_agent_arn(name: str) -> str:
"""에이전트 실행 주소 조회 (신규 에이전트는 레지스트리에 등록만 하면 된다)"""
return AGENT_REGISTRY.get(name, "")
# 계층과 무관하게 호출 패턴은 언제나 동일하다
invoke_agent(get_agent_arn("db-supervisor"), prompt)
invoke_agent(get_agent_arn("incident-analyzer"), prompt)
이 구조 덕분에 에이전트가 늘어도 각 에이전트는 자신의 도메인 로직에만 집중하면 되고, 시스템 전체의 일관성은 자연스럽게 유지됩니다. 더 나아가, 새 에이전트를 만드는 일 자체를 에이전트에게 맡길 수도 있습니다. 우리는 기존 에이전트의 구조를 학습해 새 에이전트의 코드와 등록까지 자동으로 준비하는 에이전트 빌더를 운영하고 있으며, 이는 시스템이 스스로 확장하는 단계로 가는 출발점입니다.

그림 3. 공통 모듈 위에 도메인 로직만 더하는 에이전트 구조
시스템을 지탱하는 네 개의 필라
아키텍처 구조를 설계했다고 끝이 아니었습니다. 에이전트를 “만드는 것”과 “운영하는 것”은 전혀 다른 문제입니다. 데모는 한 번 동작하면 되지만, 운영 시스템은 매일 신뢰할 수 있게 동작해야 합니다. 앞에서 설명한 멀티 에이전트 구조를 실제로 운영하려면, 빌드와 배포, 추적, 품질 관리, 보안 거버넌스를 지탱하는 토대가 필요했습니다. 우리가 직접 구축하는 대신 AgentCore Runtime, AgentCore Observability, AgentCore Evaluation, 그리고 Bedrock Guardrails라는 네 개의 필라 위에 올렸습니다.
AgentCore Runtime: 멀티 에이전트를 지탱하는 실행 환경
개발 중에는 에이전트를 로컬 CLI에서 실행해도 충분합니다. 하지만 운영 환경은 다릅니다. 알람은 새벽에도 발생하고, 여러 팀원이 동시에 요청을 보냅니다. 에이전트가 인프라 위에서 항상 떠 있어야 하고, 요청이 몰려도 안정적으로 처리되어야 합니다. 우리는 직접 서버를 구성하고 오토스케일링을 설정하는 대신 AgentCore Runtime을 활용해 실행 환경을 관리형으로 전환했습니다.
- 클라우드 호스팅 — 에이전트는 배포된 상태로 언제든 호출을 받을 수 있습니다. 서버를 직접 프로비저닝하거나 오토스케일링을 설정할 필요 없이, 요청이 오면 바로 동작합니다.
- 세션 격리 — 각 요청은 격리된 실행 환경(microVM)에서 처리됩니다. 동시에 여러 운영자가 서로 다른 작업을 요청해도 세션 간 상태나 자격증명이 섞이지 않아, 민감한 운영 작업을 안전하게 병렬 처리할 수 있습니다.
- 장시간 실행 지원 — 장애 원인 분석이나 다단계 작업처럼 오래 걸리는 워크로드도 중간에 끊기지 않고 끝까지 실행됩니다. 일반적인 함수 실행 시간 제한에 맞춰 작업을 쪼개야 하는 제약에서 벗어났습니다.

그림 4. AgentCore Runtime 기반 에이전트 실행 환경
AgentCore Observability: 3계층 호출 흐름을 끝까지 추적
요청 하나가 Orchestrator → Supervisor → Sub-agent의 3계층을 거치다 보면, 어느 구간에서 지연이 생겼는지, 토큰이 어디서 많이 쓰이는지를 파악하기 어렵습니다. 우리는 AgentCore Observability로 두 가지를 확보했습니다.
첫 번째는 트레이스 ID 기반 호출 추적입니다. 에이전트 공통 모듈에 아래 코드를 넣어 두면, 모든 에이전트 간의 호출과 도구 사용에 대한 span 데이터가 로그에 남습니다.
# _common/tools.py: trace_id 기준으로 tool/llm 호출을 span으로 기록하는 HookProvider
def make_trace_hooks(trace_id: str) -> list:
class TraceHooks(HookProvider):
def register_hooks(self, registry):
registry.add_callback(BeforeToolCallEvent, self.on_before_tool)
registry.add_callback(AfterToolCallEvent, self.on_after_tool)
registry.add_callback(BeforeModelCallEvent, self.on_before_model)
registry.add_callback(AfterModelCallEvent, self.on_after_model)
def on_after_tool(self, event):
logger.info(json.dumps({
"event": "tool_span",
"trace_id": trace_id,
"agent": AGENT_NAME,
"tool": event.tool_use["name"],
"start_ms": self._tool_starts.pop(event.tool_use["toolUseId"], 0),
"end_ms": int(time.time() * 1000),
"status": "error" if getattr(event, "exception", None) else "ok",
}))
def on_after_model(self, event):
logger.info(json.dumps({
"event": "llm_span",
"trace_id": trace_id,
"agent": AGENT_NAME,
"start_ms": self._model_start,
"end_ms": int(time.time() * 1000),
}))
# _common/agent_invoke.py: 에이전트 간 호출(invoke)도 동일한 trace_id로 span 기록
if trace_id:
logger.info(json.dumps({
"event": "span",
"trace_id": trace_id,
"caller": AGENT_NAME,
"callee": callee,
"start_ms": start_ms,
"end_ms": end_ms,
}))
수집된 span 데이터로 각 요청이 어느 에이전트를 거쳤는지, 어느 구간에서 시간이 걸렸는지를 아래처럼 한눈에 확인할 수 있습니다. 응답이 느린 구간을 찾아 도구 호출을 줄이거나, 특정 도구에서 반복적으로 오류가 발생하는 패턴을 조기에 포착하는 데 실질적인 도움이 됩니다.

그림 5. trace ID 기준 에이전트 호출 흐름

그림 6. 동일 요청의 구간별 소요 시간(Span)
수집된 span은 아래와 같은 JSON 레코드로 남습니다. 에이전트 간 호출은 span, 도구 호출은 tool_span, LLM 추론은 llm_span으로 구분되고, 같은 trace_id로 묶여 하나의 요청 전체를 재구성할 수 있습니다.
[
{
"event": "span", "trace_id": "tr-8f2a1c",
"caller": "orchestrator", "callee": "db-supervisor",
"start_ms": 0, "end_ms": 8500
},
{
"event": "span", "trace_id": "tr-8f2a1c",
"caller": "db-supervisor", "callee": "aurora-monitor",
"start_ms": 200, "end_ms": 4200
},
{
"event": "tool_span", "trace_id": "tr-8f2a1c",
"agent": "aurora-monitor", "tool": "get_aurora_metrics",
"start_ms": 300, "end_ms": 2800, "status": "ok"
},
{
"event": "tool_span", "trace_id": "tr-8f2a1c",
"agent": "aurora-monitor", "tool": "get_aurora_slow_queries",
"start_ms": 2900, "end_ms": 4100, "status": "ok"
},
{
"event": "llm_span", "trace_id": "tr-8f2a1c",
"agent": "aurora-monitor",
"start_ms": 4100, "end_ms": 4200
},
{
"event": "span", "trace_id": "tr-8f2a1c",
"caller": "db-supervisor", "callee": "dynamo-monitor",
"start_ms": 200, "end_ms": 3200
},
{
"event": "tool_span", "trace_id": "tr-8f2a1c",
"agent": "dynamo-monitor", "tool": "get_dynamo_metrics",
"start_ms": 300, "end_ms": 3000, "status": "ok"
},
{
"event": "span", "trace_id": "tr-8f2a1c",
"caller": "orchestrator", "callee": "incident-supervisor",
"start_ms": 0, "end_ms": 12000
},
{
"event": "span", "trace_id": "tr-8f2a1c",
"caller": "incident-supervisor", "callee": "incident-analyzer",
"start_ms": 300, "end_ms": 11800
},
{
"event": "tool_span", "trace_id": "tr-8f2a1c",
"agent": "incident-analyzer", "tool": "get_alarm_details",
"start_ms": 400, "end_ms": 2400, "status": "ok"
},
{
"event": "tool_span", "trace_id": "tr-8f2a1c",
"agent": "incident-analyzer", "tool": "analyze_alarm",
"start_ms": 2600, "end_ms": 10600, "status": "ok"
}
]
두 번째는 주요 지표 수집입니다. 응답 시간, 토큰 사용량, 호출 성공률을 에이전트별로 수집해 웹 콘솔 대시보드에서 확인합니다. 실제로 이 데이터를 바탕으로 응답이 지연되는 구간을 찾아 도구 호출을 줄이고, 토큰을 많이 쓰는 에이전트의 프롬프트를 다듬어 비용과 지연을 함께 낮출 수 있었습니다.

그림 7. 웹 콘솔 대시보드: 도메인별 호출 수·응답시간·토큰 사용량
AgentCore Evaluation: 품질을 지표로 관리
LLM 기반 에이전트의 품질은 “한 번 잘 동작했다”로 보장되지 않습니다. 프롬프트나 도구를 바꾸면 의도치 않게 다른 응답의 품질이 떨어질 수 있습니다. 그래서 우리는 평가를 하나의 에이전트로 만들었습니다. agent-evaluator가 각 에이전트의 테스트 케이스를 실행하고, 에이전트의 역할에 맞게 정의한 지표를 기준으로 응답 품질을 점수화합니다. 모든 에이전트에 같은 지표를 일괄 적용하지 않고, 예를 들어 분석형 에이전트는 목표 달성도(goal)나 도움이 되었는지(helpfulness)를, 도구 호출이 중요한 에이전트는 도구 사용 정확도(tool accuracy)를 함께 보는 등 에이전트마다 의미 있는 지표를 선택해 평가합니다.

그림 8. AgentCore Evaluation 동작 흐름
그림 8은 이 흐름을 보여줍니다. 에이전트에 변경이 발생하면 agent-evaluator가 이를 감지해 기존 테스트케이스를 검토하고 필요한 경우 수정·추가합니다. 이후 각 테스트케이스를 실행해 해당 에이전트에 맞게 정의된 지표를 LLM Judge로 채점하고, 결과를 Slack과 웹 콘솔로 전달합니다. 변경할 때마다 이 사이클이 자동으로 돌아 품질이 떨어진 지점을 회귀(regression)처럼 즉시 잡아낼 수 있고, 에이전트 개선이 감이 아니라 데이터에 근거하게 됩니다. 아래는 실제 평가 결과의 예시로, incident-analyzer에는 Goal Success Rate·Helpfulness·Tool Accuracy를 지표로 선택해, 테스트케이스별 점수가 에이전트 단위로 관리되는 모습을 보여줍니다.

그림 9. 웹 콘솔 평가 탭에서 incident-analyzer 에이전트의 테스트케이스별 평가 결과
Bedrock Guardrails: Orchestrator 레벨에서 민감정보 차단
운영 데이터에는 민감정보(PII)가 섞여 들어올 수 있습니다. 더욱이 삼성계정은 전 세계 사용자의 인증을 담당하는 서비스라, 운영 과정에서 식별정보가 의도치 않게 로그나 프롬프트에 노출되는 것을 특히 경계해야 했습니다. 우리는 이를 개별 에이전트마다 따로 처리하지 않고, 모든 요청이 가장 먼저 지나는 Orchestrator 레벨에서 한 번에 검사하도록 했습니다. 에이전트가 몇 개든 민감정보 차단 로직은 입구 한 곳에만 두면 되므로, 에이전트가 늘어도 거버넌스가 흔들리지 않습니다. Amazon Bedrock Guardrails가 입력과 출력의 PII를 탐지해 익명화(ANONYMIZED)하거나 차단(BLOCKED)하므로, 민감정보가 하위 에이전트나 외부로 흘러가기 전에 단일 지점에서 걸러집니다.
탐지 이력은 보안 감사를 위해 남기되, 원본을 그대로 저장하지 않습니다. 앞뒤 일부만 남기고 마스킹한 값과 해시, 추적 ID만 기록해, 감사 로그 자체가 또 다른 유출 경로가 되지 않도록 했습니다.
그림 10은 실제 처리 흐름을 보여줍니다. 사용자 요청이 들어오면 Orchestrator가 LLM을 호출할 때 Guardrails가 입력을 먼저 검사하고, LLM 추론 후 출력도 동일하게 재검사합니다. 탐지 시 마스킹 또는 차단이 자동으로 적용됩니다.

그림 10. Bedrock Guardrails 처리 흐름
Guardrail 설정은 모든 요청이 가장 먼저 지나는 Orchestrator에 적용합니다. 하위 에이전트까지 개별로 설정하지 않아도 입구 한 곳에서 걸러지므로, 에이전트가 늘어도 거버넌스 구조는 흔들리지 않습니다.
# _common/model.py: 모든 에이전트가 공유하는 모델 설정. guardrail은 여기 한 번만 설정.
def get_model(agent_name: str = None) -> BedrockModel:
model_id = AGENT_MODELS.get(agent_name, DEFAULT_MODEL_ID)
return BedrockModel(
model_id=model_id,
guardrail_id=GUARDRAIL_ID, # 모든 에이전트에 동일 guardrail 적용
guardrail_version=GUARDRAIL_VERSION,
guardrail_trace="enabled_full", # 탐지 이력 trace 전체 활성화
guardrail_redact_input=True, # 입력 가드레일 작동 시 입력을 안내 메시지로 교체
guardrail_redact_output=False, # 출력은 가드레일 메시지로 덮어쓰지 않고 원본 그대로 전달
guardrail_redact_input_message=GUARDRAIL_INPUT_MSG,
guardrail_redact_output_message=GUARDRAIL_OUTPUT_MSG,
)
탐지된 PII는 원본을 저장하지 않습니다. 앞뒤 3자만 남긴 마스킹값과 SHA-256 해시, trace_id를 비동기로 저장해 감사 추적은 유지하되, 감사 로그 자체가 유출 경로가 되지 않도록 했습니다.
# _common/guardrail.py: 스트리밍 응답 trace에서 PII 파싱 후 S3 비동기 저장
def log_if_detected(kwargs: dict, trace_id: str, user_id: str, original_input: str):
chunk = kwargs.get("event", {})
guardrail = chunk.get("metadata", {}).get("trace", {}).get("guardrail", {})
if not guardrail:
return
pii_detected = []
for assessment in guardrail.get("inputAssessment", {}).values():
pii = assessment.get("sensitiveInformationPolicy", {})
pii_detected += [e for e in pii.get("piiEntities", [])
if e.get("action") in ("ANONYMIZED", "BLOCKED")]
if not pii_detected:
return
record = {
"trace_id": trace_id, "user_id": user_id,
"pii_detected": [{
"type": e["type"], "action": e["action"],
"match_masked": _mask(e["match"]), # 앞 3자·뒤 3자만 보존
"match_hash": _hash(e["match"]), # SHA-256 앞 16자 (추적용)
} for e in pii_detected],
}
threading.Thread(target=_save, args=(record,), daemon=True).start()
에이전트와의 협업
시스템이 갖춰지고 나서 자연스럽게 따라온 질문이 있었습니다. 어디까지 자율화할 것인가. 이는 기술의 문제가 아니라 신뢰의 문제입니다. 좋은 운영 자동화의 핵심은 “에이전트가 얼마나 똑똑한가”가 아니라 “에이전트에게 어디까지 맡길 수 있는가”입니다. 우리는 자율 운영을 “있다/없다”의 이분법이 아니라, 신뢰를 검증하며 쌓아가는 단계로 설계했습니다. 이 장에서는 우리가 실제로 도달한 두 단계인 멀티 에이전트가 함께 분석하는 Level 1과, 계획·승인을 거쳐 실행하는 Level 2가 어떻게 동작하는지 살펴봅니다.
자율운영 성숙도 모델
자율성을 한 번에 부여하는 것은 위험합니다. LLM은 비결정적이고, 운영 환경에서의 잘못된 실행은 곧바로 사용자 영향으로 이어지기 때문입니다. 그래서 우리는 자율 운영을 네 단계로 나누고, 단계마다 신뢰를 검증하며 위로 올라가는 방식을 택했습니다.
핵심은 Level 1(분석·읽기)과 Level 2(실행·쓰기)의 경계입니다. 분석은 에이전트에게 맡기되, 실행은 반드시 사람의 판단을 거칩니다. 특히 에이전트에게 직접 쓰기 권한을 부여하는 순간부터는 다른 차원의 신뢰가 필요합니다.

그림 11. 삼성계정 AIOps 자율운영 성숙도 모델
Level 1: 멀티 에이전트가 함께 분석한다
가장 먼저 자동화한 것은 분석입니다. 읽기 중심이라 위험이 낮고, 동시에 운영팀의 시간을 가장 많이 잡아먹던 일이었기 때문입니다. 알람이 오면 여러 시스템의 지표와 로그를 일일이 뒤져 원인을 좁혀가는 작업, 정기적으로 상태를 점검하고 리포트를 만드는 작업이 모두 여기에 해당합니다.
그런데 운영 문제는 한 도메인에서 끝나지 않는 경우가 많습니다. “Aurora 비용이 갑자기 늘었다”는 알람 하나만 해도, 실제 쿼리량이 늘어난 것인지, 최근 배포가 영향을 준 것인지, 아니면 RI/SP 커버리지가 줄어 온디맨드 요금 비중이 높아진 것인지 알려면 비용·DB·인프라를 함께 봐야 합니다. 한 영역만 보는 단일 에이전트로는 이런 도메인을 가로지르는 원인을 짚어낼 수 없었습니다.
멀티 에이전트 구조는 이 문제를 병렬 교차 분석으로 풉니다. Orchestrator가 관련 Supervisor들을 동시에 호출하고, 각 Supervisor가 자신의 영역을 Sub-agent에게 맡겨 수집한 결과를 하나의 분석으로 종합합니다. 운영자는 여러 콘솔을 오가는 대신 이미 맥락이 정리된 분석 결과에서 판단을 시작할 수 있게 됩니다.

그림 12. 멀티 에이전트 교차 분석 흐름
Level 2: 계획하고, 실행한다
Level 1이 자리를 잡으면서 다음 고민이 생겼습니다. 분석에서 그치지 않고, 조치까지 에이전트가 맡을 수 있어야 했습니다. 그런데 실행은 분석과 본질적으로 다릅니다. 분석은 틀려도 다시 하면 되지만, 실행은 한 번 잘못되면 곧바로 서비스 영향으로 이어집니다. 그래서 우리는 에이전트가 혼자 결정하고 바로 실행하는 구조 대신, 계획과 실행의 매 단계를 사람이 확인할 수 있는 구조를 택했습니다.
이 원칙을 구현한 것이 Blackboard 패턴입니다. 에이전트와 사람이 서로 직접 통신하는 대신, 공유된 작업 공간(Blackboard)의 상태를 보고 자신이 행동해야 할 시점에 개입합니다. 계획 단계에서 에이전트가 계획을 작성하면 사람이 검토하고, 방향이 잘못됐다면 여기서 되돌립니다. 수행 단계에서는 합의된 계획을 에이전트가 실행하고, 사람이 결과를 확인한 뒤 완료됩니다.
Blackboard가 비동기라는 것이 핵심입니다. 에이전트와 사람이 각자의 시점에 상태를 확인하고 다음 행동을 이어갈 수 있습니다. 각 단계의 계획서, 검토 의견, 수행 결과가 모두 기록되어 있어 누구든 필요한 시점에 현재 상태를 보고 작업을 이어받을 수 있습니다.

그림 13. Blackboard 패턴: task-id 기반 단계별 상태 저장 흐름
단계를 설계하고 나서 한 가지 고민이 생겼습니다. 어떤 작업은 계획서를 거치는 것 자체가 오히려 느렸고, 어떤 작업은 계획 없이 바로 실행하기엔 위험 부담이 컸습니다. 그래서 작업 유형에 따라 에이전트가 처리하는 단계를 달리 설정하고, 이를 세 가지 타입으로 구분했습니다.

그림 14. Task 특성별 처리 유형 분류
이 설정을 담은 것이 Skill입니다. Skill은 반복되는 운영 업무를 표준화한 문서로, Task 에이전트 그룹인 task-planner, task-reviewer, task-executor가 업무에 따라 적합한 Skill을 동적으로 참조해 작업을 처리합니다. 새로운 운영 업무를 에이전트와의 협업 구조로 편입할 때, Skill 문서를 추가하는 것만으로 기존 Task 에이전트 그룹이 그대로 처리할 수 있어 시스템 변경 없이 커버리지를 넓혀갈 수 있었습니다. Skill이 실제로 어떻게 구성되고 고도화되는지는 다음 장에서 이어서 다룹니다.
지속 가능한 운영 모델
Level 2까지 구현하고 나서 우리가 마주한 다음 질문은 “이 시스템을 어떻게 시간이 지날수록 더 나아지게 만드는가”였습니다. 한번 만들어 둔 상태로 멈춰 있는 시스템이 아니라, 작업을 거듭할수록 업무 지식이 정교해지고 분석 경험이 쌓여 점점 더 유용해지는 시스템을 만들고 싶었습니다. 이를 위해 두 가지 축을 설계했습니다. 하나는 작업 절차를 스스로 다듬어 가는 Skill이고, 다른 하나는 분석 경험을 장기적으로 기억하는 Memory입니다.
Skill: 업무 지식을 자산화하고 스스로 고도화
Skill은 에이전트 프레임워크에서 흔히 쓰이는 개념입니다. 우리가 고민한 것은 이 Skill을 운영 환경에서 어떻게 활용할 것인가였습니다. 단순히 API 호출을 묶어두는 것이 아니라, SRE 엔지니어가 실제로 작업을 수행할 때의 절차, 판단 기준, 롤백 방안까지 담은 Markdown 문서로 정의했습니다. 에이전트는 작업 요청이 들어오면 해당 업무에 맞는 Skill 문서를 참조해, 엔지니어의 작업 절차를 그대로 따라 작업을 진행합니다.

그림 15. Skill을 중심으로 한 Task 에이전트 그룹의 작업 흐름과 skill-calibrator의 상시 고도화
앞선 그림에서 Skill을 상시 고도화하는 skill-calibrator를 함께 그려둔 이유가 여기 있습니다. Skill은 한 번 작성하고 끝나는 문서가 아닙니다. Blackboard에는 작업 이력이 계획서, 검토 의견, 수행 결과, 엔지니어의 피드백 형태로 쌓이는데, skill-calibrator는 이 이력을 분석해 Skill 자체를 다듬습니다. 완료된 Task의 산출물에서 계획이 부족했던 부분은 무엇인지, 엔지니어가 반복해서 수정을 요청한 패턴은 무엇인지를 파악하고, 개선된 내용을 담은 Skill 업데이트 PR을 자동으로 생성합니다.
# skill-calibrator: 산출물 기반 스킬 캘리브레이션
@tool
def list_recent_artifacts(days: int = 7) -> str:
"""최근 N일간의 task 산출물 목록을 조회합니다."""
days = min(days, 30)
s3 = _get_s3()
today = datetime.now(timezone.utc)
artifacts = []
for d in range(days):
date_prefix = (today - timedelta(days=d)).strftime("%Y-%m-%d")
paginator = s3.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket=BUCKET, Prefix=f"{PREFIX}/{date_prefix}/"):
for obj in page.get("Contents", []):
artifacts.append({"key": obj["Key"], "size": obj["Size"]})
return json.dumps({"count": len(artifacts), "artifacts": artifacts}, ensure_ascii=False)
@tool
def get_skill_file(agent_name: str, skill_name: str) -> str:
"""GitHub 레포에서 현재 skills MD 파일을 조회합니다."""
path = f"{SKILLS_PATHS[agent_name]}/{skill_name}.md"
data = _gh("GET", f"/repos/{GITHUB_ORG}/{REPO}/contents/{path}", params={"ref": "main"})
return base64.b64decode(data["content"]).decode("utf-8")
@tool
def create_calibration_pr(branch: str, title: str, body: str, files: str) -> str:
"""스킬 개선 PR을 생성합니다."""
# 기존 파일 내용을 읽어 변경분만 커밋 → main 대상 PR 생성 (사람 검토 후 머지)
file_list = json.loads(files)
for f in file_list:
_gh("PUT", f"/repos/{GITHUB_ORG}/{REPO}/contents/{f['path']}", json={
"message": f"calibration: update {f['path']}",
"content": base64.b64encode(f["content"].encode()).decode(),
"branch": branch,
})
pr = _gh("POST", f"/repos/{GITHUB_ORG}/{REPO}/pulls", json={
"title": title, "body": body, "head": branch, "base": "main"})
return json.dumps({"pr_number": pr["number"], "url": pr["html_url"]})
PR도 사람이 검토·승인한 뒤 반영됩니다. 작업 수행 → 이력 축적 → Skill 개선의 루프가 반복되면서 시스템의 업무 지식이 점진적으로 고도화됩니다.
AgentCore Memory: 별도 DB 없이 운영 지식을 축적
Skill이 “작업하는 방법”에 대한 지식을 쌓는다면, AgentCore Memory는 “그동안 무슨 일이 있었고 무엇이 옳은 운영 방식인가”에 대한 경험을 쌓습니다. 흔히 이런 지식을 남기려면 별도의 데이터베이스를 두고 스키마를 설계하고 적재 파이프라인을 만들어야 하지만, 우리는 그런 구성 없이 Memory의 장기 기억 기능만으로 운영 지식을 축적합니다.
핵심은 경험의 자동 지식화입니다. 대화나 분석 결과를 그대로 쌓아두는 것이 아니라, 그 안에서 “기억할 가치가 있는 사실”을 에이전트가 스스로 골라냅니다. Orchestrator의 Memory에는 의미 기반(semantic) 추출 전략을 설정해 두었습니다. 대화가 저장될 때마다 별도 모델이 그 내용을 분석해, 기억할 가치가 있는 팩트를 유형별로 추출하고 장기 기억으로 통합합니다.

그림 16. 별도 DB 없이 운영 지식이 장기 기억으로 축적되는 과정
특히 사용자 교정과 운영 규칙이 에이전트를 성장시키는 축입니다. 엔지니어가 에이전트의 판단을 바로잡으면 그 교정이 사실로 남고, 반복적으로 나타나는 운영 방식은 규칙으로 굳습니다. 이렇게 쌓인 팩트는 이후 분석과 판단에 다시 참조되어, 같은 실수를 반복하지 않고 한번 검증된 운영 지식을 일관되게 따르도록 만듭니다. 결과적으로 에이전트는 사용할수록 그 조직의 운영 맥락에 맞게 성숙해지고, 그 성숙도는 별도의 지식 베이스 없이 Memory 안에 그대로 축적됩니다.
// Orchestrator Memory에 설정한 의미 기반(semantic) 추출 전략
{
"name": "sre_knowledge_extraction",
"type": "SEMANTIC_OVERRIDE",
"extraction": {
"appendToPrompt": "대화에서 도메인 지식·사용자 교정·운영 규칙 등 기억할 가치가 있는 팩트를 한 줄로 추출. 일상 대화에서는 추출하지 않음.",
"modelId": "us.anthropic.claude-..."
}
}
// eventExpiryDuration: 90일. 추출된 팩트는 90일간 장기 기억으로 유지된다
별도의 적재 코드도 필요 없습니다. 에이전트는 평소처럼 대화를 저장하기만 하면 되고, 팩트 추출과 통합은 Memory가 백그라운드에서 알아서 처리합니다.
# 대화를 저장만 하면 semantic 전략이 백그라운드에서 팩트를 추출·통합한다
memory.save_conversation(
memory_id=mid, actor_id=actor_id, session_id=session_id,
messages=[(prompt, "USER"), (response[:4000], "ASSISTANT")],
)
# 별도 DB나 파이프라인 없이 사용자 교정·운영 규칙 같은 운영 지식이 장기 기억으로 축적된다
스케줄러: 권한을 늘리지 않고 자동화를 확장
시스템이 똑똑해질수록 더 많은 일을 맡기고 싶어집니다. 이때 흔한 유혹은 에이전트에게 직접 변경 권한을 쥐여주는 것이지만, 우리는 반대로 갔습니다. 에이전트에게는 시스템을 직접 바꾸는 권한을 주지 않고, “이렇게 해달라”고 요청할 권한만 부여합니다. 실제 변경은 사람의 승인을 거쳐 별도의 실행 주체인 스케줄러가 수행합니다. 자동화 범위를 넓히는 동력은 에이전트의 권한이 아니라, 요청을 안전하게 수행하는 실행 시스템을 고도화하는 데 두었습니다.
흐름은 이렇습니다. 에이전트는 직접적인 쓰기 권한을 갖지 않습니다. 쓰기 작업이 필요하면 그 작업을 실행하는 대신, 무엇을 어떻게 실행할지를 스케줄러에 등록해 둡니다. 스케줄러가 그 작업을 실행하려는 시점에 엔지니어에게 승인을 요청하고, 이때 실행될 커맨드나 코드가 그대로 검토됩니다. 승인이 떨어지면 스케줄러는 검토된 그 내용을 결정적으로 그대로 실행합니다.

그림 17. 비결정적인 제안과 결정적인 실행을 승인 게이트로 분리하는 구조
이 구조의 핵심은 실행 주체를 분리했다는 점입니다. 만약 에이전트가 쓰기 권한을 직접 쥐고 있다면, 비결정적인 특성 탓에 의도치 않은 시점에 예상 밖의 변경을 일으킬 위험이 남습니다. 대신 에이전트는 “무엇을 할지”를 제안하는 역할만 맡고, “실제로 실행하는” 역할은 사람이 검토한 내용을 그대로 따르는 스케줄러가 맡습니다. 비결정적인 판단과 결정적인 실행을 떼어놓아, 에이전트가 스스로 운영 환경을 건드리는 경로를 구조적으로 막은 것입니다.
이 구조 덕분에 자동화를 넓혀가는 방식이 달라집니다. 새로운 작업 유형을 자동화할 때 에이전트의 권한을 조금씩 푸는 것이 아니라, 스케줄러가 안전하게 수행할 수 있는 작업의 종류를 늘려갑니다. 검증된 실행 절차가 하나씩 추가될수록 자동화 범위는 넓어지지만, 사람이 통제하는 안전선은 그대로 유지됩니다. 시스템이 성장해도 신뢰의 경계는 흔들리지 않는 셈입니다.
마무리
지금까지 삼성계정 서비스의 SRE 조직이 Amazon Bedrock AgentCore 위에 멀티 에이전트 AIOps 시스템을 어떤 구조로 세웠고, 그 구조를 어떤 토대로 떠받쳤으며, 자율성의 경계를 어떻게 단계적으로 넓혀왔는지를 살펴봤습니다. 요점은 세 가지입니다. 책임을 도메인으로 나눈 3계층 구조, Runtime·Observability·Evaluation·Guardrails라는 네 개의 필라, 그리고 분석은 맡기되 실행은 승인을 거치는 단계적 자율운영입니다.
이어지는 Part 2에서는 이 시스템이 실제 운영 현장에서 어떻게 쓰이고 있는지, 도입 전후로 업무의 무게중심이 어떻게 이동했는지, 그리고 더 높은 수준의 자율운영으로 나아가기 위해 무엇을 준비하고 있는지를 다룹니다.
여러분의 운영 환경에서도 같은 여정을 시작해 보고 싶다면, Amazon Bedrock AgentCore 개발자 안내서와 Strands Agents SDK 문서로 첫 에이전트를 만들어 보시기 바랍니다. 작게 시작해 읽기 전용 분석부터 맡기고, 신뢰가 쌓이는 만큼 범위를 넓혀가는 방식을 권합니다.
참고
AWS 문서
- Amazon Bedrock AgentCore
- AgentCore Runtime
- AgentCore Memory
- AgentCore Observability
- Amazon Bedrock Guardrails
- Strands Agents SDK
참고 논문
- Exploring Advanced LLM Multi-Agent Systems Based on Blackboard Architecture (2025): 공유 블랙보드 기반 협업으로 SOTA에 준하는 성능을 더 적은 토큰으로 달성
- LLM-based Multi-Agent Blackboard System for Information Discovery in Data Science (2025): 중앙 에이전트가 공유 블랙보드에 요청을 올리고 하위 에이전트가 자발적으로 응답
- HALO: Hierarchical Autonomous Logic-Oriented Orchestration for Multi-Agent LLM Systems (2025): Orchestrator → Domain Supervisor → Specialist 3계층 구조의 성능 향상
- A Comparative Study of Orchestration Patterns, Cost-Accuracy Tradeoffs and Production Scaling Strategies (2026): 계층형 supervisor-worker가 비용·정확도 측면에서 가장 유리
- Enhancing LLM Agent Tool Use through Tool Merging and Context-Aware Filtering (2025): 관련 도구만 선별 노출 시 선택 정확도 향상
삼성전자 삼성계정 서비스 SRE팀
소새롬
김정현
김재영
이동준
최송희
편수빈
삼성계정 서비스 SRE팀은 삼성전자의 주요 서비스가 공통으로 사용하는 인증 플랫폼인 삼성계정을 운영하고 있습니다. 전 세계에서 유입되는 트래픽을 감당할 인프라를 설계·운영하고, 장애에 대응하며, 클라우드 비용과 보안을 관리합니다. 안정적인 서비스를 유지하면서도 운영 부담을 줄이는 방법을 꾸준히 고민하고 있습니다.