본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 30. 15:52

ReefWatch 구축하기: Coral 기반의 프로덕션 트리아지 (Triage) 에이전트

요약

Coral을 기반으로 구축된 ReefWatch는 단순한 챗봇을 넘어 실제 시스템 데이터를 조사하는 프로덕션 트리아지 에이전트입니다. GitHub, Sentry, Slack 등 다양한 도구의 데이터를 SQL로 쿼리하고 상관 분석하여 증거 기반의 장애 보고서를 생성합니다.

핵심 포인트

  • Coral을 활용해 에이전트 툴링을 SQL 형태로 변환하여 추론 가능하게 구현
  • 단순 할 일 목록이 아닌 실제 시스템 데이터 기반의 증거 중심 조사 수행
  • 코드, 배포, 에러, 알림 등 다양한 소스 간의 상관관계 분석 기능
  • 정책 계층(policy layers)을 통해 거대한 프롬프트 없이도 집중력 유지

프로덕션 장애(Production incidents)는 거의 단 한 곳에서만 발생하지 않습니다.

알람은 한 도구에서 울립니다. 잘못된 배포(deploy)는 Netlify에 있습니다. 의심스러운 변경 사항은 GitHub에 있습니다. 스택 트레이스(stack trace)는 Sentry에 있습니다. 상황에 대한 인간의 맥락은 Slack에 있습니다. 런북(runbook)은 Notion에 있습니다. "이게 정말 누군가에게 페이징(paging)을 보내고 있는 건가?"에 대한 답은 PagerDuty에 있습니다.

일반적인 챗봇(chatbot)은 그런 상황에서 도움이 되는 것처럼 보일 수 있습니다. "최근 배포를 확인해야 합니다"라거나 "Sentry에서 관련 에러를 찾아보세요"와 같은 말을 할 수 있죠.

하지만 그것은 트리아지 (triage)가 아닙니다. 그것은 그럴싸하게 포장된 할 일 목록(to-do list)일 뿐입니다.

저는 더 유용한 것을 원했습니다. 증거를 직접 가져오고, 여러 소스 간의 점들을 연결하며, 작업 과정을 보여주고, 실제 시스템 데이터에 기반하여 운영자 수준의 답변을 제공할 수 있는 에이전트(agent) 말입니다.

처음부터 설계 제약 조건은 간단했습니다: 증거가 없으면 답변도 없다.

그것이 바로 즉흥적으로 대처하는 대신 조사하도록 구축된 Coral 기반의 프로덕션 트리아지 (triage) 에이전트인 ReefWatch가 되었습니다.

GH

ReefWatch는 런타임(runtime)에 워크스페이스에 연결된 도구들을 발견하고, 이를 증거로서 쿼리(query)하며, 시스템 간의 기록을 상관 분석(correlate)하고, 사실 관계가 뒷받침될 때만 간결한 답변을 생성합니다.

Coral은 에이전트 툴링(agent tooling)에서 가장 무질서한 부분을 모델이 실제로 추론할 수 있는 형태인 SQL로 변환해주기 때문에 중추(backbone) 역할을 하게 되었습니다.

이 가이드에서 구축하는 것

이 경로를 마칠 때쯤, 여러분은 다음과 같은 기능을 수행할 수 있는 에이전트의 청사진을 갖게 될 것입니다:

  • 런타임(runtime) 시점에 연결된 Coral 소스를 발견
  • 읽기 전용 SQL을 통해 프로덕션 시스템에 쿼리 수행
  • 코드, 배포(deploys), 에러, 알림(alerts), 채팅, 런북(runbooks) 전반에 걸친 증거 상관관계 분석
  • 모든 쿼리와 행 수(row count)를 검사 가능한 UI로 스트리밍
  • 스크립트화 가능한 경로가 필요할 때 CLI에서 동일한 조사 워크플로우 실행
  • 증거가 뒷받침될 때만 장애 보고서(incident report) 생성
  • 거대한 프롬프트 덩어리(prompt blob) 대신 정책 계층(policy layers)을 통해 집중력 유지

