<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Fastapi on Yarang's Tech Lair</title><link>https://blog.agentthread.dev/ko/tags/fastapi/</link><description>Recent content in Fastapi on Yarang's Tech Lair</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Tue, 05 May 2026 00:30:00 +0900</lastBuildDate><atom:link href="https://blog.agentthread.dev/ko/tags/fastapi/index.xml" rel="self" type="application/rss+xml"/><item><title>[AgentForge] 블로그 자동화 서비스 전체 아키텍처 — AI 댓글, 번역, 포스트 생성까지</title><link>https://blog.agentthread.dev/ko/post/2026-05-05-001-agentforge-blog-automation-architecture/</link><pubDate>Tue, 05 May 2026 00:30:00 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/2026-05-05-001-agentforge-blog-automation-architecture/</guid><description>&lt;p&gt;블로그를 운영하면서 가장 번거로운 작업 세 가지가 있습니다. 댓글에 답하기, 영문 번역 유지하기, 그리고 꾸준히 글 쓰기. &lt;a class="link" href="https://github.com/yarang" target="_blank" rel="noopener"
 &gt;AgentForge&lt;/a&gt; 프로젝트에서는 이 세 가지를 모두 AI 에이전트로 자동화했습니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 2개 서버에 걸쳐 동작하는 블로그 자동화 서비스의 전체 아키텍처를 정리합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="시스템-토폴로지"&gt;시스템 토폴로지
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;┌─────────────────────┐ HTTPS ┌─────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ arm1 서버 │ ──────────────▶ │ ec1 서버 │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ (에이전트 오퍼레이터) │ │ (블로그 호스팅) │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├─────────────────────┤ ├─────────────────────┤
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ blog-agent (:8081) │ │ Hugo (nginx) │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ ├─ CommentHandler │ │ Blog API (:8000) │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ ├─ TranslateHandler│ │ ├─ translator.py │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ └─ PostGenerator │ │ ├─ blog_manager.py │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ │ └─ git_handler.py │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ NATS / PostgreSQL │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ Prometheus / Grafana │ │ Git (yarang/blogs) │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└─────────────────────┘ └─────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;서버&lt;/th&gt;
					&lt;th&gt;역할&lt;/th&gt;
					&lt;th&gt;핵심 서비스&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;arm1&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;에이전트 오퍼레이터&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;blog-agent.service&lt;/code&gt; — Flask + Scheduler + LLM Client&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ec1&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;블로그 호스팅 + API&lt;/td&gt;
					&lt;td&gt;Hugo (nginx) + &lt;code&gt;blog-api.service&lt;/code&gt; (FastAPI)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;두 서버 간 통신은 &lt;strong&gt;HTTPS API 호출만&lt;/strong&gt; 가능합니다. arm1에서 ec1로의 SSH 접속은 차단되어 있어, 모든 연동은 Blog API를 통해 이루어집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="arm1-통합-블로그-에이전트"&gt;arm1: 통합 블로그 에이전트
