로컬 AI 인프라 노트 (5/15) — 홈서버 대시보드 설계: Next.js 멀티 에이전트 모니터링

에이전트가 보내는 것을 시각화할 뿐, 직접 제어하지 않는다


핵심 요약

  • 멀티 에이전트 환경에서는 개별 터미널로 상태를 추적하는 방식이 확장되지 않는다. 대시보드는 운영 규모가 커질수록 필수 인프라가 된다
  • Next.js + SQLite 조합은 홈서버 대시보드에 적합하다. 서버사이드 렌더링으로 별도 프론트 빌드 없이 동작하고, 외부 DB 의존이 없다
  • 핵심 설계 원칙은 "대시보드는 읽기 전용". 에이전트를 제어하는 기능을 넣으면 복잡도가 폭발한다

왜 멀티 에이전트 모니터링이 필요한가

에이전트 수가 늘어날수록 "지금 어떤 에이전트가 활성 상태인가?", "마지막으로 인사이트를 보낸 건 언제인가?", "주식 시그널이 최근에 들어왔는가?"라는 질문에 답하기 위해 여러 터미널과 로그 파일을 수동으로 뒤져야 한다. 운영 규모가 일정 임계치를 넘으면 이 방식은 사실상 불가능해진다.

대시보드의 역할은 이 정보를 단일 인터페이스로 집계하는 것이다. 기술적 흥미가 아니라 운영 가능성(operability) 이 설계의 출발점이다.


본문

1. 왜 Next.js + SQLite인가

스택 선택 기준은 세 가지였습니다:

  1. 단일 프로세스로 동작할 것 — 홈서버 리소스가 제한적이기 때문에 DB 서버를 따로 띄우고 싶지 않았습니다
  2. 서버사이드 렌더링 — API와 프론트를 한 프로젝트에서 관리하고 싶었습니다
  3. 빠른 프로토타이핑 — 에이전트 운영이 급한 상황에서 인프라에 시간을 쏟을 수 없었습니다

Next.js는 1, 2를 충족합니다. API Routes로 백엔드 엔드포인트를 만들 수 있고, 같은 프로젝트에서 페이지 렌더링까지 합니다. SQLite는 파일 하나가 데이터베이스이므로 3을 충족합니다. PostgreSQL이나 MySQL 같은 서버 프로세스가 필요 없습니다.

검토했지만 탈락한 대안들:

  • Grafana + Prometheus: 모니터링 전용이라 커스텀 위젯이 제한적. 에이전트 메시지 같은 비정형 데이터를 표시하기 어려움
  • Express + React: 프론트/백 분리가 홈서버에서는 오버엔지니어링
  • 정적 HTML + cron 생성: 실시간성이 없음

2. 대시보드 주요 섹션

대시보드는 4개 섹션으로 구성됩니다.

에이전트 상태 표시

등록된 에이전트의 현재 상태를 카드 형태로 보여줍니다. 상태는 세 가지: 활성(Active), 중단(Suspended), 완료(Complete). 각 카드에는 에이전트 이름, 역할, 기술 스택, 마지막 활동 시간이 표시됩니다.

상태 판단 로직은 webhooks 수신 이력 기반입니다. 최근 폴링 주기 내 인사이트나 메시지가 있으면 활성 처리, 없으면 에이전트 설정에 정의된 기본 상태를 표시합니다. 활성 판정 임계치는 에이전트 유형별로 설정 파일에서 조정할 수 있습니다.

인사이트 타임라인

에이전트들이 webhooks로 보내온 인사이트를 시간순으로 나열합니다. 각 인사이트에는 소스 에이전트, 태그, 타임스탬프가 표시됩니다. 필터 기능으로 특정 에이전트나 태그의 인사이트만 볼 수 있습니다.

이 타임라인이 대시보드의 핵심입니다. "지금 시스템에서 무슨 일이 일어나고 있는가"를 한눈에 보여줍니다.

주식 시그널 위젯

주식 관련 에이전트가 보내는 시그널을 별도 위젯으로 분리했습니다. 종목명, 시그널 타입(매수/매도/관망), 점수, 수신 시간이 표시됩니다. 일반 인사이트와 분리한 이유는 업데이트 빈도가 다르고, 시각적으로 즉시 확인해야 하는 데이터이기 때문입니다.

에이전트 메시지 로그

에이전트 간 메시지를 로그 형태로 보여줍니다. 발신자, 수신자, 메시지 타입(task_request, insight, status_update 등), ACK 상태가 표시됩니다. 메시지가 ACK되지 않으면 시각적으로 강조됩니다.

3. 데이터 흐름 — webhooks 수신 → SQLite 저장 → 시각화

전체 데이터 흐름은 단방향입니다:

에이전트 → POST /api/webhooks → SQLite 저장 → 대시보드 렌더링

에이전트는 작업 중 발생하는 인사이트나 상태 변화를 webhooks으로 보냅니다. 대시보드의 API 엔드포인트가 이를 수신하고, SQLite에 저장하고, 페이지 렌더링 시 최신 데이터를 읽어서 표시합니다.