Investigation Workspace

한 문장으로 요약하자면:

ReefWatch는 Coral 기반의 조사 워크스페이스로, 에이전트가 런타임에 연결된 도구를 발견하고, 읽기 전용 SQL로 이를 쿼리하며, 증거 추적 경로를 스트리밍하고, 사실 관계가 실제로 뒷받침될 때만 장애 보고서를 생성할 수 있게 해줍니다.

The ReefWatch Flow

왜 Coral이 중심에 있어야 하는가

MCP는 통합 계층(integration layer)으로서 매우 뛰어납니다. MCP는 모델이 UI 글루(UI glue)를 통해 인간의 동작을 스크래핑하는 대신, 스키마(schema)를 사용하여 도구를 호출할 수 있는 방법을 제공합니다.

하지만 모든 소스가 별도의 맞춤형 도구(bespoke tools) 모음이 된다면, 새로운 문제가 발생합니다:

  • 모델이 수많은 도구 형태를 학습해야 함
  • 모든 API가 서로 다른 페이지네이션(pagination)과 필터(filters)를 가짐
  • 소스 간의 레코드 조인(joining records)이 프롬프트 작업이 되어버림
  • 소스별 에러가 에이전트 루프(agent loop)로 직접 유출됨
  • 새로운 통합이 추가될 때마다 앱이 더 많은 통합 로직을 관리해야 함

Coral은 이 추상화(abstraction)를 바꿉니다.

Coral은 여전히 MCP를 사용하지만, 에이전트는 주로 안정적인 소수의 기능 세트만을 보게 됩니다: 카탈로그 발견(discover catalog), 스키마 검사(inspect schema), 가이드 읽기(read the guide), 그리고 SQL 실행(run SQL).

즉, 새로운 소스가 추가된다는 것은 다음과 같은 의미가 아닙니다:

ReefWatch에 또 다른 SDK를 가르치는 것

대신 다음과 같이 됩니다:

Coral 소스를 설치하고 -> 테이블을 발견하며 -> SQL로 증거를 쿼리합니다.

Tool-first flow

ReefWatch flow using Coral

실질적인 이점은 지루할 정도로 좋습니다: ReefWatch가 작게 유지될 수 있습니다.

이 앱은 GitHub 페이지네이션, Sentry 인증(auth), Slack 테이블 구조(table shapes), 또는 Netlify 배포 스키마를 소유하지 않습니다.

Coral가 그것을 소유합니다. ReefWatch는 조사 행동(investigation behavior)을 소유합니다.

이러한 분리는 또한 제가 신뢰할 수 있는 에이전트를 구축하는 방식과 잘 일치합니다: 실제 환경 피드백에 모델을 기반하고, 도구를 조합 가능하게 유지하며, 작업을 추적하고, 완벽한 프롬프트가 영원히 작동하기를 기대하기보다는 작은 가드레일(guardrails)로 루프를 감싸는 것입니다.

MCP는 에이전트에게 손을 제공합니다. Coral은 지도와 쿼리 언어를 제공합니다.

구축 경로 (The Build Path)

만약 제가 ReefWatch를 처음부터 다시 구축한다면, UI로 시작하지 않을 것입니다.

저는 조사 파이프라인으로 시작하여 각 계층이 그 자리를 얻을 만한 가치를 증명하도록 만들 것입니다.

기억하세요. 표면(surface)에서 시작하는 것이 유혹적이지만, 이미 신뢰할 가치가 있는 시스템을 반영하도록 표면을 만들어야 합니다.

프로젝트는 여덟 개의 슬라이스(slices)로 완성되었습니다:

슬라이스제가 구축한 것중요했던 이유
1Coral MCP 클라이언트Coral이 데이터 평면(data plane)이 될 수 있음을 증명함
...
Build Path

최종 프로젝트 구조는 대략 다음과 같습니다:

(SQLite 스토어와 React 프론트엔드를 가진 FastAPI 백엔드)

reefwatch/
|-- src/
|   |-- api/
... 