&lt;/h2&gt;&lt;h3 id="왜-통합했는가"&gt;왜 통합했는가
&lt;/h3&gt;&lt;p&gt;초기에는 댓글 응답, 번역, 포스트 생성이 각각 독립 프로세스(3개 systemd 서비스)로 운영되었습니다. 문제는:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude Code CLI(&lt;code&gt;--print&lt;/code&gt;) 호출 방식으로 &lt;strong&gt;응답 시간 9.7초&lt;/strong&gt;, 디스크 688MB 소모&lt;/li&gt;
&lt;li&gt;systemd 유닛 6개 관리 부담&lt;/li&gt;
&lt;li&gt;프로세스 간 상태 공유 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 &lt;strong&gt;1개 프로세스&lt;/strong&gt;로 통합하면서 직접 LLM API 호출로 전환했습니다. 결과:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;지표&lt;/th&gt;
					&lt;th&gt;Before&lt;/th&gt;
					&lt;th&gt;After&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;응답 시간&lt;/td&gt;
					&lt;td&gt;9.7초&lt;/td&gt;
					&lt;td&gt;1.7초&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;디스크 사용&lt;/td&gt;
					&lt;td&gt;688MB&lt;/td&gt;
					&lt;td&gt;~50MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;systemd 유닛&lt;/td&gt;
					&lt;td&gt;6개&lt;/td&gt;
					&lt;td&gt;1개&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;프로세스&lt;/td&gt;
					&lt;td&gt;3개&lt;/td&gt;
					&lt;td&gt;1개&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="아키텍처"&gt;아키텍처
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BlogAgent&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;1 프로세스 = Flask (webhook) + Scheduler (timer) + LLM Client&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;config &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AgentConfig&lt;span style="color:#f92672"&gt;.&lt;/span&gt;from_credentials()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;llm &lt;span style="color:#f92672"&gt;=&lt;/span&gt; LLMClient(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;config) &lt;span style="color:#75715e"&gt;# ZAI glm-4.7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;api &lt;span style="color:#f92672"&gt;=&lt;/span&gt; BlogAPIClient(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;config) &lt;span style="color:#75715e"&gt;# ec1 Blog API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 핸들러&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;comment &lt;span style="color:#f92672"&gt;=&lt;/span&gt; CommentHandler(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;llm, self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;config)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;translate &lt;span style="color:#f92672"&gt;=&lt;/span&gt; TranslateHandler(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;api)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post_gen &lt;span style="color:#f92672"&gt;=&lt;/span&gt; PostGenerator(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;llm, self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;api)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 스케줄러&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;scheduler &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Scheduler()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;scheduler&lt;span style="color:#f92672"&gt;.&lt;/span&gt;every(hours&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, task&lt;span style="color:#f92672"&gt;=&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;translate&lt;span style="color:#f92672"&gt;.&lt;/span&gt;check_and_sync)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;scheduler&lt;span style="color:#f92672"&gt;.&lt;/span&gt;daily_at(hour&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;, task&lt;span style="color:#f92672"&gt;=&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post_gen&lt;span style="color:#f92672"&gt;.&lt;/span&gt;generate_and_publish)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="모듈별-동작"&gt;모듈별 동작
&lt;/h3&gt;&lt;h4 id="1-commenthandler--ai-댓글-응답"&gt;1. CommentHandler — AI 댓글 응답
&lt;/h4&gt;&lt;p&gt;GitHub Discussions의 Webhook 이벤트를 수신하여 자동으로 AI 댓글을 생성합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[사용자 댓글] → GitHub Webhook → arm1 Flask → CommentHandler
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → LLM 호출 (ZAI glm-4.7) → 답변 생성 → GitHub API로 댓글 게시
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;트리거&lt;/strong&gt;: Webhook 이벤트 기반 (실시간)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;필터링&lt;/strong&gt;: 블로그 소유자 댓글, AI 생성 댓글은 건너뜀&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보안&lt;/strong&gt;: HMAC-SHA256 Webhook 시크릿 검증, Flask-Limiter 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-translatehandler--자동-번역-트리거"&gt;2. TranslateHandler — 자동 번역 트리거
&lt;/h4&gt;&lt;p&gt;6시간마다 ec1의 Blog API에 번역 동기화를 요청합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[Scheduler 6h] → TranslateHandler.check_and_sync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → POST /translate/sync → ec1 Blog API가 실제 번역 수행
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;arm1은 번역을 직접 수행하지 않고, ec1 API에 &lt;strong&gt;트리거만&lt;/strong&gt; 보냅니다. 실제 번역 로직은 ec1의 &lt;code&gt;translator.py&lt;/code&gt;에 있습니다.&lt;/p&gt;
&lt;h4 id="3-postgenerator--자동-포스트-생성"&gt;3. PostGenerator — 자동 포스트 생성
&lt;/h4&gt;&lt;p&gt;매일 오전 9시에 기술 블로그 포스트를 자동 생성합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[Scheduler 09:00 KST] → PostGenerator.generate_and_publish()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → 기존 주제 수집 → RSS 트렌드 참조 → LLM으로 콘텐츠 생성
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → 중복 검사 → Blog API로 게시
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;중복 방지&lt;/strong&gt;가 핵심입니다. &lt;code&gt;difflib.SequenceMatcher&lt;/code&gt;로 새 제목과 최근 100개 기존 제목의 유사도를 비교합니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_is_duplicate_title&lt;/span&gt;(self, new_title, existing_titles):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;threshold 0.6 이상이면 중복으로 판정&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; new_lower &lt;span style="color:#f92672"&gt;=&lt;/span&gt; new_title&lt;span style="color:#f92672"&gt;.&lt;/span&gt;lower()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strip()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; title &lt;span style="color:#f92672"&gt;in&lt;/span&gt; existing_titles[&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;:]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ex_lower &lt;span style="color:#f92672"&gt;=&lt;/span&gt; title&lt;span style="color:#f92672"&gt;.&lt;/span&gt;lower()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strip()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ratio &lt;span style="color:#f92672"&gt;=&lt;/span&gt; difflib&lt;span style="color:#f92672"&gt;.&lt;/span&gt;SequenceMatcher(&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;, new_lower, ex_lower)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ratio()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; ratio &lt;span style="color:#f92672"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.6&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="ec1-blog-api-번역-시스템"&gt;ec1: Blog API 번역 시스템
&lt;/h2&gt;&lt;h3 id="gemini로의-전환"&gt;Gemini로의 전환
&lt;/h3&gt;&lt;p&gt;초기에는 ZAI(glm-4.7)로 번역을 수행했으나, 치명적인 문제가 발생했습니다:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;glm-4.7은 &lt;strong&gt;reasoning 모델&lt;/strong&gt;로, &lt;code&gt;max_tokens&lt;/code&gt; 예산을 &lt;code&gt;reasoning_content&lt;/code&gt;(내부 사고 과정)에 먼저 소진합니다. &lt;code&gt;max_tokens=256&lt;/code&gt;이면 reasoning에 256토큰을 모두 쓰고, 실제 &lt;code&gt;content&lt;/code&gt;는 빈 문자열이 됩니다.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;이로 인해 &lt;strong&gt;9개 영문 게시글의 제목이 빈 문자열&lt;/strong&gt;로 번역되는 사고가 발생했습니다.&lt;/p&gt;
&lt;p&gt;해결책: &lt;strong&gt;Gemini 2.5 Flash Lite&lt;/strong&gt;로 교체.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;항목&lt;/th&gt;
					&lt;th&gt;ZAI (이전)&lt;/th&gt;
					&lt;th&gt;Gemini (현재)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;모델&lt;/td&gt;
					&lt;td&gt;glm-4.7 (reasoning)&lt;/td&gt;
					&lt;td&gt;gemini-2.5-flash-lite&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;번역 시간&lt;/td&gt;
					&lt;td&gt;~30초/포스트&lt;/td&gt;
					&lt;td&gt;~8초/포스트&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;비용&lt;/td&gt;
					&lt;td&gt;API 유료&lt;/td&gt;
					&lt;td&gt;무료 (1,500건/일)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;빈 응답 문제&lt;/td&gt;
					&lt;td&gt;발생&lt;/td&gt;
					&lt;td&gt;없음&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="openai-compatible-엔드포인트"&gt;OpenAI-Compatible 엔드포인트
