
당신의 온콜(On-Call) 에이전트가 모든 것을 잊어버렸나요? 저희는 그렇지 않습니다.
요약
과거의 장애 이력을 기억하여 인시던트 대응을 돕는 'On-Call Copilot' 에이전트를 소개합니다. Hindsight 메모리 레이어를 활용해 단순 키워드 검색이 아닌 의미론적 유사성을 바탕으로 과거의 근본 원인과 해결책을 회상합니다.
핵심 포인트
- Hindsight 메모리 레이어를 통한 조직적 기억(Organizational Memory) 구현
- 단순 조언을 넘어 과거의 성공 및 실패 사례를 구조화하여 제공
- FastAPI, Groq, Hindsight를 결합한 5단계 인시던트 대응 파이프라인
- 의미론적 그래프를 활용해 에러 메시지가 달라도 근본 원인이 같은 사례 탐색
과거의 프로덕션 장애를 실제로 기억하는 무언가를 처음 사용했을 때, 저는 그것을 완전히 신뢰하지 못했습니다. 결과가 우연이 아니라는 것을 확인하기 위해 동일한 인시던트(incident)를 두 번 제출했습니다.
우연이 아니었습니다.
저는 On-Call Copilot을 구축하고 있었습니다. 이는 단순히 조언을 생성하는 것이 아니라, 유사한 문제가 마지막으로 발생했을 때 실제로 어떤 일이 일어났는지를 회상하는 인시던트 대응(incident response) 에이전트입니다. 라이브 앱은 on-call-copilot.vercel.app에서 확인할 수 있습니다. 메모리 레이어(memory layer)는 Hindsight입니다. 그리고 저를 가장 놀라게 했던 것은 통합(integration)이 얼마나 어려웠느냐가 아니라, 일단 작동하기 시작하자 그 차이가 얼마나 즉각적으로 명확해졌느냐 하는 점이었습니다.
시스템이 실제로 하는 일
On-Call Copilot은 조직적 기억(organizational memory)을 가진 AI 인시던트 커맨더(AI Incident Commander)입니다. 앱의 슬로건은 "모든 장애로부터 배우세요. 다음 장애는 더 빠르게 해결하세요."입니다. 이것은 마케팅 문구가 아니라 말 그대로 아키텍처(architecture)입니다.
Sentry 트레이스백(traceback), Datadog 트리거, 원시 CLI 로그와 같은 프로덕션 알림(production alert)이 들어오면, 이를 인시던트 인제스션 콘솔(Incident Ingestion Console)에 붙여넣습니다. 시스템은 이를 5단계 파이프라인(pipeline)을 통해 실행합니다:
프로덕션 알림 (Production Alert) → FastAPI 라우터 (FastAPI Router) → Hindsight 메모리 (Hindsight Memory) → Groq 추론 (Groq Reasoning) → SRE 플레이북 (SRE Playbook)
가장 중요한 것은 3단계입니다. Groq가 무언가를 생성하기 전에, Hindsight의 시맨틱 그래프(semantic graph)가 조직의 전체 인시던트 이력을 대상으로 회상(recall)을 수행합니다. 이는 키워드 검색(keyword search)을 수행하는 것이 아닙니다. 표면적인 에러 메시지가 다르더라도 근본적인 원인이 동일하여 실패했던 과거의 인시던트들, 즉 의미론적으로 관련된(semantically related) 과거 인시던트들을 찾아냅니다.
돌아오는 결과물은 단순히 "유사한 인시던트가 여기 있습니다" 수준이 아닙니다. 결과는 구조화되어 있습니다: 과거의 근본 원인(root cause), 성공적인 해결책, 그리고 결정적으로 — 피해야 할 실패한 시도들입니다. 즉, 누군가가 이미 시도했다가 상황을 악화시켰던 것들입니다. 바로 이 마지막 부분이 일반적인 LLM(Large Language Model)의 응답과 차별화되는 지점입니다.
FastAPI 레이어: 트리아지(triage) 요청의 흐름
모든 인시던트는 단일 POST 엔드포인트에서 시작됩니다. 프론트엔드는 가공되지 않은(raw) 경고 텍스트를 전송하며, FastAPI가 오케스트레이션(orchestration)을 담당합니다. 먼저 Hindsight로부터 메모리 컨텍스트(memory context)를 가져온 다음, 해당 컨텍스트를 경고 내용과 함께 Groq 추론 체인(reasoning chain)으로 전달합니다.
# backend/api.py
@app.post("/analyze")
def analyze(data: IncidentRequest):
...
이러한 순서가 핵심적인 설계 결정입니다. 회상(recall)은 LLM이 무엇인가를 보기 _전에_ 일어납니다. Groq가 근본 원인에 대해 추론할 때쯤이면, 조직의 컨텍스트가 이미 프롬프트(prompt)의 일부로서 내재되어 있습니다. 이는 별도의 조회(lookup) 방식이 아닙니다.
Hindsight의 회상이 실제로 반환하는 것
저는 이 프로젝트를 시작하기 전까지 Hindsight를 사용해 본 적이 없었습니다. 제가 처음에 가졌던 멘탈 모델(mental model)은 이것이 검색(search)처럼 동작할 것이라는 점이었습니다. 즉, 키워드를 입력하면 일치하는 문서를 돌려주는 방식 말입니다.
하지만 실제로 동작하는 방식은 지식 그래프(knowledge graph) 상에서의 의미론적 추론(semantic reasoning)에 더 가깝습니다. 제가 "FATAL: database pool choked during active transaction"을 제출했을 때, 시스템은 두 개의 과거 인시던트를 회상해냈습니다:
- INC-103 — 트랜잭션 트래픽이 높은 상황에서의 데이터베이스 연결 풀 고갈(Database connection pool exhaustion under high transactional traffic). 일치율 91%. 성공적인 해결책: 프록시 풀 제한을 50으로 증가시키고, 트랜잭션 타임아웃 안전장치를 구현. 실패한 시도: 풀 복제본을 동적으로 확장하는 것(DB 잠금 폭풍 유발).
- INC-104 — Redis 클러스터 메모리 할당 초과(Redis cluster memory allocation overrun). 일치율 87%. 성공적인 해결책: maxmemory-policy를 volatile-lru로 구성. 실패한 시도: Redis 서비스의 콜드 재시작(모든 활성 세션 삭제).
이 일치율은 Hindsight's agent memory 시스템에서 나오는 실제 신뢰 점수입니다. '실패한 시도(failed attempt)' 필드는 새벽 3시에 빛을 발하는 부분인데, 무엇에 손대지 말아야 하는지를 알려주어 그곳에 40분을 낭비하는 일을 막아줍니다.
두 가지 메모리 작업: 유지(retain)와 회상(recall)
backend/memory.py의 전체 Hindsight 통합은 두 개의 호출에 기반을 두고 있습니다. 여기 두 개를 나란히 보여드립니다:
# backend/memory.py
from contextlib import contextmanager
from hindsight import HindsightClient
...
두 개의 함수, 각각 한 번의 호출입니다. 전체 조직 메모리 레이어는 이 약 30줄로 이루어져 있습니다. Hindsight retain/recall API가 백그라운드에서 수행하는 의미론적 인덱싱(semantic indexing), 그래프 순회(graph traversal), 신뢰 점수 산정 등 모든 것을 무료로 얻을 수 있습니다.
실제 파이프라인 적용 사례
UI의 인시던트 해결 타임라인은 이 파이프라인을 실시간으로 보여줍니다:
- Alert Received (경고 수신) — 가공되지 않은 메트릭(raw metrics) 또는 트레이스(trace)가 버퍼로 수집됨
- Memory Retrieved (메모리 검색) — 지역 인덱스 맵(regional index maps)을 대상으로 FastAPI를 통한 시맨틱 상관관계(semantic correlation) 분석 수행
- Root Cause Identified (근본 원인 식별) — LLM이 이상 징후를 격리하고 일치 신뢰도(match confidence)를 계산함
- Resolution Suggested (해결책 제안) — 방지 경고(avoidance warnings)가 포함된 상세 플레이북(playbook) 생성
- Knowledge Stored (지식 저장) — 사후 분석(post-mortem) 답변이 조직의 메모리에 다시 인덱싱됨
다섯 번째 단계가 바로 학습 루프(learning loop)입니다. 해결된 모든 인시던트는 retain()을 통해 Hindsight로 피드백됩니다. 다음에 발생하는 유사한 인시던트는 이를 회상된 컨텍스트(recalled context)로 불러옵니다. 시스템은 시간이 지남에 따라 더욱 구체화됩니다. 이는 모델이 변해서가 아니라, 메모리 뱅크(memory bank)가 성장했기 때문입니다.
Before vs after — 메모리가 실제로 바꾸는 것
조직 메모리(organizational memory)가 없는 경우:
- 훈련 데이터에서 가져온 일반적인 조언 — "네트워크 어댑터 확인", "OS 재설치" 등
- 귀하의 특정 환경에서 이미 시도된 작업에 대한 인지 부족
- 귀하의 클러스터에서 이미 두 번이나 실패했던 제안이 다시 나타남
- 모든 인시던트가 제로(zero) 상태에서 시작됨
Hindsight 메모리가 있는 경우 (지식 베이스에 150개의 인시던트 포함):
- 교과서적인 예시가 아닌, 실제 과거 장애 사례에서 추출한 정밀한 일치 항목
- 엔지니어가 반복하지 않도록 실패한 수정 사항을 명시적으로 표시
- 해결 후 즉시 인덱싱하여 다음 인시던트가 즉각적인 혜택을 받음
- 대시보드에 실시간으로 표시되는 평균 해결 시간(MTTR)의 약 42% 감소 추정치
이 42%라는 수치는 제가 주장하는 벤치마크가 아닙니다. 이는 로드된 인시던트 지식 베이스 전반에 걸친 시스템의 과거 회상 성능(historical recall performance)을 기반으로 대시보드가 보여주는 수치입니다.
What the Teach the System 패널이 하는 역할
앱의 하단에는 "Teach the System — Training Mode (시스템 학습 — 트레이닝 모드)"라고 불리는 섹션이 있습니다. 해결 요약(resolution summary)이나 인시던트(incident) 링크를 붙여넣고 제출하면, Hindsight가 즉시 이를 인덱싱(indexing)합니다. Telemetry Console은 이 모든 과정을 실시간으로 기록합니다. 여러분은 리테인 콜(retain call)이 나가는 것을 지켜보고, 성공 응답을 확인하며, 다음에 유사한 문제에 직면할 엔지니어가 회상된 컨텍스트(recalled context) 내에서 이 해결책을 받게 될 것임을 알 수 있습니다.
실제 세션의 로그:
[1:20:26 pm] SUCCESS [OUTPOST] Triage finished successfully in 44.97s. Status 200 OK.
[1:20:27 pm] SUCCESS [OUTPOST] Taught system successfully in 39.57s. Status 200 OK.
트리아지(Triage)와 학습(teach). 이 두 가지 작업이 제품 루프(product loop)의 전부입니다.
Hindsight를 사용하며 배운 점
저는 처음에 메모리(memory)를 저장(storage)의 문제로 생각하며 이 프로젝트에 뛰어들었습니다. 하지만 결과적으로 메모리를 검색 설계(retrieval design)의 문제로 생각하게 되었습니다. 무엇을 저장하느냐보다, 적절한 시점에 적절한 정보가 표면으로 드러나느냐가 더 중요합니다.
Hindsight의 retain/recall API는 작습니다. 두 가지 핵심 작업이 거의 모든 것을 다룹니다. 하지만 회상(recall) 시점에 돌려받는 결과물의 품질은 리테인(retained)된 콘텐츠가 얼마나 잘 구조화되어 있는지에 전적으로 달려 있습니다. 근본 원인(root cause), 성공적인 수정(successful fix), 그리고 실패한 시도(failed attempts)를 명확하게 구분한 사후 분석(postmortem)은 즉시 실행 가능한 회상 결과를 만들어냅니다. 반면 모호한 자유 형식의 텍스트 요약은 노이즈를 생성합니다.
다르게 해보고 싶은 또 다른 점은 지식 베이스(knowledge base)를 더 일찍 구축하는 것입니다. 시스템이 일반적인 LLM보다 확실히 더 낫다고 느껴지려면, 정확한 매칭을 표면화할 수 있을 만큼 충분한 인시던트 이력이 쌓여야 합니다. 150개의 인시던트가 로드되었을 때는 차이가 극명하지만, 5개일 때는 미미합니다. 데이터의 품질과 양은 제품의 일부입니다.
다음 단계
현재의 "시스템 학습시키기 (Teach the System)" 입력 방식은 자유 형식의 텍스트(free text)입니다. 명백한 다음 단계는 구조화된 양식(structured form)을 도입하는 것입니다. 즉, 근본 원인(root cause), 해결 단계(fix steps), 실패한 시도(failed attempts), 그리고 혼란을 가장 적게 일으켰던 고객 메시지(customer message)를 위한 별도의 필드를 만드는 것입니다. 구조화된 입력은 더 일관된 기억(memories)을 생성하며, 이는 더 신뢰할 수 있는 회상(recall)으로 이어집니다.
또한, 이 아키텍처는 단일 지식 저장소(knowledge bank)를 넘어 확장할 여지가 있습니다. 현재는 모든 인시던트(incident)에 걸쳐 공유되는 하나의 조직적 기억(organizational memory)이 존재합니다. 팀별 또는 서비스별로 별도의 메모리 뱅크(memory banks)를 갖춘 멀티 테넌트(multi-tenant) 버전이 구현된다면, 서로 다른 엔지니어링 팀들이 각자의 인시던트 이력을 독립적으로 유지하면서도 필요할 때는 이를 통합하여 쿼리(query)할 수 있게 될 것입니다.
메모리 레이어(memory layer)는 작동합니다. 제가 계속 생각하는 점은, 이 레이어를 통과하는 인시던트가 늘어날수록 성능이 얼마나 더 좋아질 것인가 하는 점, 그리고 대부분의 엔지니어링 팀이 이와 같은 시스템이 즉시 활용할 수 있는 수년간의 인시던트 이력을 보유하고 있다는 사실입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