이러한 구조는 제가 문제를 해결했던 순서에서 비롯되었습니다.

Slice 1: Coral이 데이터 플레인 (Data Plane)이 될 수 있음을 증명하기

첫 번째 백엔드 슬라이스(Slice)는 의도적으로 작게 구성했습니다.

저는 한 가지 질문에 답하고 싶었습니다:

ReefWatch가 Coral을 운영상의 진실의 원천 (Source of operational truth)으로 취급할 수 있는가?

첫 번째 증명 과정은 다음과 같았습니다:

  1. coral mcp-stdio 실행
  2. JSON-RPC를 통해 MCP 초기화
  3. coral://tables 읽기
  4. sql 도구 호출
  5. 일반 API 엔드포인트로 행(rows) 반환

그 시점에서 ReefWatch는 아직 에이전트가 아니었습니다. 그것은 얇은(thin) Coral 클라이언트였습니다.

이는 가장 중요한 가설을 증명했기에 유용했습니다: 앱이 GitHub, Slack, Sentry 및 기타 모든 소스에 대해 직접적인 SDK 통합을 구축하는 대신, Coral을 데이터 플레인 (Data Plane)으로 취급할 수 있다는 점입니다.

첫 번째 재사용 가능한 모듈은 mcp_client.py였습니다.

이 모듈은 지루하지만 필수적인 전송(transport) 세부 사항을 담당합니다:

  • Coral 바이너리 실행 (spawn)
  • stdio를 통한 JSON-RPC 통신
  • MCP 도구를 OpenAI 호환 함수 도구 (function tools)로 변환
  • coral://guidecoral://tables와 같은 Coral 리소스 읽기
  • stderr 및 디코딩 오류를 명확하게 노출

설계 결정 (Design decision): 전송 계층은 지루하게 유지할 것. mcp_client.py가 작동하게 되면, 앱의 나머지 부분은 프로세스에 대한 고민을 멈추고 조사 (investigations)에 집중할 수 있습니다.

Slice 2: Coral을 활성 상태로 유지하기 (Keep Coral Warm)

단순한 접근 방식은 다음과 같을 것입니다:

사용자 질문 -> Coral 실행 -> 스키마 발견 -> 모델에게 질문 -> SQL 실행

스크립트라면 괜찮습니다.

하지만 제품 (Product) 관점에서는 매끄럽지 않게 느껴집니다.

따라서 두 번째 슬라이스는 coral_session.py였습니다. 이 모듈은 하나의 Coral 프로세스를 계속 살아있는 상태로 유지하고, 스키마/가이드/도구 캐시를 워밍업(warm)하며, 프로세스가 종료되면 다시 생성합니다.

이를 통해 ReefWatch는 더 깔끔한 런타임 구조를 갖게 되었습니다:

앱 시작 -> Coral을 한 번 워밍업 -> 조사가 세션을 재사용

Agent loop

세션 캐시는 세 가지를 저장합니다:

  • Coral 소스 스키마 (source schema)
  • Coral 가이드 (guide)
  • OpenAI 호환 도구 정의 (tool definitions)

그 하나의 결정이 제품의 느낌을 다르게 만들었습니다.

모든 사용자 프롬프트가 MCP 부트스트래핑 (bootstrapping)과 카탈로그 탐색 (catalog discovery)을 기다리는 대신,
ReefWatch는 사용 가능한 소스들의 워밍업된 맵 (warm map)에서 시작합니다.

여전히 폴백 (fallback) 경로가 존재합니다. 프로세스가 중단될 경우, CoralSession이 클라이언트를 재생성하고 캐시를 다시 워밍업할 수 있습니다.

MCP 클라이언트는 UTF-8 디코딩을 통해 stdio 상에서 JSON-RPC를 읽고 쓰며, 백그라운드 스레드에서 stderr를 비우고, 조용히 멈춰있는 대신 유용한 전송 오류 (transport errors)를 보고합니다.

자신의 배관 (plumbing) 작업 때문에 무작위로 대기하는 프로덕션 트리아지 (triage) 에이전트는 프로덕션 트리아지 에이전트가 아닙니다.