&lt;/h3&gt;&lt;p&gt;Gemini는 OpenAI 호환 API를 제공합니다. 기존 코드를 &lt;strong&gt;한 줄도 바꾸지 않고&lt;/strong&gt; base URL만 교체하면 됩니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;LLM_BASE_URLS &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GEMINI&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://generativelanguage.googleapis.com/v1beta/openai&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ZAI&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://api.z.ai/api/coding/paas/v4&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="번역-매칭-로직"&gt;번역 매칭 로직
&lt;/h3&gt;&lt;p&gt;한국어↔영어 게시글 페어링은 &lt;strong&gt;날짜 접두사 매칭&lt;/strong&gt;을 사용합니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ko: 2026-05-04-001-개발-생산성-17배-극대화-deepseek-v4와-...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;en: 2026-05-04-001-개발-생산성-17배-극대화-deepseek-v4와-...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↑ 같은 접두사 = 같은 게시글
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;slug의 언어가 다를 수 있지만, &lt;code&gt;YYYY-MM-DD-NNN&lt;/code&gt; 부분이 같으면 같은 게시글로 인식합니다. 이 방식의 전제 조건은 &lt;strong&gt;같은 날짜에 같은 번호가 2개 이상 존재하면 안 된다&lt;/strong&gt;는 것입니다.&lt;/p&gt;
&lt;h3 id="title-in-body-번역-기법"&gt;Title-in-Body 번역 기법
&lt;/h3&gt;&lt;p&gt;제목을 별도 API 호출로 번역하면 reasoning 모델에서 빈 결과가 나오는 문제가 있었습니다. 해결책은 &lt;strong&gt;제목을 본문 첫 줄에 포함&lt;/strong&gt;시키는 것:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 번역 요청 시&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;prompt &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;# &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;original_title&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;original_body&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 번역 결과에서 제목 추출&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; translated&lt;span style="color:#f92672"&gt;.&lt;/span&gt;lstrip()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;startswith(&lt;span style="color:#e6db74"&gt;&amp;#34;# &amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lines &lt;span style="color:#f92672"&gt;=&lt;/span&gt; translated&lt;span style="color:#f92672"&gt;.&lt;/span&gt;lstrip()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;split(&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extracted_title &lt;span style="color:#f92672"&gt;=&lt;/span&gt; lines[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]&lt;span style="color:#f92672"&gt;.&lt;/span&gt;lstrip(&lt;span style="color:#e6db74"&gt;&amp;#34;# &amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strip()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; translated_body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; lines[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]&lt;span style="color:#f92672"&gt;.&lt;/span&gt;lstrip(&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;하나의 API 호출로 제목과 본문을 동시에 번역하므로, 맥락이 보존되고 토큰도 절약됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="llm-전략-역할별-모델-분리"&gt;LLM 전략: 역할별 모델 분리
&lt;/h2&gt;&lt;p&gt;하나의 LLM으로 모든 작업을 처리하지 않습니다. 작업 성격에 맞춰 모델을 분리했습니다.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;작업&lt;/th&gt;
					&lt;th&gt;서버&lt;/th&gt;
					&lt;th&gt;모델&lt;/th&gt;
					&lt;th&gt;이유&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;댓글 AI 응답&lt;/td&gt;
					&lt;td&gt;arm1&lt;/td&gt;
					&lt;td&gt;ZAI glm-4.7&lt;/td&gt;
					&lt;td&gt;대화형, 한국어 품질 우수&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;포스트 생성&lt;/td&gt;
					&lt;td&gt;arm1&lt;/td&gt;
					&lt;td&gt;ZAI glm-4.7&lt;/td&gt;
					&lt;td&gt;긴 글 생성, 창의성 필요&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;번역 (ko→en)&lt;/td&gt;
					&lt;td&gt;ec1&lt;/td&gt;
					&lt;td&gt;Gemini Flash Lite&lt;/td&gt;
					&lt;td&gt;비추론형, 빠르고 무료&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;핵심 원칙: &lt;strong&gt;reasoning 모델은 번역에 쓰지 않는다&lt;/strong&gt;. reasoning 모델은 내부 사고에 토큰을 소비하므로, 단순 변환 작업에는 비추론형 모델이 적합합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="모니터링과-운영"&gt;모니터링과 운영
