백엔드 개발자의 AI 비서 만들기 (2편) — 메시지 하나가 처리되는 과정
TL;DR
- 전체 흐름: Discord 메시지 → Node.js 봇 → Nexus MCP →
claude -p→ Discord 응답. 전부 맥미니 로컬에서 돌아갑니다 - 채널이 11개인 이유: 처음엔 1개였습니다. 섞이는 게 불편해서 하나씩 쪼갰습니다. 채널 = 페르소나 + 컨텍스트
- Nexus MCP 없으면: Claude는 대화만 합니다. 외부를 조회하거나 명령을 실행할 수 없습니다
- 그 차이: “GCP 콘솔 직접 확인해주세요” vs 실제로 조회해서 알려주는 것
- 결과: 질문 → 응답까지 평균 3~8초. 크론 93개는 응답 없이 알아서 처리
채널 1개였을 때의 현실
v0.1 시점, Discord 채널은 하나였습니다. #jarvis-general. 모든 게 여기로 들어왔습니다.
그 채널이 어떻게 생겼는지 보면 이랬습니다.
오전 8:12 Jarvis → 아침 브리핑: 오늘 날씨 맑음, GitHub PR 2개 대기 중, 할 일 4개
오전 10:34 나 → Spring Batch Chunk 방식 전환 시 트랜잭션 롤백 정책 어떻게 잡는 게 좋아?
오전 11:02 Jarvis → Cloud Run mcp-api-server 응답 없음. 확인 필요.
오후 1:17 나 → 저번에 쓰다 말았던 Redis 원자 연산 글 초안, 서론 구조 피드백 해줘
오후 3:45 Jarvis → 디스크 사용량 경고: /dev 82% 도달
오후 5:28 나 → 오늘 블로그 초안 쓸 주제 추천해줘
기술 질문, 인프라 알림, 블로그 초안 작업, 사이드 프로젝트 아이디어가 전부 한 채널에 섞였습니다. Jarvis는 이 맥락들을 구분하지 못했습니다.
실제로 벌어진 사례를 하나 들면, 오후에 “이번 주 블로그 주제 추천해줘”라고 물었더니 이런 답이 왔습니다.
Jarvis
오전에 디스크 사용량이 82%에 달했던 상황을 감안하면, “서버 디스크 관리 자동화” 를 주제로 쓰는 게 어떨까요? 오늘 직접 겪은 경험이라 내용이 구체적으로 나올 것 같습니다.
틀린 말은 아닙니다. 하지만 저는 Spring Batch 최적화 글을 쓰려던 참이었습니다. Jarvis가 오전에 자동으로 올라온 디스크 경고를 “오늘 가장 중요한 일”로 파악하고 거기에 맞춰 추천한 겁니다.
인프라 알림과 블로그 작업이 같은 채널에 있으면 Jarvis는 이 둘을 구분할 방법이 없습니다.
그리고 더 큰 문제가 있었습니다. Jarvis가 어떤 역할을 해야 하는지 불분명해진다는 것.
업무 어시스턴트와 인프라 모니터 봇과 블로그 어시스턴트는 완전히 다른 역할입니다. 하나의 시스템 프롬프트가 이 셋을 동시에 커버하려면 어느 것도 제대로 못 합니다. 결국 분리했습니다.
전체 구조 한눈에
채널을 쪼개면서 구조도 함께 잡혔습니다. 지금 Jarvis의 전체 흐름입니다.
graph TB
subgraph Interface["인터페이스 레이어"]
U["👤 Owner\n(모바일/PC)"]
D["Discord\n11개 채널"]
end
subgraph Local["맥미니 — 항상 켜져 있음"]
BOT["Discord Bot\n(Node.js)"]
NEXUS["Nexus MCP\n컨텍스트 어셈블러"]
RAG["LanceDB\nRAG 12만 청크"]
CRON["launchd\n크론 93개"]
CLI["claude -p\n헤드리스 CLI"]
end
subgraph External["외부"]
GCP["GCP Cloud Run"]
GH["GitHub"]
end
U -- "메시지" --> D
D -- "이벤트" --> BOT
BOT -- "컨텍스트 요청" --> NEXUS
NEXUS -- "관련 기억 검색" --> RAG
NEXUS -- "프롬프트 조립 후 실행" --> CLI
CLI -- "응답" --> BOT
BOT -- "답장" --> D
CRON -- "주기적 실행" --> NEXUS
NEXUS -- "필요 시 조회" --> GCP
NEXUS -- "필요 시 조회" --> GH크게 세 레이어입니다.
- 인터페이스: 사람이 만지는 부분. Discord 채널들.
- 맥미니: 모든 처리가 일어나는 곳. 봇, MCP, RAG, 크론, Claude CLI 전부 여기.
- 외부: 필요할 때 조회하는 외부 서비스.
맥미니가 꺼지면 Jarvis가 꺼지는 이유가 여기 있습니다. 핵심이 전부 로컬입니다.
채널이 11개가 된 과정
채널 쪼개기는 한번에 한 것이 아니라, 불편함이 생길 때마다 하나씩 분리했습니다.
📁 JARVIS
├── 🏢 WORK
│ ├── #jarvis-work ← 업무 / 코드 리뷰 / 기술 질문
│ └── #jarvis-career ← 커리어 / 기술 성장 / 개인 전략
│
├── 📖 BLOG
│ └── #jarvis-blog ← 블로그 글 작성 / 커리어 전략
│
├── ⚙️ OPS
│ ├── #jarvis-infra ← 서버 / 배포 / 인프라
│ ├── #ops-daily ← 일일 인프라 리포트 (자동)
│ └── #jarvis-alerts ← 장애 / 이상 감지 알림 (자동)
│
├── 🤖 AUTOMATION
│ ├── #jarvis-cron-log ← 크론 실행 기록
│ └── #jarvis-tasks ← 할 일 / 일정 관리
│
└── 💬 PERSONAL
├── #jarvis-dm ← 일상 대화 / 잡담
└── #jarvis-morning ← 아침 브리핑 (자동)채널마다 시스템 프롬프트 구성이 다릅니다. #jarvis-work에서는 “당신은 9년차 백엔드 개발자의 코드 리뷰어입니다”가 베이스이고, #jarvis-blog에서는 “당신은 기술 블로그 편집자이자 커리어 어드바이저입니다”가 베이스입니다. 같은 질문이라도 채널에 따라 응답의 결이 달라집니다.
자동화 채널과 대화 채널이 분리된 것도 중요합니다. #ops-daily에서 인프라 리포트가 하루에 한 번씩 올라오는데, 이게 #jarvis-work에 섞이면 기술 질문 스레드를 방해합니다. 분리하니까 각자 채널에서 목적에 맞는 내용만 보이게 됐습니다.
Nexus MCP: Claude에게 손을 붙여주는 것
Claude 자체는 대화만 합니다. 외부 데이터를 가져오거나, 명령을 실행하거나, 파일을 읽을 수 없습니다.
Nexus MCP 없이 인프라 질문을 했을 때와 있을 때를 비교하면 이렇습니다.
Nexus 없이:
나 → #jarvis-infra
지금 Cloud Run 서비스 상태 어때?
Jarvis
죄송합니다, 저는 실시간 인프라 정보에 접근할 수 없습니다. GCP 콘솔에서 직접 확인해주세요.
Nexus 있을 때:
나 → #jarvis-infra
지금 Cloud Run 서비스 상태 어때?
Jarvis (gcloud 실제 조회 후)
mcp-api-server 정상, email-dispatcher 정상입니다. 둘 다 응답 중이고, 마지막 배포는 3일 전입니다. 현재 요청량은 낮은 수준입니다.
“GCP 콘솔 직접 확인해주세요”와 “지금 확인했습니다”의 차이. Nexus가 없으면 AI가 관찰자가 됩니다. Nexus가 있으면 실행자가 됩니다.
비유하자면 이렇습니다. Nexus 없는 Claude는 “저 창고에 재고가 얼마나 있는지 직접 가서 확인해주실 수 있나요?”라고 묻는 직원입니다. Nexus 있는 Claude는 직접 창고에 다녀오는 직원입니다. 차이는 여기서 납니다.
Nexus MCP가 제공하는 주요 도구들입니다.
| 도구 | 역할 | 실제 쓰임 |
|---|---|---|
exec | 쉘 명령 실행 | gcloud 조회, launchctl 상태 확인 |
scan | 파일/디렉토리 병렬 스캔 | 로그 파일 빠른 탐색 |
cache_exec | TTL 캐시 적용 실행 | 같은 조회 반복 호출 방지 |
log_tail | 로그 끝 부분 읽기 | 서비스 에러 추 적 |
health | 서비스 헬스체크 | 배포 후 정상 확인 |
rag_search | RAG 벡터 검색 | “저번에 말한 것” 꺼내오기 |
Claude가 이 도구들을 필요에 따라 스스로 선택해서 씁니다. 인프라 질문이 오면 자동으로 exec를 호출하고, 이전 대화 참조가 필요하면 자동으로 rag_search를 씁니다.
메시지 하나가 처리되는 과정
“오늘 PR 상태 어때?”라고 보내면 내부에서는 이렇게 흘러갑니다.
sequenceDiagram
participant U as 👤 Owner
participant D as Discord
participant B as Bot (Node.js)
participant N as Nexus MCP
participant R as LanceDB (RAG)
participant C as claude -p
U->>D: "오늘 PR 상태 어때?"
D->>B: messageCreate 이벤트
B->>N: 채널 컨텍스트 + 메시지 전달
N->>R: 관련 기억 검색
R-->>N: 관련 청크 반환
N->>N: 시스템 프롬프트 조립
N->>C: 완성된 프롬프트로 claude -p 실행
C->>N: exec("gh pr list") 호출
N-->>C: PR 목록 반환
C-->>B: 최종 응답 생성
B-->>D: 메시지 전송
D-->>U: "현재 열린 PR 3개..."사용자 입장에서는 3~8초. 맥미니에서 전부 돌아가는 것치고 나쁘지 않은 속도입니다.
크론은 다르다
대화 흐름은 위와 같지만, 크론 93개는 이 흐름을 타지 않습니다.
크론은 응답을 기다리는 주체가 없습니다. launchd가 정해진 시간에 스크립트를 실행하고, 결과를 Discord 특정 채널에 포스팅하거나 로그에 남깁니다.
graph LR
L["launchd\n(스케줄러)"] -- "오전 8시" --> S["morning-brief.sh"]
S --> N["Nexus MCP"]
N --> G["GitHub API\n태스크\n날씨"]
N --> C["claude -p\n(요약 생성)"]
C --> D["Discord\n#jarvis-morning"]대화 채널과 자동화 채널이 분리돼 있는 이유가 여기 있습니다.
수치로 정리
| 항목 | 수치 |
|---|---|
| Discord 채널 수 | 11개 |
| 활성 크론 수 | 93개 |
| RAG 청크 수 | 12만+ |
| 평균 응답 시간 | 3~8초 |
| 월 비용 | $103 (Max $100 + GCP ~$3) |
$3이 GCP 비용 전부입니다. 블로그 어드민 API 하나를 Cloud Run에 올린 것 외에는 전부 맥미니 로컬에서 돌아갑니다. 구조 대비 비용이 낮은 이유입니다. 클라우드에 많이 올릴수록 비용이 올라가는 구조라, 의도적으로 로컬에 최대한 두고 있습니다.
마치며: 구조는 문제가 생길 때 진화한다
채널 11개를 처음부터 설계한 게 아닙니다. 1개에서 시작해서 불편할 때마다 쪼갰습니다. Nexus MCP도, RAG도 마찬가지입니다. 없을 때 불편해서 추가했고, 붙이고 나서야 “이게 왜 필요했는지” 알게 됐습니다.
다음 편에서는 RAG 장기기억을 다룹니다.
첫 번째 시도는 4주 만에 토큰 한도를 초과했습니다. 두 번째 시도는 두 달 만에 AI 자체가 이상하게 동작하기 시작했습니다. 세 번째 시도가 됐을 때, 면접 당일 아침에 Jarvis가 먼저 오늘 면접이라고 말을 걸어왔습니다.
두 번의 실패에서 배운 게 있습니다.
참고 :
https://www.anthropic.com
https://github.com/ramsbaby/openclaw