Slice 3: 메모리가 아닌 Coral로부터 스키마 컨텍스트 구축하기

소스 스키마 (source schemas)를 하드코딩하는 것은 Coral을 사용하는 목적을 무색하게 만들 것입니다.

에이전트는 지금 당장 무엇이 설치되어 있는지 발견해야 합니다.

다음과 같이 직접 작성한 프롬프트를 쓰고 싶은 유혹이 있었습니다:

GitHub에는 이러한 테이블들이 있습니다. Sentry에는 이러한 테이블들이 있습니다. Slack은 채널 ID를 사용합니다.

그렇게 했다면 ReefWatch는 취약해지고 Coral 네이티브 (Coral-native) 방식에서 벗어났을 것입니다.

이것은 아키텍처가 기반이 되는 도구를 존중하느냐, 아니면 조용히 우회하느냐를 결정하는 작은 선택 중 하나였습니다.

대신, ReefWatch는 Coral 자체로부터 프롬프트 컨텍스트 (prompt context)를 구축합니다.

coral://tables를 읽고, 그 결과를 coral.columns로 풍부하게 만들며, 소스별로 테이블을 그룹화하고, 각 소스의 압축된 슬라이스 (compact slice)만을 프롬프트에 포함합니다.

SELECT schema_name, table_name, column_name, data_type
FROM coral.columns
ORDER BY schema_name, table_name, ordinal_position

핵심 아이디어: 모델은 미로가 아닌 지도를 받습니다.

소스 카탈로그 (source catalog)가 작으면 모델은 대부분을 볼 수 있습니다.

카탈로그가 크면 모델은 시작하기에 충분한 정보를 받고, 나머지는 Coral 탐색 도구 (discovery tools)를 사용할 수 있습니다.

이를 통해 앱이 모든 소스에 대해 영구적인 지식을 가지고 있는 척하지 않으면서도 프롬프트를 유용하게 유지할 수 있습니다.

Slice 4: 가장 작고 유용한 에이전트 루프로 시작하기

Coral 전송 (transport)과 스키마 컨텍스트가 작동한 후에야 에이전트 루프 (agent loop)를 구축했습니다.

agent/loop.py의 첫 번째 버전은 단 하나의 작업만을 수행했습니다:

messages -> LLM tool call -> Coral SQL -> tool result -> final answer

그 버전은 의도적으로 단순했습니다.

이를 통해 가공되지 않은 실패 모드 (failure modes)를 확인할 수 있었습니다:

  • 행동 대신 조언 (advice instead of action): 모델이 쿼리를 수행하는 대신 지침(instructions)으로 답변함
  • 증거 대신 카탈로그 (catalog instead of evidence): 테이블을 사용하는 대신 테이블 목록을 나열함
  • 불필요한 확인 (unnecessary clarification): GitHub 인증 (GitHub auth)을 통해 확인할 수 있는 리포지토리 이름을 물어봄
  • 단일 차선 터널 시야 (single-lane tunnel vision): 하나의 소스만 쿼리하고 모든 것을 조사했다고 주장함
  • 거짓 음성 (false negatives): 필터링되어 결과가 0건인 쿼리를 증거가 없다는 증거로 취급함

이러한 실패들은 유용했습니다.

어떤 부분이 프롬프트 (prompt)에 포함되어야 하고, 어떤 부분이 코드 수준의 정책 (code-level policy)으로 다뤄져야 하는지를 보여주었기 때문입니다.

시스템에 구조가 필요한 지점을 알려준다면, 첫 번째 에이전트 실행의 실패는 낭비된 시간이 아닙니다.

Slice 5: 루프 주변에 정책 추가하기 (Add Policy Around The Loop)

이것이 진정한 전환점이었습니다.

저는 하나의 영웅적인 시스템 프롬프트 (system prompt)가 모든 것을 수행하도록 만드는 시도를 중단했습니다.

대신, 에이전트의 동작을 집중된 모듈 (modules)로 분리했습니다:

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0