&lt;/h2&gt;&lt;h3 id="헬스체크-엔드포인트"&gt;헬스체크 엔드포인트
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# arm1 에이전트&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl http://arm1:8081/health
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# → {&amp;#34;status&amp;#34;:&amp;#34;healthy&amp;#34;,&amp;#34;agent&amp;#34;:&amp;#34;blog-agent&amp;#34;,&amp;#34;scheduler_jobs&amp;#34;:2,&amp;#34;uptime_sec&amp;#34;:...}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl http://arm1:8081/status
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# → {&amp;#34;scheduler&amp;#34;:[{&amp;#34;name&amp;#34;:&amp;#34;auto-translate&amp;#34;,&amp;#34;last_run&amp;#34;:...},{&amp;#34;name&amp;#34;:&amp;#34;post-generator&amp;#34;,&amp;#34;last_run&amp;#34;:&amp;#34;2026-05-04&amp;#34;}]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ec1 Blog API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl https://blog.example.com/api/health
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# → {&amp;#34;status&amp;#34;:&amp;#34;healthy&amp;#34;,&amp;#34;version&amp;#34;:&amp;#34;2.0.0&amp;#34;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="관찰-포인트"&gt;관찰 포인트
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;지표&lt;/th&gt;
					&lt;th&gt;정상 범위&lt;/th&gt;
					&lt;th&gt;알림 조건&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;arm1 uptime&lt;/td&gt;
					&lt;td&gt;&amp;gt;0&lt;/td&gt;
					&lt;td&gt;서비스 다운&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;scheduler_jobs&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;≠ 2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;번역 동기화&lt;/td&gt;
					&lt;td&gt;ko=en 개수 일치&lt;/td&gt;
					&lt;td&gt;차이 발생&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;포스트 생성&lt;/td&gt;
					&lt;td&gt;매일 1건&lt;/td&gt;
					&lt;td&gt;24시간 이상 미생성&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="교훈과-운영-팁"&gt;교훈과 운영 팁