실시간 업데이트는 폴링 방식을 사용합니다. WebSocket을 고려했지만, 에이전트 이벤트 빈도가 분당 수 회 수준이라 30초 폴링으로 충분합니다. 복잡도 대비 실익이 없어서 단순한 방식을 선택했습니다.

4. API 엔드포인트 설계

대시보드의 API는 세 개의 핵심 엔드포인트로 구성됩니다.

POST /api/webhooks — 인사이트 수신

{
  "content": "Qwen3 모델 벤치마크 완료. 이전 대비 15% 성능 향상.",
  "tags": ["benchmark", "qwen3"],
  "source": "model-manager"
}

에이전트가 fire-and-forget으로 보내는 엔드포인트. ACK가 필요 없는 단방향 통신입니다. 수신 시 타임스탬프를 붙여 SQLite에 저장합니다.

GET /api/events — 이벤트 조회

대시보드 프론트엔드가 호출하는 읽기 엔드포인트. 쿼리 파라미터로 소스, 태그, 기간 필터링을 지원합니다. 페이지네이션 포함.

POST /api/agent-messages — 에이전트 간 메시지

{
  "fromAgent": "research-agent",
  "toAgent": "quality-agent",
  "messageType": "task_request",
  "intent": "fact-check requested",
  "body": { "text": "소스 파일 검증 요청" },
  "dedupeKey": "fc-20260405-001"
}

webhooks와 달리 ACK가 필요한 양방향 통신입니다. 수신 에이전트가 메시지를 확인하면 ACK를 보내야 하고, 미확인 메시지는 대시보드에서 시각적으로 강조됩니다.

5. 설계 원칙 — "시각화만, 제어는 안 한다"

대시보드 설계에서 가장 중요하게 지킨 원칙: 대시보드는 에이전트가 보내는 것을 시각화할 뿐, 직접 제어하지 않는다.

에이전트 시작/중지 또는 작업 할당 기능을 넣으면 세 가지 문제가 발생합니다:

  1. 제어를 넣으면 상태 동기화 문제가 생긴다 — 대시보드에서 에이전트를 중지했는데, 에이전트가 자체 복구하면? 상태가 불일치합니다
  2. 에이전트마다 제어 인터페이스가 다르다 — Flutter 앱, Python 스크립트, Node.js 서버를 동일한 인터페이스로 제어하는 것은 추상화 비용이 큽니다
  3. 읽기 전용이면 버그가 치명적이지 않다 — 대시보드에 버그가 있어도 에이전트가 영향받지 않습니다

이 원칙 덕분에 대시보드 개발이 단순해집니다. 수신 → 저장 → 표시. 이 세 가지만 합니다.

에이전트 제어가 필요하면 각 에이전트의 프로젝트에서 직접 합니다. 대시보드는 전체 상황을 보는 곳이지, 조작하는 곳이 아닙니다.


주요 트러블슈팅

SQLite 동시 쓰기 문제. 여러 에이전트가 동시에 webhooks를 보내면 "database is locked" 에러가 발생합니다. WAL(Write-Ahead Logging) 모드를 활성화하면 해결됩니다. PRAGMA journal_mode=WAL; 한 줄로 적용되며, 동시 읽기/쓰기 처리량이 크게 개선됩니다.

폴링 주기 최적화. 폴링 주기를 너무 짧게 설정하면 SQLite 읽기 부하가 과도해집니다. 에이전트 이벤트는 초 단위 실시간성이 필요하지 않으므로, 30초 폴링이 운영상 적절합니다. 이벤트 빈도가 높은 위젯은 별도 폴링 주기를 설정할 수 있습니다.

에이전트 이름 정규화. 에이전트 식별자에 한글과 공백이 포함되면 URL 파라미터, 필터, SQLite 쿼리에서 인코딩 문제가 반복됩니다. 내부적으로는 영문 slug를 사용하고, 표시 레이어에서만 한글명을 매핑하는 방식으로 해결됩니다.


마무리

홈서버 대시보드 설계의 핵심을 정리하면:

  1. Next.js + SQLite로 단일 프로세스 대시보드를 구성한다
  2. 에이전트 상태, 인사이트 타임라인, 주식 시그널, 메시지 로그의 4개 섹션으로 나눈다
  3. webhooks 수신 → SQLite 저장 → 페이지 렌더링의 단방향 흐름을 유지한다
  4. 대시보드는 시각화만 한다. 에이전트 제어 기능을 넣지 않는다

마지막 원칙이 가장 중요합니다. 대시보드에 제어를 넣고 싶은 유혹은 강하지만, 그 순간 단순한 모니터링 도구가 복잡한 오케스트레이션 플랫폼이 됩니다. 모니터링과 제어는 설계 레이어에서 분리해야 합니다.

시리즈 전체 안내: 시리즈 목차

댓글