&lt;/h2&gt;&lt;h3 id="1-reasoning-모델의-함정"&gt;1. Reasoning 모델의 함정
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;max_tokens&lt;/code&gt;가 reasoning과 content를 &lt;strong&gt;합산&lt;/strong&gt;한다는 것을 문서에서 명시하지 않는 경우가 많습니다. 빈 응답이 나오면 &lt;code&gt;finish_reason&lt;/code&gt;을 확인하세요 — &lt;code&gt;&amp;quot;length&amp;quot;&lt;/code&gt;라면 토큰 예산 부족입니다.&lt;/p&gt;
&lt;h3 id="2-openai-compatible-패턴의-가치"&gt;2. OpenAI-Compatible 패턴의 가치
&lt;/h3&gt;&lt;p&gt;번역 제공자를 ZAI에서 Gemini로 바꿀 때 코드 변경이 &lt;strong&gt;base URL 1줄&lt;/strong&gt;이었습니다. 처음부터 OpenAI-compatible 인터페이스로 추상화하면 LLM 교체 비용이 극적으로 줄어듭니다.&lt;/p&gt;
&lt;h3 id="3-날짜-접두사-매칭의-제약"&gt;3. 날짜 접두사 매칭의 제약
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;YYYY-MM-DD-NNN&lt;/code&gt; 패턴에서 같은 날짜에 같은 번호가 2개 이상 존재하면 번역 매칭이 깨집니다. PostGenerator에서 새 게시글 생성 시 해당 날짜의 마지막 번호 + 1을 확인하는 로직이 필수입니다.&lt;/p&gt;
&lt;h3 id="4-통합-프로세스의-이점"&gt;4. 통합 프로세스의 이점
&lt;/h3&gt;&lt;p&gt;3개 독립 서비스를 1개로 통합하면서 얻은 것:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;상태 공유 (LLM 클라이언트, 설정, API 클라이언트를 한 번만 초기화)&lt;/li&gt;
&lt;li&gt;배포 단순화 (systemd 유닛 1개)&lt;/li&gt;
&lt;li&gt;디버깅 용이 (로그가 한 곳에 모임)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="향후-계획"&gt;향후 계획
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;arm1 에이전트의 LLM도 Gemini로 통합 검토&lt;/li&gt;
&lt;li&gt;댓글 품질 평가 파이프라인 (자동 생성 댓글의 적절성 모니터링)&lt;/li&gt;
&lt;li&gt;번역 품질 자동 검증 (역번역 비교)&lt;/li&gt;
&lt;li&gt;AgentForge 프레임워크를 통한 에이전트 간 협업 확대&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;블로그 자동화는 &amp;ldquo;완전 자동&amp;quot;이 아니라 &amp;ldquo;최소 개입&amp;quot;을 목표로 합니다. AI가 생성한 콘텐츠를 사람이 검토하고, 시스템이 이상 징후를 감지하면 운영자에게 알리는 구조가 안정적인 운영의 핵심입니다.&lt;/p&gt;</description></item><item><title>[Discord MCP] Gateway 아키텍처 설계</title><link>https://blog.agentthread.dev/ko/post/discord-gateway-mcp-architecture-design/</link><pubDate>Sun, 01 Mar 2026 01:13:00 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/discord-gateway-mcp-architecture-design/</guid><description>&lt;p&gt;Claude Code 팀에서 Discord를 통한 사용자 소통을 위해 Discord Gateway Service를 설계했다. 이 글에서는 주요 아키텍처 결정 사항과 user_comm Agent 설계를 정리한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-전체-아키텍처"&gt;1. 전체 아키텍처
&lt;/h2&gt;&lt;h3 id="구성-요소"&gt;구성 요소
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;계층&lt;/th&gt;
					&lt;th&gt;구성요소&lt;/th&gt;
					&lt;th&gt;역할&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Bot, Channel, Thread&lt;/td&gt;
					&lt;td&gt;사용자 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Gateway&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;WebSocket, REST API, SSE&lt;/td&gt;
					&lt;td&gt;메시지 라우팅&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;gcp-mcp, oci-mcp, db-mcp&lt;/td&gt;
					&lt;td&gt;도구 실행&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;user_comm&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Discord Agent&lt;/td&gt;
					&lt;td&gt;사용자 소통 담당&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="메시지-흐름"&gt;메시지 흐름
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 subgraph D[Discord]
 U[User]
 end
 subgraph G[Gateway :8081]
 WS[WebSocket]
 API[REST API]
 SSE[SSE]
 end
 subgraph A[Claude Code Team]
 UC[user_comm]
 GCP[gcp-mcp]
 OCI[oci-mcp]
 DB[db-mcp]
 end

 U --&gt; WS
 WS --&gt; UC
 UC --&gt; API
 API --&gt; GCP
 API --&gt; OCI
 API --&gt; DB
 GCP --&gt; UC
 OCI --&gt; UC
 DB --&gt; UC
 UC --&gt; SSE
 SSE --&gt; U&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="2-user_comm-agent-사용자-소통-담당"&gt;2. user_comm Agent (사용자 소통 담당)
&lt;/h2&gt;&lt;h3 id="역할-및-책임"&gt;역할 및 책임
&lt;/h3&gt;&lt;p&gt;user_comm Agent는 Claude Code 팀의 멤버로서 Discord 채널을 통해 사용자와 소통하고 다른 agent들과 협업합니다.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;기능&lt;/th&gt;
					&lt;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;입력 수신&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Discord 메시지를 받아 적절한 agent에게 전달&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;의견 요청&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;다른 agent의 요청으로 사용자 의견 질의&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;알림/보고&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;시스템 상태, 경고, 리포트 전송&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;팀 통신&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;다른 agent와 메시지 송수신&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="내부-구조"&gt;내부 구조
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 subgraph UC[user_comm Agent]
 DBot[Discord Bot]
 Agent[UserCommAgent]
 TComm[TeamCommunicator]
 DBot &lt;--&gt; Agent
 Agent &lt;--&gt; TComm
 end
 subgraph DAPI[Discord API]
 M[Messages]
 B[Buttons]
 T[Threads]
 end
 subgraph O[Other Agents]
 GCP[gcp-mcp]
 OCI[oci-mcp]
 AL[alert-mcp]
 end

 DBot &lt;--&gt; M
 DBot &lt;--&gt; B
 DBot &lt;--&gt; T
 TComm --&gt; GCP
 TComm --&gt; OCI
 TComm --&gt; AL&lt;/pre&gt;&lt;h3 id="메시지-타입"&gt;메시지 타입
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MessageType&lt;/span&gt;(Enum):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TASK_REQUEST &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;task_request&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 작업 요청&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NOTIFICATION &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;notification&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 알림&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; OPINION_REQUEST &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;opinion_request&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 의견 요청&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; OPINION_RESPONSE &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;opinion_response&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 의견 응답&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; STATUS_REPORT &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;status_report&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 상태 보고&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ERROR &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;error&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 에러&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="주요-기능-동작"&gt;주요 기능 동작
&lt;/h3&gt;&lt;h4 id="입력-수신-discord--agent"&gt;입력 수신 (Discord → Agent)
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;사용자: &amp;#34;@gcp-monitor 서버 상태 확인&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → DiscordBot.on_message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → UserCommAgent._parse_command (target: gcp-monitor)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → TeamCommunicator.send_to_agent
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → Discord: &amp;#34;gcp-monitor에게 요청을 전달했습니다.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="의견-요청-agent--discord"&gt;의견 요청 (Agent → Discord)
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gcp-monitor: &amp;#34;디스크 정리할까요?&amp;#34; → user_comm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → DiscordBot.ask_opinion (버튼 또는 답장 대기)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → 사용자 응답
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → TeamCommunicator.send_to_agent (응답 전달)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="알림보고-agent--discord"&gt;알림/보고 (Agent → Discord)
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oci-monitor: &amp;#34;디스크 92% 경고&amp;#34; → user_comm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → DiscordBot.send_message (Embed 형식)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="3-redis-없이-동작하는-가벼운-아키텍처"&gt;3. Redis 없이 동작하는 가벼운 아키텍처
&lt;/h2&gt;&lt;h3 id="왜-redis를-제거했나"&gt;왜 Redis를 제거했나?
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;항목&lt;/th&gt;
					&lt;th&gt;Redis 사용&lt;/th&gt;
					&lt;th&gt;In-Memory 사용&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Thread Lock&lt;/td&gt;
					&lt;td&gt;Redis SET NX&lt;/td&gt;
					&lt;td&gt;Python dict&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;이벤트 분배&lt;/td&gt;
					&lt;td&gt;Redis Streams&lt;/td&gt;
					&lt;td&gt;SSE 직접&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;상태 저장&lt;/td&gt;
					&lt;td&gt;Redis Cache&lt;/td&gt;
					&lt;td&gt;메모리 캐시&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;결론&lt;/strong&gt;: 단일 인스턴스 환경에서는 In-Memory로 충분&lt;/p&gt;
&lt;h3 id="gateway-구조"&gt;Gateway 구조
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 subgraph GW[Gateway Service]
 Bot[Discord Bot]
 subgraph C[Core Components]
 Lock[Thread Lock]
 Cache[Message Cache]
 SSEMgr[SSE Manager]
 end
 end
 DAPI[Discord API] &lt;--&gt; Bot
 Bot --&gt; Lock
 Bot --&gt; Cache
 Bot --&gt; SSEMgr
 SSEMgr --&gt; Clients[WebSocket Clients]&lt;/pre&gt;&lt;h3 id="구성요소-상세"&gt;구성요소 상세
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;모듈&lt;/th&gt;
					&lt;th&gt;역할&lt;/th&gt;
					&lt;th&gt;특징&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Discord Bot&lt;/td&gt;
					&lt;td&gt;WebSocket 연결&lt;/td&gt;
					&lt;td&gt;자동 재연결&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Thread Lock&lt;/td&gt;
					&lt;td&gt;동시성 제어&lt;/td&gt;
					&lt;td&gt;5분 타임아웃&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Message Cache&lt;/td&gt;
					&lt;td&gt;메시지 보관&lt;/td&gt;
					&lt;td&gt;최대 1000개&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SSE Manager&lt;/td&gt;
					&lt;td&gt;실시간 전송&lt;/td&gt;
					&lt;td&gt;모든 MCP에 브로드캐스트&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="4-mcp-선택-방식-4단계-하이브리드"&gt;4. MCP 선택 방식: 4단계 하이브리드
&lt;/h2&gt;&lt;h3 id="선택-우선순위"&gt;선택 우선순위
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style="text-align: center"&gt;순위&lt;/th&gt;
					&lt;th&gt;방식&lt;/th&gt;
					&lt;th&gt;예시&lt;/th&gt;
					&lt;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style="text-align: center"&gt;1&lt;/td&gt;
					&lt;td&gt;슬래시 커맨드&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/gcp status&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;가장 명시적&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style="text-align: center"&gt;2&lt;/td&gt;
					&lt;td&gt;@멘션&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;@gcp-monitor status&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;자연스러운 대화&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style="text-align: center"&gt;3&lt;/td&gt;
					&lt;td&gt;키워드 감지&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;gcp 서버 상태&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;키워드 자동 인식&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style="text-align: center"&gt;4&lt;/td&gt;
					&lt;td&gt;채널별 지정&lt;/td&gt;
					&lt;td&gt;#gcp-모니터링&lt;/td&gt;
					&lt;td&gt;채널 기본 MCP&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="fallback-동작-순서"&gt;Fallback 동작 순서
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 A[Message Received] --&gt; B{Slash Command?}
 B --&gt;|Yes| C[Route to MCP]
 B --&gt;|No| D{Mention?}
 D --&gt;|Yes| C
 D --&gt;|No| E{Keyword?}
 E --&gt;|Yes| C
 E --&gt;|No| F{Channel Default?}
 F --&gt;|Yes| C
 F --&gt;|No| G[Broadcast]&lt;/pre&gt;&lt;h3 id="슬래시-커맨드-목록"&gt;슬래시 커맨드 목록
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;커맨드&lt;/th&gt;
					&lt;th&gt;MCP&lt;/th&gt;
					&lt;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;/gcp status [server]&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;gcp-mcp&lt;/td&gt;
					&lt;td&gt;GCP 서버 상태&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;/gcp list&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;gcp-mcp&lt;/td&gt;
					&lt;td&gt;GCP 인스턴스 목록&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;/oci status [server]&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;oci-mcp&lt;/td&gt;
					&lt;td&gt;OCI 서버 상태&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;/oci list&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;oci-mcp&lt;/td&gt;
					&lt;td&gt;OCI 인스턴스 목록&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;/db query &amp;lt;sql&amp;gt;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;db-mcp&lt;/td&gt;
					&lt;td&gt;DB 쿼리 실행&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;/db list&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;db-mcp&lt;/td&gt;
					&lt;td&gt;DB 목록&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;/alert check&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;alert-mcp&lt;/td&gt;
					&lt;td&gt;알림 확인&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="5-thread-lock-규칙"&gt;5. Thread Lock 규칙
&lt;/h2&gt;&lt;h3 id="락-동작-방식"&gt;락 동작 방식
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;sequenceDiagram
 participant M1 as gcp-mcp
 participant GW as Gateway
 participant M2 as oci-mcp
 participant DT as Discord Thread

 Note over M1,DT: Lock Acquisition
 M1-&gt;&gt;+GW: Lock request
 GW--&gt;&gt;-M1: Lock acquired
 M1-&gt;&gt;DT: Send response

 Note over M2,GW: Lock Conflict
 M2-&gt;&gt;GW: Lock request
 GW--&gt;&gt;M2: Lock failed

 Note over M1,GW: 5min timeout
 GW-&gt;&gt;GW: Auto-release lock

 Note over M2,GW: Lock Retry
 M2-&gt;&gt;+GW: Lock request
 GW--&gt;&gt;-M2: Lock acquired&lt;/pre&gt;&lt;h3 id="lock-api"&gt;Lock API
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Method&lt;/th&gt;
					&lt;th&gt;Endpoint&lt;/th&gt;
					&lt;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/threads/{id}/acquire&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;락 획득&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/threads/{id}/release&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;락 해제&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/threads/{id}/lock&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;락 상태 확인&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="요청응답-예시"&gt;요청/응답 예시
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;락 획득 요청&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;POST /api/threads/123456/acquire
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;agent_name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;gcp-mcp&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;timeout&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;락 획득 응답&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;acquired&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;thread_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;123456&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;agent&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;gcp-mcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="6-mcp-도구-8개"&gt;6. MCP 도구 (8개)
&lt;/h2&gt;&lt;h3 id="도구-목록"&gt;도구 목록
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;도구&lt;/th&gt;
					&lt;th&gt;설명&lt;/th&gt;
					&lt;th&gt;주요 파라미터&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_send_message&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;메시지 전송&lt;/td&gt;
					&lt;td&gt;channel_id, content&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_get_messages&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;메시지 조회&lt;/td&gt;
					&lt;td&gt;channel_id, limit&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_wait_for_message&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;메시지 대기&lt;/td&gt;
					&lt;td&gt;channel_id, timeout&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_create_thread&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;스레드 생성&lt;/td&gt;
					&lt;td&gt;channel_id, message_id&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_list_threads&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;스레드 목록&lt;/td&gt;
					&lt;td&gt;channel_id&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_archive_thread&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;스레드 아카이브&lt;/td&gt;
					&lt;td&gt;thread_id&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_acquire_thread&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;락 획득&lt;/td&gt;
					&lt;td&gt;thread_id, agent_name&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_release_thread&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;락 해제&lt;/td&gt;
					&lt;td&gt;thread_id, agent_name&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="도구-관계도"&gt;도구 관계도
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 subgraph Lock[Lock Tools]
 Acquire[acquire_thread]
 Release[release_thread]
 end
 subgraph Thread[Thread Tools]
 Create[create_thread]
 List[list_threads]
 Archive[archive_thread]
 end
 subgraph Msg[Message Tools]
 Send[send_message]
 Get[get_messages]
 Wait[wait_for_message]
 end

 Acquire --&gt; Create
 Create --&gt; Send
 Send --&gt; Release&lt;/pre&gt;&lt;h3 id="사용-예시"&gt;사용 예시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 메시지 전송&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;discord_send_message(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; channel_id&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;123456789&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; content&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;서버 상태: 정상&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 스레드 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;discord_create_thread(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; channel_id&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;123456789&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message_id&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;987654321&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;상태 확인&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 락 획득&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;discord_acquire_thread(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; thread_id&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;111222333&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent_name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;gcp-mcp&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="7-파일-구조"&gt;7. 파일 구조
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 subgraph P[server_monitor/]
 subgraph Gw[gateway/]
 M1[main.py]
 M2[discord_ws.py]
 M3[thread_lock.py]
 M4[sse.py]
 end
 subgraph Uc[user_comm/]
 A1[agent.py]
 A2[discord_bot.py]
 A3[team_comm.py]
 end
 subgraph Mcp[discord_mcp/]
 S1[server.py]
 end
 subgraph Sh[mcp_shared/]
 MO[monitor/]
 end
 subgraph Doc[docs/]
 D1[MCP_SELECTION_STRATEGY.md]
 D2[MCP_ROUTING_POLICY.md]
 end
 end&lt;/pre&gt;&lt;h3 id="디렉토리-설명"&gt;디렉토리 설명
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;경로&lt;/th&gt;
					&lt;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;gateway/&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Gateway Service (FastAPI)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;user_comm/&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;사용자 소통 Agent&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_mcp/&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;MCP Server (8개 도구)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;mcp_shared/&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;공유 MCP 도구&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;docs/&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;문서&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="8-실행-방법"&gt;8. 실행 방법
&lt;/h2&gt;&lt;h3 id="로컬-실행"&gt;로컬 실행
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Gateway Service 시작&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uvicorn gateway.main:app --host 0.0.0.0 --port &lt;span style="color:#ae81ff"&gt;8081&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# user_comm Agent 시작 (독립 모드)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --standalone
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# user_comm Agent 시작 (팀 모드)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --team server-monitor
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 헬스체크&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl http://localhost:8081/health
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 응답&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;healthy&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;discord_connected&amp;#34;&lt;/span&gt;: true&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="claude-code-mcp-설정"&gt;Claude Code MCP 설정
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ~/.claude/settings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;mcpServers&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;discord-gateway&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;python3&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;args&amp;#34;&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;/path/to/discord_mcp/server.py&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;env&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;GATEWAY_URL&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8081&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="9-로드맵"&gt;9. 로드맵
&lt;/h2&gt;&lt;h3 id="phase-1-완료"&gt;Phase 1: 완료
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;input checked="" disabled="" type="checkbox"&gt; FastAPI Gateway Service&lt;/li&gt;
&lt;li&gt;&lt;input checked="" disabled="" type="checkbox"&gt; Discord WebSocket 연결&lt;/li&gt;
&lt;li&gt;&lt;input checked="" disabled="" type="checkbox"&gt; Thread Lock (In-Memory)&lt;/li&gt;
&lt;li&gt;&lt;input checked="" disabled="" type="checkbox"&gt; SSE 브로드캐스트&lt;/li&gt;
&lt;li&gt;&lt;input checked="" disabled="" type="checkbox"&gt; MCP Server (8개 도구)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="phase-2-진행-예정"&gt;Phase 2: 진행 예정
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; user_comm Agent 구현&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 슬래시 커맨드 구현&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 채널별 기본 MCP 설정&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 키워드 자동 감지&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 라우팅 설정 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="phase-3-선택-사항"&gt;Phase 3: 선택 사항
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; API 인증 (API Key)&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Rate Limiting&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 메시지 영속성 (SQLite)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="결론"&gt;결론
&lt;/h2&gt;&lt;p&gt;가벼운 아키텍처로 시작해서 필요시 확장하는 전략을 선택했다.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;항목&lt;/th&gt;
					&lt;th&gt;현재&lt;/th&gt;
					&lt;th&gt;향후&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;상태 저장&lt;/td&gt;
					&lt;td&gt;In-Memory&lt;/td&gt;
					&lt;td&gt;SQLite (필요시)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;분산 락&lt;/td&gt;
					&lt;td&gt;미사용&lt;/td&gt;
					&lt;td&gt;Redis (다중 인스턴스 시)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;인증&lt;/td&gt;
					&lt;td&gt;없음&lt;/td&gt;
					&lt;td&gt;API Key (필요시)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;사용자 소통&lt;/td&gt;
					&lt;td&gt;Gateway 직접&lt;/td&gt;
					&lt;td&gt;user_comm Agent&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;단일 인스턴스에서는 현재 구조로 충분하며, 트래픽이 늘어나면 점진적으로 확장할 계획이다. user_comm Agent를 통해 사용자와의 소통을 체계화하고, 팀 내 다른 agent들과의 협업을 효율화한다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;영어 버전:&lt;/strong&gt; &lt;a class="link" href="https://blog.agentthread.dev/post/2026-03-01-013-discord-gateway-mcp-architecture-design/" &gt;English Version&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>