<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Architecture on Yarang's Tech Lair</title><link>https://blog.agentthread.dev/ko/tags/architecture/</link><description>Recent content in Architecture on Yarang's Tech Lair</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sun, 28 Jun 2026 09:00:35 +0900</lastBuildDate><atom:link href="https://blog.agentthread.dev/ko/tags/architecture/index.xml" rel="self" type="application/rss+xml"/><item><title>MCP 통합 가이드: ZeroClaw 런타임에서 Discord MCP 연동하기</title><link>https://blog.agentthread.dev/ko/post/mcp-%ED%86%B5%ED%95%A9-%EA%B0%80%EC%9D%B4%EB%93%9C-zeroclaw-%EB%9F%B0%ED%83%80%EC%9E%84%EC%97%90%EC%84%9C-discord-mcp-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0/</link><pubDate>Sun, 28 Jun 2026 09:00:35 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/mcp-%ED%86%B5%ED%95%A9-%EA%B0%80%EC%9D%B4%EB%93%9C-zeroclaw-%EB%9F%B0%ED%83%80%EC%9E%84%EC%97%90%EC%84%9C-discord-mcp-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0/</guid><description>&lt;p&gt;최근 고성능 Rust 에이전트 런타임인 &lt;strong&gt;ZeroClaw&lt;/strong&gt;를 발표하며, 멀티 에이전트 시스템의 효율성을 극대화하는 방안에 대한 고민이 깊어졌습니다. 특히 [Discord Decision MCP] 아키텍처 설계를 진행하면서 겪었던 통신 프로토콜의 복잡성은 MCP(Model Context Protocol)를 표준 인터페이스로 채택하게 된 결정적인 계기가 되었습니다.&lt;/p&gt;
&lt;p&gt;이번 포스트에서는 ZeroClaw의 환경에서 &lt;strong&gt;Discord MCP&lt;/strong&gt;를 실제로 통합하여, 에이전트가 Discord 메시지를 수신하고 처리하는 과정을 구체적인 코드와 함께 설명하겠습니다.&lt;/p&gt;
&lt;h3 id="1-아키텍처-설계-단일-채널에서의-이중-통신"&gt;1. 아키텍처 설계: 단일 채널에서의 이중 통신
&lt;/h3&gt;&lt;p&gt;기존 [Discord MCP] Gateway 아키텍처에서는 게이트웨이가 이벤트를 필터링하고 에이전트에게 전달하는 역할을 했습니다. 하지만 ZeroClaw 내부적으로 MCP 클라이언트를 직접 구현함으로써 중간 계층을 제거하고 지연 시간을 줄이는 방향으로 설계를 변경했습니다.&lt;/p&gt;
&lt;p&gt;핵심은 **&lt;code&gt;MCP Client&lt;/code&gt;**가 Discord 이벤트를 &lt;code&gt;stdio&lt;/code&gt;를 통해 ZeroClaw 프로세스로 전달하고, 에이전트의 응답을 다시 Discord로 보내는 흐름입니다.&lt;/p&gt;
&lt;h3 id="2-필수-의존성-및-설정-rust"&gt;2. 필수 의존성 및 설정 (Rust)
&lt;/h3&gt;&lt;p&gt;ZeroClaw는 Rust로 작성되었으므로, 높은 동시성 처리를 위해 비동기 런타임인 &lt;code&gt;tokio&lt;/code&gt;와 JSON 처리를 위한 &lt;code&gt;serde&lt;/code&gt;를 사용합니다. MCP 서버와의 통신은 JSON-RPC 메시지를 표준 입출력(stdio)으로 주고받는 방식을 가정합니다.&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-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Cargo.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tokio&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;full&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;derive&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;async-trait&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-mcp-클라이언트-구현"&gt;3. MCP 클라이언트 구현
&lt;/h3&gt;&lt;p&gt;ZeroClaw 내부에서 Discord MCP 서버를 제어하기 위한 간단한 클라이언트 구조를 정의합니다. 이 클라이언트는 MCP 표준을 따르는 &lt;code&gt;tools/call&lt;/code&gt; 메서드를 사용하여 Discord 봇의 기능을 실행합니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::process::{Command, Stdio};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::io::{BufReader, BufWriter, Write};
&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;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MCPRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#66d9ef"&gt;u64&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; method: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; params: &lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt;::Value,
&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;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MCPResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#66d9ef"&gt;u64&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;serde_json::Value&lt;span style="color:#f92672"&gt;&amp;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 style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DiscordMCPClient&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#66d9ef"&gt;u64&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:#66d9ef"&gt;impl&lt;/span&gt; DiscordMCPClient {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;() -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Self { id: &lt;span style="color:#ae81ff"&gt;0&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:#e6db74"&gt;/// Discord 채널에 메시지를 전송하는 MCP 툴 실행
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;send_message&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self, channel_id: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;, content: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self.id &lt;span style="color:#f92672"&gt;+=&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; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// MCP 서버 프로세스 실행 (예: Python 기반 Discord MCP 서버)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; child &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Command::new(&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; .arg(&lt;span style="color:#e6db74"&gt;&amp;#34;discord_mcp_server.py&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .stdin(Stdio::piped())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .stdout(Stdio::piped())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .spawn()&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; stdin &lt;span style="color:#f92672"&gt;=&lt;/span&gt; child.stdin.as_mut().ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to open stdin&amp;#34;&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:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; stdout &lt;span style="color:#f92672"&gt;=&lt;/span&gt; BufReader::new(child.stdout.as_mut().ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to open stdout&amp;#34;&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; request &lt;span style="color:#f92672"&gt;=&lt;/span&gt; MCPRequest {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;self&lt;/span&gt;.id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; method: &lt;span style="color:#e6db74"&gt;&amp;#34;tools/call&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; params: &lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt;::&lt;span style="color:#a6e22e"&gt;json!&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;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;send_message&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;arguments&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;channel_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;channel_id&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;content&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;content&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;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;let&lt;/span&gt; request_json &lt;span style="color:#f92672"&gt;=&lt;/span&gt; serde_json::to_string(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;request)&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:#a6e22e"&gt;writeln!&lt;/span&gt;(stdin, &lt;span style="color:#e6db74"&gt;&amp;#34;{}&amp;#34;&lt;/span&gt;, request_json)&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&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:#75715e"&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; Ok(())
&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;h3 id="4-zeroclaw-에이전트와의-통합"&gt;4. ZeroClaw 에이전트와의 통합
&lt;/h3&gt;&lt;p&gt;이제 위에서 만든 &lt;code&gt;DiscordMCPClient&lt;/code&gt;를 ZeroClaw의 에이전트 루프 내에서 사용해봅시다. 에이전트가 특정 작업을 완료했을 때, 이를 Discord에 알리는 시나리오입니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ZeroClawAgent&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; discord_client: &lt;span style="color:#a6e22e"&gt;DiscordMCPClient&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:#66d9ef"&gt;impl&lt;/span&gt; ZeroClawAgent {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run_task&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self, task: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[Agent] 작업 시작: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, task);
&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:#75715e"&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:#66d9ef"&gt;let&lt;/span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;작업 &amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39; 완료됨.&amp;#34;&lt;/span&gt;, task);
&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;// 결과를 Discord로 전송
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; self.discord_client.send_message(&lt;span style="color:#e6db74"&gt;&amp;#34;123456789&amp;#34;&lt;/span&gt;, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;result).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(_) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[Agent] Discord 알림 전송 성공&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[Agent] 전송 실패: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e),
&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;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[tokio::main]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; agent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ZeroClawAgent {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; discord_client: &lt;span style="color:#a6e22e"&gt;DiscordMCPClient&lt;/span&gt;::new(),
&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; agent.run_task(&lt;span style="color:#e6db74"&gt;&amp;#34;서버 로그 분석&amp;#34;&lt;/span&gt;).&lt;span style="color:#66d9ef"&gt;await&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="5-효율성-및-보안-고려사항-architecture-insights"&gt;5. 효율성 및 보안 고려사항 (Architecture Insights)
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;리소스 관리&lt;/strong&gt;: [Hacker News]의 최신 트렌드인 &amp;lsquo;Adrafinil&amp;rsquo;처럼, 에이전트가 유휴 상태일 때는 Discord MCP 연결을 끊고 작업이 활성화되었을 때만 연결을 맺는 &lt;code&gt;speculative decoding&lt;/code&gt; 방식을 적용하면 리소스를 아낄 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보안&lt;/strong&gt;: [Anonymous GitHub account] 관련 이슈처럼, MCP 서버에 전달되는 파라미터(예: API 토큰)는 환경 변수로 관리하거나 별도의 보안 저장소(Vault)에서 가져오도록 설계해야 합니다. 코드 내에 하드코딩된 시크릿은 치명적입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에러 처리&lt;/strong&gt;: 통신 플랫폼 설계 고찰에서 언급된 바와 같이, Discord API의 Rate Limit을 고려하여 MCP 클라이언트에 재시도 로직(Retry Logic)을 포함하는 것이 중요합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="마무리하며"&gt;마무리하며
&lt;/h3&gt;&lt;p&gt;ZeroClaw와 Discord MCP의 통합은 단순히 봇을 만드는 것을 넘어, 에이전트가 외부 세계와 상호작용하는 &lt;strong&gt;표준화된 인터페이스&lt;/strong&gt;를 구축하는 과정입니다. 앞으로도 [MCP] 블로그 자동화 시스템 구축 경험을 살려, 더욱 정교한 멀티 에이전트 협업 시스템을 발전시켜 나갈 계획입니다.&lt;/p&gt;</description></item><item><title>ZeroClaw 에이전트 런타임: 고성능 멀티 에이전트 통신을 위한 TCP/IP 아키텍처 구현</title><link>https://blog.agentthread.dev/ko/post/zeroclaw-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%ED%86%B5%EC%8B%A0%EC%9D%84-%EC%9C%84%ED%95%9C-tcp/ip-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B5%AC%ED%98%84/</link><pubDate>Fri, 26 Jun 2026 09:01:09 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/zeroclaw-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%ED%86%B5%EC%8B%A0%EC%9D%84-%EC%9C%84%ED%95%9C-tcp/ip-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B5%AC%ED%98%84/</guid><description>&lt;p&gt;안녕하세요! 최근 오픈소스 AI 및 에이전트 생태계가 급격히 확장되면서, 단일 LLM을 넘어 여러 에이전트가 협력하는 &amp;lsquo;멀티 에이전트 시스템&amp;rsquo;에 대한 관심이 뜨겁습니다. 이전 포스트에서 ZeroClaw 프로젝트의 아키텍처 설계를 다루었는데요, 이번에는 실제 ZeroClaw 런타임이 내부적으로 어떻게 에이전트 간의 통신을 처리하고, Rust의 강력한 비동기 런타임을 활용해 고성능을 달성하는지 그 구체적인 구현 방법을 소개하고자 합니다.&lt;/p&gt;
&lt;h2 id="기존-통신의-문제점과-zeroclaw의-접근"&gt;기존 통신의 문제점과 ZeroClaw의 접근
&lt;/h2&gt;&lt;p&gt;일반적인 MCP(Model Context Protocol)나 기초적인 에이전트 시스템은 주로 단일 프로세스 내의 메모리 공유나 간단한 로컬 함수 호출로 통신을 처리합니다. 하지만 시스템이 복잡해지고 에이전트가 분산되면서 다음과 같은 문제가 발생합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;확장성의 한계:&lt;/strong&gt; 단일 프로세스 메모리 버퍼는 한정되어 있으며, 수백 개의 에이전트가 동시에 메시지를 주고받을 때 병목이 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결합도(Coupling) 증가:&lt;/strong&gt; 에이전트 간의 통신이 로직에 강하게 결합되어, 특정 에이전트를 교체하거나 업데이트하기 어렵습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;네트워크 분리:&lt;/strong&gt; 로컬 개발 환경과 클라우드 배포 환경 간의 통신 방식이 달라지면 코드를 수정해야 하는 불편함이 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ZeroClaw는 이를 해결하기 위해 **&amp;lsquo;파일 기반 아키텍처 설계&amp;rsquo;**와 **&amp;lsquo;TCP/IP 네트워킹 계층&amp;rsquo;**을 결합한 하이브리드 방식을 채택했습니다. 에이전트는 자신의 상태를 파일 시스템에 선언적으로(Declarative) 기록하며, 실제 메시지 전송은 비동기 TCP 소켓을 통해 처리합니다.&lt;/p&gt;
&lt;h2 id="rust-tokio를-활용한-비동기-서버-구조"&gt;Rust Tokio를 활용한 비동기 서버 구조
&lt;/h2&gt;&lt;p&gt;ZeroClaw의 핵심은 Rust의 &lt;code&gt;tokio&lt;/code&gt; 런타임 위에서 구축된 고성능 네트워크 계층입니다. 각 에이전트는 독립적인 태스크(Task)로 실행되며, 채널(Channel)을 통해 안전하게 메시지를 교환합니다.&lt;/p&gt;
&lt;p&gt;다음은 ZeroClaw 런타임의 핵심인 메시지 브로커(Message Broker)의 간소화된 구현 예제입니다.&lt;/p&gt;
&lt;h3 id="1-기본-메시지-구조-정의"&gt;1. 기본 메시지 구조 정의
&lt;/h3&gt;&lt;p&gt;먼저, 에이전트 간 주고받을 데이터 구조를 정의합니다. &lt;code&gt;serde&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::collections::HashMap;
&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;#[derive(Debug, Clone, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; source: String, &lt;span style="color:#75715e"&gt;// 송신 에이전트 ID
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; target: String, &lt;span style="color:#75715e"&gt;// 수신 에이전트 ID (혹은 &amp;#34;broadcast&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; payload: &lt;span style="color:#a6e22e"&gt;PayloadType&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;#[derive(Debug, Clone, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PayloadType&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(String),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Data(HashMap&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;), &lt;span style="color:#75715e"&gt;// 유연한 데이터 전달을 위한 Map
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Control(ControlSignal),
&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;#[derive(Debug, Clone, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ControlSignal&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Start,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Stop,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HeartBeat,
&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="2-비동기-tcp-핸들러-구현"&gt;2. 비동기 TCP 핸들러 구현
&lt;/h3&gt;&lt;p&gt;실제 통신은 &lt;code&gt;tokio::net::TcpListener&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::net::{TcpListener, TcpStream};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::io::{AsyncReadExt, AsyncWriteExt};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::sync::mpsc;
&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;#[derive(Clone)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentConfig&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: String,
&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:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run_agent_server&lt;/span&gt;(config: &lt;span style="color:#a6e22e"&gt;AgentConfig&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;let&lt;/span&gt; listener &lt;span style="color:#f92672"&gt;=&lt;/span&gt; TcpListener::bind(&lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1:8080&amp;#34;&lt;/span&gt;).&lt;span style="color:#66d9ef"&gt;await&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:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Agent Server listening on 127.0.0.1:8080&amp;#34;&lt;/span&gt;, config.id);
&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;let&lt;/span&gt; (tx, &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; rx) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mpsc::channel::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1000&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;let&lt;/span&gt; config_clone &lt;span style="color:#f92672"&gt;=&lt;/span&gt; config.clone();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tokio::spawn(&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;move&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Ok((&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; socket, addr)) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; listener.accept().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Connection from &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, config_clone.id, addr);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; tx_clone &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tx.clone();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tokio::spawn(&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;move&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; buf &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;loop&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; n &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; socket.read(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; buf).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(n) &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; n &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&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; Ok(n) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; n,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to read from socket; err = &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e);
&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&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 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;let&lt;/span&gt; received_data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;buf[&lt;span style="color:#f92672"&gt;..&lt;/span&gt;n];
&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:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Received: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, String::from_utf8_lossy(received_data));
&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&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;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; tokio::spawn(&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;move&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; interval &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tokio::time::interval(tokio::time::Duration::from_secs(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;loop&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; interval.tick().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; hb &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AgentMessage {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source: &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.id.clone(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target: &lt;span style="color:#e6db74"&gt;&amp;#34;broadcast&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload: &lt;span style="color:#a6e22e"&gt;PayloadType&lt;/span&gt;::Control(ControlSignal::HeartBeat),
&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:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Sending Heartbeat...&amp;#34;&lt;/span&gt;, config.id);
&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;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&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;이 코드는 ZeroClaw 에이전트가 다른 에이전트의 요청을 비동기적으로 수신하고, 자신의 상태를 주기적으로 보고하는 기본 뼈대를 보여줍니다. &lt;code&gt;tokio::spawn&lt;/code&gt;을 통해 각 연결이 독립적으로 실행되므로, 수천 개의 동시 연결도 효율적으로 처리할 수 있습니다.&lt;/p&gt;
&lt;h2 id="팀-에이전트-통신과-프로토콜-설계"&gt;팀 에이전트 통신과 프로토콜 설계
&lt;/h2&gt;&lt;p&gt;단순한 1:1 통신을 넘어, ZeroClaw는 &lt;strong&gt;팀(Team) 단위의 통신&lt;/strong&gt;을 지향합니다. 이는 이전에 논의된 &amp;lsquo;팀 에이전트 통신 아키텍처&amp;rsquo;와 연결됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pub/Sub 패턴:&lt;/strong&gt; 특정 주제(Topic)를 구독한 에이전트 그룹에게 메시지를 일괄 전달합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;피어 투 피어 (P2P):&lt;/strong&gt; 중앙 서버를 거치지 않고 에이전트 간 직접 통신하여 대기 시간(Latency)을 최소화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ZeroClaw의 프로토콜은 TCP 계층 위에서 JSON 또는 바이너리 포맷(성능이 중요할 경우 MessagePack 등)으로 캡슐화되어 전송됩니다. 이를 통해 네트워크 추상화 계층(Network Abstraction Layer)을 형성하여, 상위 비즈니스 로직은 통신이 로컬인지 원격인지 신경 쓰지 않고 로직을 작성할 수 있습니다.&lt;/p&gt;
&lt;h2 id="결론-확장-가능한-에이전트-시스템을-향하여"&gt;결론: 확장 가능한 에이전트 시스템을 향하여
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 단순한 LLM 래퍼(Wrapper)를 넘어, 진정한 분산 시스템으로 설계되고 있습니다. Rust의 메모리 안전성과 Zero-cost 추상화, 그리고 &lt;code&gt;tokio&lt;/code&gt;의 강력한 비동기 처리 능력을 결합하여, 안정적이고 빠른 에이전트 런타임을 구축하고 있습니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이러한 통신 아키텍처 위에서 LLM이 어떻게 자신의 의사를 결정(Decision)하고, MCP 도구를 통해 실제 행동을 수행하는지 그 플로우를 디버깅해보겠습니다.&lt;/p&gt;
&lt;h2 id="참고-코드-및-리소스"&gt;참고 코드 및 리소스
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;ZeroClaw GitHub Repository (오픈소스 준비 중)&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elsevier.com/books/the-garbage-collection-handbook/jones/978-0-12-812720-5" target="_blank" rel="noopener"
 &gt;The Garbage Collection Handbook: The Art of Automatic Memory Management (2nd Ed)&lt;/a&gt; - 최근 뉴스에서 언급되었던 메모리 관리의 고전이자 바이블입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;읽어주셔서 감사합니다!&lt;/p&gt;</description></item><item><title>Zero-Touch OAuth 구현을 위한 MCP 인증 아키텍처 설계</title><link>https://blog.agentthread.dev/ko/post/zero-touch-oauth-%EA%B5%AC%ED%98%84%EC%9D%84-%EC%9C%84%ED%95%9C-mcp-%EC%9D%B8%EC%A6%9D-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84/</link><pubDate>Fri, 19 Jun 2026 09:00:58 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/zero-touch-oauth-%EA%B5%AC%ED%98%84%EC%9D%84-%EC%9C%84%ED%95%9C-mcp-%EC%9D%B8%EC%A6%9D-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84/</guid><description>&lt;p&gt;최근 &amp;lsquo;Zero-Touch OAuth for MCP&amp;rsquo;라는 흥미로운 기술 트렌드를 접했습니다. 우리가 개발 중인 &lt;code&gt;ZeroClaw&lt;/code&gt; 런타임과 &lt;code&gt;blog-api-server&lt;/code&gt;의 MCP(Model Context Protocol) 통합 과정에서, 사용자 경험을 저해하지 않으면서도 보안성을 확보하는 것은 필수적인 과제입니다. 이번 포스트에서는 별도의 복잡한 설정 없이 사용자가 MCP 클라이언트를 연결할 수 있도록 돕는 &amp;lsquo;Zero-Touch&amp;rsquo; 인증 흐름을 설계하고, 이를 실제 코드로 구현하는 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;h3 id="왜-zero-touch인가"&gt;왜 Zero-Touch인가?
&lt;/h3&gt;&lt;p&gt;기존의 OAuth 2.0 흐름은 사용자에게 &amp;lsquo;Client ID&amp;rsquo;와 &amp;lsquo;Client Secret&amp;rsquo;을 발급받아 설정 파일에 입력하도록 요구하곤 했습니다. 하지만 일반 사용자나 개발자에게 이는 큰 진입 장벽입니다. Zero-Touch OAuth는 클라이언트가 사전에 등록된 정보(Pre-registered metadata)를 통해 자동으로 인증을 시도하고, 사용자는 단순히 &amp;lsquo;허용&amp;rsquo; 버튼만 누르면 되는 흐름을 지향합니다.&lt;/p&gt;
&lt;h3 id="아키텍처-설계"&gt;아키텍처 설계
&lt;/h3&gt;&lt;p&gt;우리는 &lt;code&gt;blog-api-server&lt;/code&gt;를 MCP Provider로, &lt;code&gt;Claude Code&lt;/code&gt;나 커스텀 클라이언트를 MCP Consumer로 구성합니다. 핵심은 &lt;strong&gt;PKCE (Proof Key for Code Exchange)&lt;/strong&gt; 확장을 사용하여 Secret을 저장하지 않는 Public Client 환경을 구축하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 구성 요소:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;MCP Client (Consumer)&lt;/strong&gt;: 사용자의 로컬 환경에서 실행되는 에이전트입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth Server&lt;/strong&gt;: &lt;code&gt;blog-api-server&lt;/code&gt; 내에 구현된 OAuth2 인증 서버입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource Server&lt;/strong&gt;: 실제 블로그 API입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="구현-단계-1-서버-사이드-blog-api-server"&gt;구현 단계 1: 서버 사이드 (blog-api-server)
&lt;/h3&gt;&lt;p&gt;먼저, Rust 기반의 &lt;code&gt;blog-api-server&lt;/code&gt;에 간단한 인증 엔드포인트를 추가해야 합니다. 이전 글인 *&amp;rsquo;[blog-api-server] MCP 블로그 클라이언트 언어 파라미터 추가&amp;rsquo;*에서 언급된 아키텍처를 확장하여 Auth 모듈을 독립시킵니다.&lt;/p&gt;
&lt;p&gt;여기서는 핵심 로직인 &lt;strong&gt;코드 검증(Verification)&lt;/strong&gt; 로직을 보여드리겠습니다. 클라이언트로부터 받은 &lt;code&gt;code_verifier&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// blog-api-server/src/auth/handler.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; axum::{extract::State, Json};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; sha2::{Digest, Sha256};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; base64::Engine &lt;span style="color:#66d9ef"&gt;as&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;#[derive(Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TokenRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; code: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; code_verifier: String, &lt;span style="color:#75715e"&gt;// PKCE를 통해 클라이언트가 생성한 난수
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; redirect_uri: String,
&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;#[derive(Serialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TokenResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; access_token: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; token_type: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; expires_in: &lt;span style="color:#66d9ef"&gt;u64&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:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;exchange_token&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; State(state): &lt;span style="color:#a6e22e"&gt;State&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AppState&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Json(payload): &lt;span style="color:#a6e22e"&gt;Json&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;TokenRequest&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Json&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;TokenResponse&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, String&lt;span style="color:#f92672"&gt;&amp;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;// 1. DB에서 Authorization Code 조회
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; auth_record &lt;span style="color:#f92672"&gt;=&lt;/span&gt; state.db.get_auth_code(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;payload.code)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .map_err(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;_&lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Invalid code&amp;#34;&lt;/span&gt;.to_string())&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. PKCE Challenge 검증 (핵심 보안 로직)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; hasher &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Sha256::new();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hasher.update(payload.code_verifier.as_bytes());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; hashed_verifier &lt;span style="color:#f92672"&gt;=&lt;/span&gt; hasher.finalize();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; encoded_verifier &lt;span style="color:#f92672"&gt;=&lt;/span&gt; base64::engine::general_purpose::&lt;span style="color:#66d9ef"&gt;URL_SAFE_NO_PAD&lt;/span&gt;.encode(hashed_verifier);
&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;if&lt;/span&gt; encoded_verifier &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; auth_record.code_challenge {
&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; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;Code verifier mismatch&amp;#34;&lt;/span&gt;.to_string());
&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;// 3. Access Token 발급
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; access_token &lt;span style="color:#f92672"&gt;=&lt;/span&gt; generate_jwt(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;state.secrets, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;auth_record.user_id);
&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; Ok(Json(TokenResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; access_token,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; token_type: &lt;span style="color:#e6db74"&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expires_in: &lt;span style="color:#ae81ff"&gt;3600&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;p&gt;이 코드는 클라이언트가 보내온 &lt;code&gt;code_verifier&lt;/code&gt;를 해싱하여, 처음 인증 요청을 보낼 때 생성된 &lt;code&gt;code_challenge&lt;/code&gt;와 일치하는지 확인합니다. 이 과정이 &amp;lsquo;Zero-Touch&amp;rsquo;를 보안적으로 가능하게 만드는 핵심입니다.&lt;/p&gt;
&lt;h3 id="구현-단계-2-클라이언트-사이드-mcp-client"&gt;구현 단계 2: 클라이언트 사이드 (MCP Client)
&lt;/h3&gt;&lt;p&gt;이제 클라이언트는 사용자 개입 없이(혹은 최소한의 개입으로) 토큰을 발급받을 수 있습니다. Python을 사용한 MCP 클라이언트 예제를 작성해보겠습니다.&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:#f92672"&gt;import&lt;/span&gt; hashlib
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; base64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; requests
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; urllib.parse &lt;span style="color:#f92672"&gt;import&lt;/span&gt; urlencode
&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;# Zero-Touch 설정 (하드코딩 혹은 환경 변수)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CLIENT_ID &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;zeroclaw-mcp-client&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AUTH_URL &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https://api.myblog.com/oauth/authorize&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TOKEN_URL &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https://api.myblog.com/oauth/token&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;REDIRECT_URI &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:3000/callback&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;# PKCE Code Verifier 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;random_bytes &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;urandom(&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;code_verifier &lt;span style="color:#f92672"&gt;=&lt;/span&gt; base64&lt;span style="color:#f92672"&gt;.&lt;/span&gt;urlsafe_b64encode(random_bytes)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rstrip(&lt;span style="color:#e6db74"&gt;b&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;=&amp;#39;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(&lt;span style="color:#e6db74"&gt;&amp;#39;utf-8&amp;#39;&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;# Code Challenge 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;challenge_digest &lt;span style="color:#f92672"&gt;=&lt;/span&gt; hashlib&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sha256(code_verifier&lt;span style="color:#f92672"&gt;.&lt;/span&gt;encode(&lt;span style="color:#e6db74"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;))&lt;span style="color:#f92672"&gt;.&lt;/span&gt;digest()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;code_challenge &lt;span style="color:#f92672"&gt;=&lt;/span&gt; base64&lt;span style="color:#f92672"&gt;.&lt;/span&gt;urlsafe_b64encode(challenge_digest)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rstrip(&lt;span style="color:#e6db74"&gt;b&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;=&amp;#39;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(&lt;span style="color:#e6db74"&gt;&amp;#39;utf-8&amp;#39;&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;get_auth_url&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; params &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;response_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;code&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;client_id&amp;#34;&lt;/span&gt;: CLIENT_ID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;redirect_uri&amp;#34;&lt;/span&gt;: REDIRECT_URI,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;mcp:read mcp:write&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;code_challenge&amp;#34;&lt;/span&gt;: code_challenge,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;code_challenge_method&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;S256&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;return&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;AUTH_URL&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;?&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;urlencode(params)&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:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;exchange_code_for_token&lt;/span&gt;(auth_code):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data &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;grant_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;authorization_code&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;code&amp;#34;&lt;/span&gt;: auth_code,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;redirect_uri&amp;#34;&lt;/span&gt;: REDIRECT_URI,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;: CLIENT_ID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;code_verifier&amp;#34;&lt;/span&gt;: code_verifier &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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(TOKEN_URL, data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;data)
&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; response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()
&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:#75715e"&gt;# 1. 사용자가 get_auth_url()를 통해 브라우저에서 인증 (최초 1회)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. 리다이렉션된 쿼리 파라미터에서 &amp;#39;code&amp;#39; 추출&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 3. token = exchange_code_for_token(code)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="zeroclaw-런타임-통합-고찰"&gt;ZeroClaw 런타임 통합 고찰
&lt;/h3&gt;&lt;p&gt;지난 회고록인 *&amp;rsquo;[ZeroClaw] 2026 상반기 발전방향 회의록&amp;rsquo;*에서 언급했듯, 우리의 에이전트 런타임은 보안과 확장성을 최우선으로 고려해야 합니다. 위에서 구현한 OAuth 흐름을 ZeroClaw의 &lt;code&gt;Multi-Agent&lt;/code&gt; 아키텍처에 통합한다면, 각 에이전트는 독립된 Identity를 가질 수 있습니다.&lt;/p&gt;
&lt;p&gt;특히 *&amp;rsquo;[ZeroClaw] 멀티 에이전트 통신 프로토콜 설계&amp;rsquo;*에서 논의된 바와 같이, Agent 간 통신 시 이 Access Token을 사용하여 다른 에이전트의 리소스(API)에 안전하게 접근할 수 있는 기반을 마련하게 됩니다. 이는 단순한 블로그 자동화를 넘어, 에이전트가 에이전트를 신뢰하고 작업을 위임하는 분산 시스템의 첫걸음이 됩니다.&lt;/p&gt;
&lt;h3 id="마치며"&gt;마치며
&lt;/h3&gt;&lt;p&gt;Zero-Touch OAuth는 사용자의 편의성과 보안이라는 두 마리 토끼를 잡는 훌륭한 접근 방식입니다. &lt;code&gt;blog-api-server&lt;/code&gt;와 같은 작은 프로젝트부터 시작하여, 점차 &lt;code&gt;ZeroClaw&lt;/code&gt;의 핵심 인증 모듈로 발전시켜 나갈 계획입니다. 위 코드는 기본적인 골격이므로, 실제 운영 환경에서는 State 검증, Refresh Token Rotation 등의 로직을 추가해야 합니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이렇게 확보된 토큰을 활용하여 MCP 서버와 실제로 데이터를 주고받는 &amp;lsquo;핸드셰이크(Handshake)&amp;rsquo; 과정을 디버깅해보겠습니다.&lt;/p&gt;</description></item><item><title>Stop Using JWTs: 세션 기반 인증의 실용성과 구현</title><link>https://blog.agentthread.dev/ko/post/stop-using-jwts-%EC%84%B8%EC%85%98-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EC%9D%98-%EC%8B%A4%EC%9A%A9%EC%84%B1%EA%B3%BC-%EA%B5%AC%ED%98%84/</link><pubDate>Wed, 17 Jun 2026 09:00:39 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/stop-using-jwts-%EC%84%B8%EC%85%98-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EC%9D%98-%EC%8B%A4%EC%9A%A9%EC%84%B1%EA%B3%BC-%EA%B5%AC%ED%98%84/</guid><description>&lt;h1 id="stop-using-jwts-세션-기반-인증의-실용성과-구현"&gt;Stop Using JWTs: 세션 기반 인증의 실용성과 구현
&lt;/h1&gt;&lt;p&gt;최근 Hacker News와 같은 커뮤니티에서 &amp;ldquo;Stop Using JWTs&amp;quot;라는 주제가 다시 한번 뜨거운 감자로 떠올랐습니다. JWT(JSON Web Token)는 처음 등장했을 때만 해도 &amp;ldquo;무상태(Stateless)&amp;ldquo;라는 마법의 주문 덕분에 마이크로서비스 아키텍처(MSA)의 표준처럼 여겨졌습니다. 하지만 현실의 웹 서비스를 운영하면서 무상태의 함정에 빠지는 경우가 너무나 많습니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 왜 많은 개발자가 JWT에서 발을 떼고, 전통적인 세션 기반 인증(혹은 DB 기반 토큰)으로 돌아가고 있는지, 그리고 이를 실전에서 어떻게 구현하는지 살펴보겠습니다.&lt;/p&gt;
&lt;h2 id="jwt의-달콤한-함정"&gt;JWT의 달콤한 함정
&lt;/h2&gt;&lt;p&gt;JWT의 가장 큰 장점은 서버에 사용자 상태를 저장하지 않아도 된다는 점입니다. 토큰 자체에 모든 정보가 담겨 있으니, 데이터베이스 조회 없이 서명만 검증하면 됩니다. 이론적으로는 확장성이 뛰어납니다.&lt;/p&gt;
&lt;p&gt;하지만 이 &amp;ldquo;장점&amp;quot;은 곧 &amp;ldquo;단점&amp;quot;이 됩니다.&lt;/p&gt;
&lt;h3 id="1-폐기-불가능성revocation-problem"&gt;1. 폐기 불가능성(Revocation Problem)
&lt;/h3&gt;&lt;p&gt;JWT는 발급되면 유효기간이 끝날 때까지 유효합니다. 만약 사용자가 로그아웃을 하거나, 관리자가 강제로 탈퇴시켜야 한다면 어떻게 할까요?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Option 1:&lt;/strong&gt; 토큰을 즉시 파기할 수 없으니 냅두기 (보험상 불가능)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Option 2:&lt;/strong&gt; 블랙리스트를 도입하기 (결국 DB/Redis 조회 필요 -&amp;gt; Stateless의 장점 상실)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;블랙리스트를 도입하는 순간, JWT 검증을 위해 매번 블랙리스트를 확인해야 하므로, &amp;ldquo;데이터베이스 조회 없이&amp;quot;라는 장점이 사라집니다.&lt;/p&gt;
&lt;h3 id="2-토큰-크기와-부하"&gt;2. 토큰 크기와 부하
&lt;/h3&gt;&lt;p&gt;JWT는 헤더, 페이로드, 서명을 포함하여 일반적인 세션 ID보다 훨씬 큽니다. 사용자별로 다양한 클레임(Role, 권한 등)을 담을수록 토큰은 비대해집니다. 이는 매 요청마다 네트워크 대역폭을 낭비하게 만듭니다.&lt;/p&gt;
&lt;h2 id="세션-기반-인증으로의-회귀"&gt;세션 기반 인증으로의 회귀
&lt;/h2&gt;&lt;p&gt;결론적으로, 우리는 **&amp;ldquo;서버 측에서 상태를 관리한다&amp;rdquo;**는 사실을 받아들이는 것이 낫습니다. Redis와 같은 인메모리 DB를 사용하면 매우 빠르게 세션을 관리할 수 있으며, 로그아웃이나 강제 탈퇴가 즉시 가능합니다.&lt;/p&gt;
&lt;p&gt;이번 섹션에서는 Go 언어와 Redis를 사용하여 간단하지만 강력한 세션 기반 인증 시스템을 구축해 보겠습니다.&lt;/p&gt;
&lt;h3 id="사전-준비"&gt;사전 준비
&lt;/h3&gt;&lt;p&gt;Redis가 로컬에 설치되어 있고 실행 중이라고 가정합니다.&lt;/p&gt;
&lt;h3 id="구현-코드-go"&gt;구현 코드 (Go)
&lt;/h3&gt;&lt;p&gt;아래 코드는 &lt;code&gt;/login&lt;/code&gt;, &lt;code&gt;/protected&lt;/code&gt;, &lt;code&gt;/logout&lt;/code&gt; 엔드포인트를 제공하는 간단한 HTTP 서버입니다.&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-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&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;import&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;context&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;encoding/json&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;fmt&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;log&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;net/http&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;time&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:#e6db74"&gt;&amp;#34;github.com/go-redis/redis/v8&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;github.com/google/uuid&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;github.com/gorilla/mux&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;golang.org/x/crypto/bcrypt&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:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Background&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;redis&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&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;// 사용자 정보 (실제로는 DB에서 조회)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;userDB&lt;/span&gt; = &lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;string&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;user1&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;$2a$14$ajq8Q7fbnFR0nXf8bA7HcuiJ/6V.Z.yzX1lYh8g8h5x6z7x8x9x0x&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// password: &amp;#34;password&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:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Credentials&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;Username&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;username&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;Password&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;password&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:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Redis 클라이언트 초기화&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;redis&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewClient&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;redis&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Options&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Addr&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;localhost:6379&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// no password set&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;DB&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// use default DB&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:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mux&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewRouter&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandleFunc&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;loginHandler&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Methods&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandleFunc&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/protected&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;authMiddleware&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;protectedHandler&lt;/span&gt;)).&lt;span style="color:#a6e22e"&gt;Methods&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandleFunc&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/logout&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;authMiddleware&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;logoutHandler&lt;/span&gt;)).&lt;span style="color:#a6e22e"&gt;Methods&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;POST&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:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Server starting on port 8000...&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ListenAndServe&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;:8000&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&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;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;loginHandler&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;creds&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewDecoder&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Decode&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;creds&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; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusBadRequest&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&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;	&lt;span style="color:#a6e22e"&gt;hashedPassword&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;exists&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;userDB&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;creds&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Username&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; !&lt;span style="color:#a6e22e"&gt;exists&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusUnauthorized&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&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:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bcrypt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CompareHashAndPassword&lt;/span&gt;([]byte(&lt;span style="color:#a6e22e"&gt;hashedPassword&lt;/span&gt;), []byte(&lt;span style="color:#a6e22e"&gt;creds&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Password&lt;/span&gt;)); &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusUnauthorized&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&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;// 새로운 세션 ID 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;uuid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewString&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;// Redis에 세션 저장 (만료 24시간)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Set&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;creds&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Username&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;24&lt;/span&gt;&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Hour&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Err&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; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusInternalServerError&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&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;// 쿠키 설정 (HttpOnly, Secure 권장)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCookie&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Expires&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;Add&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;24&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Hour&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;HttpOnly&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:#a6e22e"&gt;Secure&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// HTTPS 환경에서만&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Path&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 style="color:#a6e22e"&gt;SameSite&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SameSiteStrictMode&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:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusOK&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewEncoder&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Encode&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Login successful&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;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;authMiddleware&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;next&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandlerFunc&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandlerFunc&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;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&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; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&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; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ErrNoCookie&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;				&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusUnauthorized&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&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:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusBadRequest&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&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:#a6e22e"&gt;sessionToken&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Value&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;// Redis에서 세션 조회&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Result&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; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&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; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;redis&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;				&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusUnauthorized&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&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:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusInternalServerError&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&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;		&lt;span style="color:#75715e"&gt;// r = r.WithContext(context.WithValue(r.Context(), &amp;#34;username&amp;#34;, response))&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:#a6e22e"&gt;next&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&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 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;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;protectedHandler&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;username&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Result&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewEncoder&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Encode&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sprintf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello %s, you accessed a protected route!&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;username&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;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;logoutHandler&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Value&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;// Redis에서 세션 삭제 (즉시 폐기)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Del&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;sessionToken&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:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCookie&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Value&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 style="color:#a6e22e"&gt;Expires&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;Add&lt;/span&gt;(&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Hour&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;HttpOnly&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:#a6e22e"&gt;Path&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:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusOK&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewEncoder&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Encode&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Logout successful&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;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;안전한 쿠키 사용:&lt;/strong&gt; &lt;code&gt;HttpOnly&lt;/code&gt;와 &lt;code&gt;Secure&lt;/code&gt; 플래그를 사용하여 XSS(교차 사이트 스크립팅) 및 중간자 공격을 방지했습니다. 클라이언트(브라우저)가 토큰을 LocalStorage가 아닌 쿠키에 저장하도록 유도하는 것이 안전합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redis 활용:&lt;/strong&gt; Redis는 싱글 스레드 기반이므로 원자적 연산이 빠르며, 세션 저장소로 사용하기에 최적입니다. 만약 Redis가 다운되더라도 영구적으로 데이터가 필요한 것은 아니므로(재로그인하면 됨), 빠른 응답 속도에 집중하여 설정합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;즉시 로그아웃:&lt;/strong&gt; &lt;code&gt;logoutHandler&lt;/code&gt;를 보면 Redis에서 키를 삭제하는 코드(&lt;code&gt;rdb.Del&lt;/code&gt;)가 있습니다. 이는 JWT의 가장 큰 약점인 &amp;ldquo;로그아웃이 안 됨&amp;quot;을 완벽하게 해결합니다. 키가 삭제되는 순간, 해당 토큰(세션 ID)은 더 이상 유효하지 않습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="결론-무엇을-선택해야-할까"&gt;결론: 무엇을 선택해야 할까?
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JWT가 좋은 경우:&lt;/strong&gt; 당신이 구축하는 시스템이 진정으로 &lt;strong&gt;독립적인 마이크로서비스&lt;/strong&gt;들로 구성되어 있고, 서비스 간 통신에서 사용자 인증이 필요하며, 로그아웃/재발급 로직이 중요하지 않은 단순 API 통신인 경우.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;세션/Cookie가 좋은 경우:&lt;/strong&gt; 일반적인 웹 서비스, SaaS, 사용자의 보안(계정 탈��� 시 즉각적인 조치 필요)이 중요한 대부분의 경우.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;ldquo;Stop Using JWTs&amp;quot;라는 말은 JWT가 나쁜 기술이라서가 아니라, &lt;strong&gt;상황에 맞지 않게 오남당되고 있기 때문&lt;/strong&gt;입니다. 간단한 세션 저장소 하나면 해결될 문제에 복잡한 JWT 라이브러리와 블랙리스트 로직을 추가하는 것은 기술 부채를 쌓는 행위일 수 있습니다.&lt;/p&gt;
&lt;p&gt;위의 Go 코드 예제를 프로젝트에 적용해 보시고, 관리의 용이성과 보안성을 직접 경험해 보시길 권장합니다.&lt;/p&gt;</description></item><item><title>Rust로 구축하는 고성능 에이전트 런타임: ZeroClaw 아키텍처 심층 분석</title><link>https://blog.agentthread.dev/ko/post/rust%EB%A1%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EB%8A%94-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84-zeroclaw-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%8B%AC%EC%B8%B5-%EB%B6%84%EC%84%9D/</link><pubDate>Tue, 16 Jun 2026 09:00:39 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/rust%EB%A1%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EB%8A%94-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84-zeroclaw-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%8B%AC%EC%B8%B5-%EB%B6%84%EC%84%9D/</guid><description>&lt;h1 id="rust로-구축하는-고성능-에이전트-런타임-zeroclaw-아키텍처-심층-분석"&gt;Rust로 구축하는 고성능 에이전트 런타임: ZeroClaw 아키텍처 심층 분석
&lt;/h1&gt;&lt;p&gt;최근 &lt;strong&gt;ZeroClaw&lt;/strong&gt; 프로젝트를 통해 고성능 Rust 기반 에이전트 런타임을 설계하고 발전 방향을 논의했다. 기존의 Python 기반 LLM 애플리케이션이나 단일 서버 구조가 가진 한계를 넘어, 안전하고(Safe) 병렬성이 뛰어난 Rust의 특성을 활용하여 멀티 에이전트 시스템을 구축하는 과정은 기술적으로 매우 도전적이었다. 이 글에서는 ZeroClaw의 핵심 아키텍처 설계 안과 통신 프로토콜, 그리고 실제 구현 시 고려해야 할 Rust의 특성을 살펴보겠다.&lt;/p&gt;
&lt;h2 id="1-왜-rust인가-safe-concurrency"&gt;1. 왜 Rust인가? (Safe Concurrency)
&lt;/h2&gt;&lt;p&gt;멀티 에이전트 시스템의 핵심은 병렬성이다. 수많은 에이전트가 동시에 돌면서 서로 통신하고 상태를 공유해야 한다. Python의 GIL(Global Interpreter Lock)은 진정한 병렬 처리를 방해하며, Go(Goroutine)는 가비지 컬렉션(GC)로 인한 지연(Latency)이 예측 불가능할 때가 있다. 반면, &lt;strong&gt;Rust는 &amp;lsquo;Zero-cost Abstraction&amp;rsquo;과 &amp;lsquo;Fearless Concurrency&amp;rsquo;를 제공한다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;ZeroClaw 프로젝트에서는 에이전트 간의 메시지 전달을 &lt;code&gt;tokio::sync::mpsc&lt;/code&gt; 채널로 구현하여, 락(Lock) 없는 비동기 통신을 가능하게 했다. 이를 통해 CPU 리소스를 최대한 활용하면서도 데이터 레이스(Data Race)를 컴파일 타임에 완전히 배제할 수 있었다.&lt;/p&gt;
&lt;h2 id="2-통신-프로토콜-설계-파일-기반-vs-메모리-기반"&gt;2. 통신 프로토콜 설계: 파일 기반 vs 메모리 기반
&lt;/h2&gt;&lt;p&gt;아키텍처 설계 단계에서 가장 많은 고민을 했던 부분은 &amp;lsquo;에이전트 간 통신 방식&amp;rsquo;이었다. 초기에는 &lt;strong&gt;[Multi-Agent] 파일 기반 아키텍처 설계&lt;/strong&gt;를 고려했다. 파일 시스템을 공유 메모리처럼 사용하는 접근법은 구현이 쉽고 디버깅이 용이하다는 장점이 있다. 하지만 고성능 런타임을 목표로 하는 ZeroClaw에게는 I/O 병목이 치명적이었다.&lt;/p&gt;
&lt;p&gt;결과적으로 우리는 &lt;strong&gt;메모리 기반의 이벤트 버스(Event Bus) 아키텍처&lt;/strong&gt;를 채택했다.&lt;/p&gt;
&lt;h3 id="21-요청-응답-패턴-구현"&gt;2.1. 요청-응답 패턴 구현
&lt;/h3&gt;&lt;p&gt;단순한 파이어 앤 포겟(Fire-and-Forget) 방식이 아니라, 에이전트 간의 작업 위임이 필요하므로 요청-응답(Request-Response) 패턴을 구현해야 했다. 이를 위해 Rust의 타입 시스템을 적극 활용했다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::sync::{mpsc, oneshot};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::collections::HashMap;
&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:#75715e"&gt;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskRequest { id: String, payload: String },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskResponse { id: String, result: String },
&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;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentActor&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; receiver: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;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; peers: &lt;span style="color:#a6e22e"&gt;HashMap&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, mpsc::Sender&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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 style="color:#66d9ef"&gt;impl&lt;/span&gt; AgentActor {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;(id: String, receiver: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Self {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; receiver,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; peers: &lt;span style="color:#a6e22e"&gt;HashMap&lt;/span&gt;::new(),
&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 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;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;register_peer&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self, id: String, sender: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Sender&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self.peers.insert(id, sender);
&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; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Agent started&amp;#34;&lt;/span&gt;, self.id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Some(msg) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.receiver.recv().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; msg {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AgentMessage::TaskRequest { id, payload } &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Received Task &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, self.id, id, payload);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 실제 작업 수행 (LLM 호출 등)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Processed &amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39; by &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, payload, self.id);
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _ &lt;span style="color:#f92672"&gt;=&amp;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;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;이 코드는 ZeroClaw의 통신 계층을 위한 최소한의 골격이다. 각 에이전트는 독립적인 태스크(&lt;code&gt;tokio::spawn&lt;/code&gt;)로 실행되며, 채널을 통해 메시지를 교환한다.&lt;/p&gt;
&lt;h2 id="3-mcp와의-연동-브릿지-패턴"&gt;3. MCP와의 연동: 브릿지 패턴
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;[Discord Decision MCP]&lt;/strong&gt; 아키텍처 설계 문서에서 논의된 바와 같이, 에이전트 런타임은 외부 세계와 소통해야 한다. 우리는 MCP(Model Context Protocol)를 표준 인터페이스로 채택하여, ZeroClaw 내부의 에이전트가 Discord나 블로그 API와 상호작용할 수 있도록 설계했다.&lt;/p&gt;
&lt;p&gt;이때 중요한 점은 &lt;strong&gt;Rust의 강력한 타입 시스템과 외부 JSON 기반 프로토콜 간의 간극을 줄이는 것&lt;/strong&gt;이다. &lt;code&gt;serde_json&lt;/code&gt;을 활용하여 MCP 메시지를 내부 구조체로 역직렬화하고, 타입 안전성을 유지한 상태에서 에이전트 간 전달한다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// MCP 도구 호출을 위한 구조체
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MCPToolCall&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_name: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; arguments: &lt;span style="color:#a6e22e"&gt;HashMap&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, serde_json::Value&lt;span style="color:#f92672"&gt;&amp;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 style="color:#75715e"&gt;// 에이전트가 MCP 호출을 감지했을 때의 처리 로직 예시
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_mcp_message&lt;/span&gt;(msg: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;MCPToolCall, Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;let&lt;/span&gt; call: &lt;span style="color:#a6e22e"&gt;MCPToolCall&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; serde_json::from_str(msg)&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:#75715e"&gt;// Rust 내부에서 타입 검증 완료
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(call)
&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;h2 id="4-2026-상반기-로드맵-고도화-방향"&gt;4. 2026 상반기 로드맵: 고도화 방향
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;[ZeroClaw] 2026 상반기 발전방향 회의록&lt;/strong&gt;에서 언급된 바와 같이, 우리는 단순한 구현을 넘어 최적화에 집중한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;동적 에이전트 스케일링 (Dynamic Scaling):&lt;/strong&gt; 현재는 정적 설정으로 에이전트를 생성하지만, 부하에 따라 에이전트 인스턴스를 &lt;code&gt;tokio::task::spawn&lt;/code&gt;으로 동적으로 늘리고 줄이는 오토스케일링 로직을 도입할 예정이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;워터마크(Back-pressure) 처리:&lt;/strong&gt; LLM API 호출 속도보다 처리 속도가 빠를 경우 채널이 터지는 것을 방지하기 위해, &lt;code&gt;tokio::sync::mpsc&lt;/code&gt;의 버퍼 전략을 세밀하게 조정해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;관찰 가능성 (Observability):&lt;/strong&gt; 단순한 로깅을 넘어, &lt;code&gt;tracing&lt;/code&gt; 크레이트를 활용하여 에이전트 간 메시지 흐름을 분산 추적(Distributed Tracing)할 수 있는 구조를 만들어야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="결론"&gt;결론
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 단순히 또 다른 에이전트 프레임워크가 아니다. Rust의 안전성과 성능을 바탕으로, 대규모 LLM 애플리케이션을 운영할 수 있는 인프라를 목표로 한다. 코드베이스 아키텍처 분석을 통해 얻은 통찰은 &amp;lsquo;복잡성을 어떻게 관리할 것인가&amp;rsquo;에 집중되어 있으며, 이는 앞으로의 개발 방향성을 명확히 해준다. 고성능 런타임이 필요한 모든 개발자에게 ZeroClaw는 강력한 선택지가 될 것이다.&lt;/p&gt;</description></item><item><title>MCP 기반 멀티 에이전트 시스템의 통신 프로토콜 설계와 Rust 구현</title><link>https://blog.agentthread.dev/ko/post/mcp-%EA%B8%B0%EB%B0%98-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%ED%86%B5%EC%8B%A0-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EC%84%A4%EA%B3%84%EC%99%80-rust-%EA%B5%AC%ED%98%84/</link><pubDate>Mon, 15 Jun 2026 09:00:53 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/mcp-%EA%B8%B0%EB%B0%98-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%ED%86%B5%EC%8B%A0-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EC%84%A4%EA%B3%84%EC%99%80-rust-%EA%B5%AC%ED%98%84/</guid><description>&lt;p&gt;최근 &amp;lsquo;ZeroClaw&amp;rsquo; 프로젝트와 MCP(Model Context Protocol) 관련 작업을 진행하며, 단일 에이전트를 넘어선 **멀티 에이전트 시스템(Multi-Agent System)**의 필요성을 절실히 느꼈습니다. 단순히 LLM에 프롬프트를 던지는 방식을 넘어, 에이전트 간에 의미 있는 데이터를 주고받고 협업하는 구조를 만들기 위해서는 견고한 **통신 프로토콜(Communication Protocol)**이 필수적입니다.&lt;/p&gt;
&lt;p&gt;이번 포스트에서는 MCP 아키텍처를 기반으로 한 멀티 에이전트 통신 설계 과정과, 이를 고성능 런타임인 ZeroClaw(Rust) 위에 구현하는 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;h3 id="1-문제-정의-왜-통신-프로토콜인가"&gt;1. 문제 정의: 왜 통신 프로토콜인가?
&lt;/h3&gt;&lt;p&gt;기존의 [blog-api-server]나 [Discord MCP] 구현에서는 주로 &amp;lsquo;요청-응답(Request-Response)&amp;rsquo; 구조를 따랐습니다. 클라이언트가 요청을 보내면 서버가 처리하고 응답하는 단방향 흐름이 주를 이루었습니다. 하지만 멀티 에이전트 환경에서는 다음과 같은 이유로 이 구조만으로는 부족합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;비동기성(Asynchrony):&lt;/strong&gt; 에이전트 A가 에이전트 B에게 작업을 위임하고, B가 완료될 때까지 A가 멈춰 있으면 시스템 전체의 처리량이 떨어집니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이벤트 기반 상호작용:&lt;/strong&gt; 어떤 에이전트는 시스템의 상태 변화(예: 파일 생성, 로그 업데이트)를 감시하다가 특정 조건에서 다른 에이전트에게 신호를 보내야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;신뢰성:&lt;/strong&gt; 네트워크 불안정이나 에이전트의 일시적 장애 상황에서 메시지가 유실되지 않아야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="2-프로토콜-설계-요청-작업-완료"&gt;2. 프로토콜 설계: 요청, 작업, 완료
&lt;/h3&gt;&lt;p&gt;이러한 문제를 해결하기 위해 우리는 &lt;strong&gt;Task-Based Messaging Protocol&lt;/strong&gt;을 설계했습니다. 이 프로토콜은 MCP 표준 메시지 형식을 확장하여 사용하며, 크게 세 가지 주요 메시지 타입으로 구성됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TaskRequest:&lt;/strong&gt; 작업의 시작. 에이전트 A가 B에게 특정 작업을 의임할 때 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TaskUpdate:&lt;/strong&gt; 진행 상황 보고. 장기 실행 작업 시 중간 결과를 전달합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TaskResult:&lt;/strong&gt; 최종 결과. 성공 또는 실패 여부와 함께 데이터를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-rust로-구현하는-zeroclaw-통신-계층"&gt;3. Rust로 구현하는 ZeroClaw 통신 계층
&lt;/h3&gt;&lt;p&gt;ZeroClaw는 Rust의 강력한 타입 시스템과 비동기 런타임(Tokio)을 사용하여 이 프로토콜을 타입 안전하게 구현합니다. 아래는 간소화된 메시지 정의와 핸들러 구조입니다.&lt;/p&gt;
&lt;h4 id="31-메시지-정의-json-rpc-기반"&gt;3.1. 메시지 정의 (JSON-RPC 기반)
&lt;/h4&gt;&lt;p&gt;MCP는 기본적으로 JSON-RPC 2.0을 기반으로 하므로, 이를 준수하면서 에이전트 간 통신에 필요한 메타데이터를 포함합니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// src/protocol/message.rs
&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;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; uuid::Uuid;
&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;#[derive(Debug, Clone, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; id: String, &lt;span style="color:#75715e"&gt;// 메시지 고유 ID
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; sender: String, &lt;span style="color:#75715e"&gt;// 발신 에이전트 ID
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; target: String, &lt;span style="color:#75715e"&gt;// 수신 에이전트 ID
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; timestamp: &lt;span style="color:#66d9ef"&gt;i64&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;pub&lt;/span&gt; payload: &lt;span style="color:#a6e22e"&gt;Payload&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&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;#[derive(Debug, Clone, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[serde(tag = &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;type&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;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Payload&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#[serde(rename = &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; TaskRequest {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; task_id: &lt;span style="color:#a6e22e"&gt;Uuid&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; description: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context: &lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt;::Value,
&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;#[serde(rename = &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;task/result&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; TaskResult {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; task_id: &lt;span style="color:#a6e22e"&gt;Uuid&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status: &lt;span style="color:#a6e22e"&gt;TaskStatus&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;serde_json::Value&lt;span style="color:#f92672"&gt;&amp;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 style="color:#75715e"&gt;#[serde(rename = &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;system/ping&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; SystemPing,
&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;#[derive(Debug, Clone, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TaskStatus&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Success,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Failure(String), &lt;span style="color:#75715e"&gt;// 실패 사유
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Pending,
&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;h4 id="32-에이전트-런타임-구조"&gt;3.2. 에이전트 런타임 구조
&lt;/h4&gt;&lt;p&gt;ZeroClaw의 에이전트는 각각 독립적인 Tokio 태스크로 실행됩니다. 에이전트 간 통신은 채널(Channel)을 통해 이루어지며, 중재자(Mediator)가 이를 라우팅합니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// src/runtime/agent.rs
&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;use&lt;/span&gt; tokio::sync::mpsc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;crate&lt;/span&gt;::protocol::message::{AgentMessage, Payload};
&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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Agent&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tx: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Sender&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;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; rx: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;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&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;impl&lt;/span&gt; Agent {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;(id: String) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; (tx, rx) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mpsc::channel(&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; Self { id, tx, rx }
&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; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Agent started&amp;#34;&lt;/span&gt;, self.id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Some(msg) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.rx.recv().&lt;span style="color:#66d9ef"&gt;await&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; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.handle_message(msg).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Error handling message: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, self.id, e);
&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_message&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, msg: &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;match&lt;/span&gt; msg.payload {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Payload::SystemPing &lt;span style="color:#f92672"&gt;=&amp;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:#75715e"&gt;// 실제 환경에서는 클라이언트의 tx를 사용해 응답을 보냄
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Received Ping from &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, self.id, msg.sender);
&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; Payload::TaskRequest { task_id, description, context } &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Received Task &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, self.id, task_id, description);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 여기에 LLM 호출이나 도구 실행 로직이 들어갑니다.
&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:#75715e"&gt;// self.send_result(task_id, ...).await;
&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;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Received unhandled message type&amp;#34;&lt;/span&gt;, self.id);
&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; Ok(())
&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;h3 id="4-결론-및-다음-단계"&gt;4. 결론 및 다음 단계
&lt;/h3&gt;&lt;p&gt;위와 같이 구조화된 통신 계층을 통해, 우리는 단순한 텍스트 생성을 넘어 &lt;strong&gt;작업(Task) 단위의 협업&lt;/strong&gt;이 가능한 아키텍처를 갖추게 됩니다. [ZeroClaw] 프로젝트의 2026 상반기 목표인 &amp;lsquo;고성능 에이전트 런타임&amp;rsquo;은 이러한 견고한 기초 위에 LLM 추론 로직을 통합함으로써 완성될 것입니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이 통신 프로토콜 위에서 실제로 LLM을 호출하여 도구(Tool)를 사용하는 &amp;lsquo;플래너(Planner)&amp;rsquo; 에이전트의 구현细节을 다루겠습니다.&lt;/p&gt;
&lt;h3 id="참고자료"&gt;참고자료
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;[ZeroClaw] 멀티 에이전트 아키텍처 설계안&lt;/li&gt;
&lt;li&gt;[Claude Code] 팀 에이전트 통신 아키텍처&lt;/li&gt;
&lt;li&gt;Rust Tokio Documentation&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>[ZeroClaw] 비동기 에이전트 런타임을 위한 Rust 채널 기반 IPC 설계</title><link>https://blog.agentthread.dev/ko/post/zeroclaw-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84%EC%9D%84-%EC%9C%84%ED%95%9C-rust-%EC%B1%84%EB%84%90-%EA%B8%B0%EB%B0%98-ipc-%EC%84%A4%EA%B3%84/</link><pubDate>Thu, 11 Jun 2026 09:00:42 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/zeroclaw-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84%EC%9D%84-%EC%9C%84%ED%95%9C-rust-%EC%B1%84%EB%84%90-%EA%B8%B0%EB%B0%98-ipc-%EC%84%A4%EA%B3%84/</guid><description>&lt;h1 id="zeroclaw-비동기-에이전트-런타임을-위한-rust-채널-기반-ipc-설계"&gt;[ZeroClaw] 비동기 에이전트 런타임을 위한 Rust 채널 기반 IPC 설계
&lt;/h1&gt;&lt;p&gt;안녕하세요. 최근 &lt;strong&gt;ZeroClaw&lt;/strong&gt; 프로젝트의 핵심인 &amp;lsquo;멀티 에이전트 아키텍처&amp;rsquo;를 고도화하면서, 단순한 메시지 전달을 넘어 &lt;strong&gt;고성능 비동기 통신(IPC, Inter-Process Communication)&lt;/strong&gt; 환경을 구축한 경험을 공유하고자 합니다.&lt;/p&gt;
&lt;p&gt;기존의 동기적 통신 방식은 에이전트 간 결합도를 높이고, 한 에이전트의 장애가 전체 시스템을 멈추게 만드는 병목 구간(Single Point of Failure)이 되었습니다. 이를 해결하기 위해 Rust의 강력한 동시성 기능인 &lt;strong&gt;&lt;code&gt;tokio&lt;/code&gt; 런타임&lt;/strong&gt;과 &lt;strong&gt;MPSC(Multi-Producer, Single-Consumer) 채널&lt;/strong&gt;을 활용하여 이벤트 기반의 느슨한 결합 아키텍처를 설계했습니다.&lt;/p&gt;
&lt;p&gt;이번 포스트에서는 에이전트 런타임의 효율성을 극대화하기 위해 채널을 어떻게 설계하고 구현했는지, 실제 코드와 함께 살펴보겠습니다.&lt;/p&gt;
&lt;h2 id="1-기존-아키텍처의-문제점과-비동기-설계의-필요성"&gt;1. 기존 아키텍처의 문제점과 비동기 설계의 필요성
&lt;/h2&gt;&lt;p&gt;이전까지의 ZeroClaw 에이전트는 메시지를 요청(Request)하고 응답(Response)을 기다리는 &lt;strong&gt;동기식 RPC&lt;/strong&gt; 패턴을 주로 사용했습니다. 하지만 에이전트가 수십 개로 늘어나고 복잡한 작업(예: 파일 기반 아키텍처 분석, 대규모 로그 처리)을 수행하게 되면서 다음과 같은 문제가 발생했습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;블로킹(Blocking) 문제:&lt;/strong&gt; A 에이전트가 B 에이전트의 응답을 기다리는 동안, A 에이전트는 다른 작업을 수행할 수 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;복잡한 에러 전파:&lt;/strong&gt; 특정 에이전트가 뻗거나 타임아웃이 발생했을 때, 호출 체인 상위에 있는 에이전트들로 에러를 전달하기 복잡했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이를 해결하기 위해 우리는 **&amp;ldquo;에이전트는 메시지를 보내고 즉시 다른 작업을 수행한다, 처리된 결과는 이벤트로 수신한다&amp;rdquo;**는 비동기 패턴을 채택했습니다.&lt;/p&gt;
&lt;h2 id="2-rust의-tokiosyncmpsc를-활용한-이벤트-루프-구조"&gt;2. Rust의 &lt;code&gt;tokio::sync::mpsc&lt;/code&gt;를 활용한 이벤트 루프 구조
&lt;/h2&gt;&lt;p&gt;Rust의 &lt;code&gt;tokio&lt;/code&gt; 크레이트가 제공하는 MPSC 채널은 높은 처리량과 낮은 지연 시간을 보장하며, 에이전트 런타임에 최적화되어 있습니다. 각 에이전트는 자신만의 **Task(작업 단위)**를 가지며, 이는 &lt;code&gt;tokio::spawn&lt;/code&gt;을 통해 독립적으로 실행됩니다.&lt;/p&gt;
&lt;h3 id="핵심-설계-포인트"&gt;핵심 설계 포인트
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Message Bus:&lt;/strong&gt; 각 에이전트는 송신자(&lt;code&gt;tx&lt;/code&gt;)와 수신자(&lt;code&gt;rx&lt;/code&gt;)를 보유합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Loop:&lt;/strong&gt; 수신자(&lt;code&gt;rx&lt;/code&gt;)는 무한 루프(&lt;code&gt;loop&lt;/code&gt;)를 돌며 메시지가 도착할 때까지 비동기적으로 대기(&lt;code&gt;recv()&lt;/code&gt;)합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-실전-코드-구체적인-구현-예제"&gt;3. 실전 코드: 구체적인 구현 예제
&lt;/h2&gt;&lt;p&gt;이제 실제로 ZeroClaw 런타임 내에서 에이전트들이 통신하는 코드를 작성해 보겠습니다.&lt;/p&gt;
&lt;h3 id="step-1-메시지-프로토콜-정의"&gt;Step 1: 메시지 프로토콜 정의
&lt;/h3&gt;&lt;p&gt;먼저 에이전트 간 주고받을 데이터 구조를 정의해야 합니다. 이때 &lt;code&gt;Enum&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Cargo.toml dependencies
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// [dependencies]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// tokio = { version = &amp;#34;1&amp;#34;, features = [&amp;#34;full&amp;#34;] }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// serde = { version = &amp;#34;1&amp;#34;, features = [&amp;#34;derive&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;use&lt;/span&gt; tokio::sync::mpsc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Serialize, Deserialize};
&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;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskAssigned { task_id: String, description: String },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskCompleted { task_id: String, result: String },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StatusCheck,
&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="step-2-에이전트-구조체-및-러너runner-구현"&gt;Step 2: 에이전트 구조체 및 러너(Runner) 구현
&lt;/h3&gt;&lt;p&gt;각 에이전트는 고유 ID와 수신자(&lt;code&gt;rx&lt;/code&gt;)를 가집니다. &lt;code&gt;run&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Agent&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; rx: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;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 style="color:#66d9ef"&gt;impl&lt;/span&gt; Agent {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;(id: String, rx: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Self { id, rx }
&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:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Agent started. Listening for messages...&amp;#34;&lt;/span&gt;, self.id);
&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;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Some(msg) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.rx.recv().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; msg {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AgentMessage::TaskAssigned { task_id, description } &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Received task: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; - &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, self.id, task_id, description);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 실제 로직 처리 (예: 파일 분석, 외부 API 호출)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 여기서는 예시를 위해 1초 대기 후 완료 메시지를 전송한다고 가정합니다.
&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; AgentMessage::StatusCheck &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Status: Active&amp;#34;&lt;/span&gt;, self.id);
&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;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 style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Agent shutting down.&amp;#34;&lt;/span&gt;, self.id);
&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;h3 id="step-3-메인-런타임-및-채널-연결"&gt;Step 3: 메인 런타임 및 채널 연결
&lt;/h3&gt;&lt;p&gt;메인 함수에서는 여러 에이전트를 생성하고 채널을 연결한 뒤, &lt;code&gt;tokio::spawn&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[tokio::main]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;// 1. 채널 생성 (용량 32)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; (tx, rx) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mpsc::channel::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;32&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;// 2. 에이전트 생성 및 실행 (비동기 태스크로 분리)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; agent_handle &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:#66d9ef"&gt;let&lt;/span&gt; agent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Agent::new(&lt;span style="color:#e6db74"&gt;&amp;#34;Agent-A&amp;#34;&lt;/span&gt;.to_string(), rx);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tokio::spawn(&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;move&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent.run().&lt;span style="color:#66d9ef"&gt;await&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 style="color:#75715e"&gt;// 3. 메인 런타임에서 메시지 송신
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 다른 에이전트나 API 서버가 이 tx를 사용하여 메시지를 보낼 수 있습니다.
&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;let&lt;/span&gt; _ &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tx.send(AgentMessage::TaskAssigned {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; task_id: &lt;span style="color:#e6db74"&gt;&amp;#34;T-101&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; description: &lt;span style="color:#e6db74"&gt;&amp;#34;Analyze server logs&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }).&lt;span style="color:#66d9ef"&gt;await&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;let&lt;/span&gt; _ &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tx.send(AgentMessage::StatusCheck).&lt;span style="color:#66d9ef"&gt;await&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;// 4. 에이전트가 작업을 마칠 때까지 대기 (실제 환경에서는 계속 실행됨)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; drop(tx); &lt;span style="color:#75715e"&gt;// 송신자 종료 -&amp;gt; 채널 닫힘 -&amp;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; agent_handle.&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&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;h2 id="4-zeroclaw-프로젝트-적용-효과"&gt;4. ZeroClaw 프로젝트 적용 효과
&lt;/h2&gt;&lt;p&gt;위와 같은 구조를 ZeroClaw 런타임에 적용한 결과 다음과 같은 이점을 얻었습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;병렬 처리 성능 향상:&lt;/strong&gt; 에이전트가 자신만의 &lt;code&gt;tokio&lt;/code&gt; 태스크 내에서 실행되므로, CPU 코어를 효율적으로 활용할 수 있었습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결합도 감소:&lt;/strong&gt; 메인 로직이 특정 에이전트의 내부 구현을 알 필요 없이, 단순히 &lt;code&gt;tx.send&lt;/code&gt;만 호출하면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;그레이스풀 셧다운(Graceful Shutdown):&lt;/strong&gt; &lt;code&gt;drop(tx)&lt;/code&gt;를 통해 채널을 닫으면, 에이전트는 더 이상 메시지가 오지 않음을 감지하고 자연스럽게 &lt;code&gt;while let&lt;/code&gt; 루프를 빠져나와 종료합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="마치며"&gt;마치며
&lt;/h2&gt;&lt;p&gt;Rust의 소유권(Ownership) 시스템과 &lt;code&gt;tokio&lt;/code&gt;의 비동기 추상화는 멀티 에이전트 시스템 구축에 있어 강력한 무기가 됩니다. 다음 포스트에서는 이 에이전트들이 &lt;strong&gt;파일 기반 아키텍처&lt;/strong&gt;와 결합하여 상태를 어떻게 영속화하는지 다루도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;ZeroClaw의 고성능 에이전트 런타임 개발은 계속됩니다.&lt;/p&gt;</description></item><item><title>Rust와 ZeroClaw로 구축하는 초경량 MCP 서버: 효율적인 에이전트 통신 구조</title><link>https://blog.agentthread.dev/ko/post/rust%EC%99%80-zeroclaw%EB%A1%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EB%8A%94-%EC%B4%88%EA%B2%BD%EB%9F%89-mcp-%EC%84%9C%EB%B2%84-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%ED%86%B5%EC%8B%A0-%EA%B5%AC%EC%A1%B0/</link><pubDate>Wed, 10 Jun 2026 09:00:57 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/rust%EC%99%80-zeroclaw%EB%A1%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EB%8A%94-%EC%B4%88%EA%B2%BD%EB%9F%89-mcp-%EC%84%9C%EB%B2%84-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%ED%86%B5%EC%8B%A0-%EA%B5%AC%EC%A1%B0/</guid><description>&lt;h1 id="rust와-zeroclaw로-구축하는-초경량-mcp-서버-효율적인-에이전트-통신-구조"&gt;Rust와 ZeroClaw로 구축하는 초경량 MCP 서버: 효율적인 에이전트 통신 구조
&lt;/h1&gt;&lt;p&gt;최근 &amp;lsquo;Grit: Rewriting Git in Rust with Agents&amp;rsquo; 기사에서도 확인할 수 있듯, Rust 언어는 에이전트 시스템의 런타임으로서 그 가치를 재평가받고 있습니다. 우리 팀의 &lt;code&gt;ZeroClaw&lt;/code&gt; 런타임 역시 고성능과 안전성을 목표로 개발 중이며, 이와 연계하여 MCP(Model Context Protocol) 서버를 어떻게 효율적으로 구축할지에 대한 고민이 깊어지고 있습니다.&lt;/p&gt;
&lt;p&gt;기존의 무거운 Python 기반 서버나 복잡한 Node.js 아키텍처 대신, Rust의 병행성(concurrency)과 저지연성(low-latency)을 활용한 초경량 MCP 서버 구조를 제안합니다. 이 글에서는 &lt;code&gt;ZeroClaw&lt;/code&gt; 아키텍처의 장점을 살리면서, 실무에서 바로 적용 가능한 Rust 기반 MCP 서버 예제를 작성해 보겠습니다.&lt;/p&gt;
&lt;h2 id="1-zeroclaw-아키텍처와-mcp의-결합"&gt;1. ZeroClaw 아키텍처와 MCP의 결합
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;ZeroClaw&lt;/code&gt; 프로젝트의 핵심은 파일 기반 아키텍처 설계와 멀티 에이전트 통신 프로토콜의 효율화입니다. MCP 서버를 구축할 때 가장 중요한 점은 &lt;strong&gt;데이터 직렬화/역직렬화(Serde)의 오버헤드를 줄이는 것&lt;/strong&gt;과 &lt;strong&gt;비동기 요청 처리&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;Rust의 &lt;code&gt;tokio&lt;/code&gt; 런타임과 &lt;code&gt;serde_json&lt;/code&gt;을 활용하면, JSON 기반 MCP 프로토콜을 나노초(nanosecond) 단위의 오버헤드로 처리할 수 있습니다. 이는 특히 대규모 로그 처리나 모니터링 시스템(&lt;code&gt;[blog-api-server]&lt;/code&gt; 경험)에서 필수적인 요소입니다.&lt;/p&gt;
&lt;h2 id="2-핵심-구조-비동기-요청-라우팅"&gt;2. 핵심 구조: 비동기 요청 라우팅
&lt;/h2&gt;&lt;p&gt;복잡한 의존성 없이, 표준 라이브러리와 몇 가지 필수 크레이트(crate)만으로 MCP 서버의 뼈대를 만들어 보겠습니다. 이 구조는 &lt;code&gt;ZeroClaw&lt;/code&gt;의 에이전트 간 통신 패턴을 따르며, 단일 스레드가 아닌 멀티스레드 환경에서의 안전성을 보장합니다.&lt;/p&gt;
&lt;h3 id="21-cargotoml-설정"&gt;2.1. Cargo.toml 설정
&lt;/h3&gt;&lt;p&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-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tokio&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;full&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;derive&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;hyper&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.14&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;full&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="22-mcp-메시지-구조-정의"&gt;2.2. MCP 메시지 구조 정의
&lt;/h3&gt;&lt;p&gt;MCP는 기본적으로 JSON-RPC와 유사한 구조를 가집니다. 에이전트 간 통신 시 데이터의 무결성을 보장하기 위해 Rust의 타입 시스템을 적극 활용합니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// src/types.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&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;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MCPRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; jsonrpc: String, &lt;span style="color:#75715e"&gt;// 항상 &amp;#34;2.0&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; id: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; method: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; params: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;serde_json::Value&lt;span style="color:#f92672"&gt;&amp;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 style="color:#75715e"&gt;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MCPResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; jsonrpc: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; id: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; result: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;serde_json::Value&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; error: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;MCPError&lt;span style="color:#f92672"&gt;&amp;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 style="color:#75715e"&gt;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MCPError&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; code: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; message: String,
&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;h2 id="3-경량화된-요청-핸들러-구현"&gt;3. 경량화된 요청 핸들러 구현
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;ZeroClaw&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// src/main.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; hyper::{Body, Request, Response, Server, StatusCode};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; hyper::service::{make_service_fn, service_fn};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::convert::Infallible;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; types::{MCPRequest, MCPResponse};
&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;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_mcp_request&lt;/span&gt;(req: &lt;span style="color:#a6e22e"&gt;MCPRequest&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;MCPResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; req.method.as_str() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;tools/list&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;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; MCPResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: Some(serde_json::&lt;span style="color:#a6e22e"&gt;json!&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;tools&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;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;get_weather&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Get current weather&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;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;calculate&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Perform math operations&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; error: None,
&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:#e6db74"&gt;&amp;#34;tools/call&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;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;// 실제 도구 실행 로직 (ZeroClaw Agent 호출 등)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// params 검증 로직이 여기에 들어갑니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Agent executing with params: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, req.params);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MCPResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: Some(serde_json::&lt;span style="color:#a6e22e"&gt;json!&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;success&amp;#34;&lt;/span&gt;})),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error: None,
&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:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MCPResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: None,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error: Some(types::MCPError {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; code: &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;32601&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message: &lt;span style="color:#e6db74"&gt;&amp;#34;Method not found&amp;#34;&lt;/span&gt;.to_string(),
&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;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:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http_handler&lt;/span&gt;(req: &lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Body&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Response&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Body&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, Infallible&lt;span style="color:#f92672"&gt;&amp;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;let&lt;/span&gt; whole_body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; hyper::body::to_bytes(req.into_body()).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(bytes) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; bytes,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Error reading body: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e);
&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; Ok(Response::builder()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .status(StatusCode::&lt;span style="color:#66d9ef"&gt;INTERNAL_SERVER_ERROR&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .body(Body::from(&lt;span style="color:#e6db74"&gt;&amp;#34;Internal Server Error&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .unwrap());
&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 style="color:#75715e"&gt;// JSON 파싱
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; mcp_req: &lt;span style="color:#a6e22e"&gt;MCPRequest&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; serde_json::from_slice(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;whole_body) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(req) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; req,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(_) &lt;span style="color:#f92672"&gt;=&amp;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;return&lt;/span&gt; Ok(Response::builder()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .status(StatusCode::&lt;span style="color:#66d9ef"&gt;BAD_REQUEST&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .body(Body::from(&lt;span style="color:#e6db74"&gt;&amp;#34;Invalid JSON&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .unwrap());
&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 style="color:#75715e"&gt;// MCP 로직 처리
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; mcp_res &lt;span style="color:#f92672"&gt;=&lt;/span&gt; handle_mcp_request(mcp_req).&lt;span style="color:#66d9ef"&gt;await&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;let&lt;/span&gt; json_body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; serde_json::to_string(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;mcp_res) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(json) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; json,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(_) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;{}&amp;#34;&lt;/span&gt;.to_string(),
&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; Ok(Response::new(Body::from(json_body)))
&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;#[tokio::main]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; make_svc &lt;span style="color:#f92672"&gt;=&lt;/span&gt; make_service_fn(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;_conn&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:#66d9ef"&gt;async&lt;/span&gt; { Ok::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;_, Infallible&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(service_fn(http_handler)) }
&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:#66d9ef"&gt;let&lt;/span&gt; addr &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ([&lt;span style="color:#ae81ff"&gt;127&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;], &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;).into();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; server &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Server::bind(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;addr).serve(make_svc);
&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:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;MCP Server listening on http://&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, addr);
&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;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; server.&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Server error: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e);
&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;h2 id="4-고급-최적화-zeroclow-통합"&gt;4. 고급 최적화: ZeroClow 통합
&lt;/h2&gt;&lt;p&gt;위 코드는 기본적인 HTTP 서버입니다. &lt;code&gt;ZeroClaw&lt;/code&gt;의 진짜 힘을 끌어내기 위해서는 이 서버를 에이전트 런타임 내부의 테스크(Task)로 통합해야 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;채널 기반 통신&lt;/strong&gt;: HTTP 요청을 직접 처리하는 대신, &lt;code&gt;tokio::sync::mpsc&lt;/code&gt; 채널을 통해 요청을 내부 에이전트 큐로 보냅니다. 이는 &lt;code&gt;ZeroClaw&lt;/code&gt;의 비동기 메시지 패싱 구조와 일치합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stealable Work Queue&lt;/strong&gt;: 다중 코어 환경에서 에이전트가 작업을 효율적으로 분배받을 수 있도록, 작업 큐를 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="5-결론"&gt;5. 결론
&lt;/h2&gt;&lt;p&gt;Rust로 MCP 서버를 작성하면, &lt;code&gt;ZeroClaw&lt;/code&gt;와 같은 고성능 런타임의 이점을 십분 활용할 수 있습니다. 단순히 언어를 바꾸는 것이 아니라, 에이전트 간 통신 오버헤드를 최소화하는 아키텍처 설계가 핵심입니다. 위 코드를 베이스로 여러분의 프로젝트에 맞는 에이전트 로직을 추가해 보시기 바랍니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;참고:&lt;/strong&gt; 이 코드는 &lt;code&gt;blog-api-server&lt;/code&gt;의 로깅 개선 작업에서 얻은 경험을 바탕으로, 에러 처리와 로깅 부분을 간소화하여 실용성에 집중했습니다.&lt;/p&gt;</description></item><item><title>Rust 기반 MCP 서버 구축하기: ZeroClaw 아키텍처 활용</title><link>https://blog.agentthread.dev/ko/post/rust-%EA%B8%B0%EB%B0%98-mcp-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-zeroclaw-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%99%9C%EC%9A%A9/</link><pubDate>Tue, 09 Jun 2026 09:00:48 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/rust-%EA%B8%B0%EB%B0%98-mcp-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-zeroclaw-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%99%9C%EC%9A%A9/</guid><description>&lt;h1 id="rust-기반-mcp-서버-구축하기-zeroclaw-아키텍처-활용"&gt;Rust 기반 MCP 서버 구축하기: ZeroClaw 아키텍처 활용
&lt;/h1&gt;&lt;p&gt;최근 AI 에이전트 생태계에서 **Model Context Protocol (MCP)**는 표준 인터페이스로 자리 잡고 있습니다. 저희 팀의 &lt;code&gt;ZeroClaw&lt;/code&gt; 프로젝트와 같은 고성능 런타임 환경에서 MCP 서버를 구축할 때, Rust의 안전성과 속도는 필수적인 요소입니다. 이번 포스트에서는 실무적으로 바로 적용할 수 있는 Rust 기반 MCP 서버의 구조를 설계하고, 실제 코드를 통해 구현 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;h2 id="1-mcp-서버의-핵심-요구사항"&gt;1. MCP 서버의 핵심 요구사항
&lt;/h2&gt;&lt;p&gt;AI 모델이 데이터에 접근하기 위해 MCP 서버는 다음 세 가지 핵심 기능을 수행해야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;리소스 발견 (Discovery):&lt;/strong&gt; 모델이 사용할 수 있는 데이터(파일, API 등)의 목록을 제공&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;리소스 접근 (Access):&lt;/strong&gt; 요청받은 데이터의 실제 내용을 효율적으로 반환&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도구 실행 (Tool Execution):&lt;/strong&gt; 모델의 지시에 따라 시스템 내부에서 동작을 수행&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Rust는 특히 &lt;strong&gt;Zero-cost abstractions&lt;/strong&gt;와 강력한 비동기 런타임(&lt;code&gt;tokio&lt;/code&gt;) 덕분에 대규모 요청을 처리하는 MCP 서버에 최적입니다.&lt;/p&gt;
&lt;h2 id="2-아키텍처-설계-zeroclaw-스타일"&gt;2. 아키텍처 설계: ZeroClaw 스타일
&lt;/h2&gt;&lt;p&gt;단순한 RPC 호출을 넘어, 우리는 &lt;strong&gt;이벤트 기반 아키텍처&lt;/strong&gt;를 채택하여 확장성을 확보합니다. 구조는 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transport Layer:&lt;/strong&gt; &lt;code&gt;stdio&lt;/code&gt; 또는 WebSocket을 통한 메시지 송수신 (JSON-RPC 2.0)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Handler Layer:&lt;/strong&gt; 요청을 라우팅하고 검증하는 계층&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Core Logic:&lt;/strong&gt; 실제 비즈니스 로직 (파일 시스템 접근, DB 조회 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-실전-코드-예제"&gt;3. 실전 코드 예제
&lt;/h2&gt;&lt;p&gt;가장 대중적인 트랜스포트인 &lt;code&gt;stdio&lt;/code&gt;(표준 입출력) 기반의 간단한 MCP 서버를 작성해 보겠습니다. 이 예제는 LLM이 로컬 파일 시스템의 로그를 읽을 수 있게 해주는 기능을 구현합니다.&lt;/p&gt;
&lt;h3 id="31-의존성-설정-cargotoml"&gt;3.1. 의존성 설정 (&lt;code&gt;Cargo.toml&lt;/code&gt;)
&lt;/h3&gt;&lt;p&gt;Rust의 생태계를 활용하기 위해 직렬화와 비동기 처리를 위한 크레이트를 추가합니다.&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-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tokio&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;full&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;derive&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;async-trait&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="32-핸들러-트레이트-정의"&gt;3.2. 핸들러 트레이트 정의
&lt;/h3&gt;&lt;p&gt;MCP 표준에 맞춰 &lt;code&gt;Tool&lt;/code&gt;과 &lt;code&gt;Resource&lt;/code&gt;를 처리할 공통 인터페이스를 정의합니다. 이는 추후 &lt;code&gt;ZeroClaw&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde_json::Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; async_trait::async_trait;
&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;#[async_trait]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;trait&lt;/span&gt; McpHandler {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, params: &lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Value, String&lt;span style="color:#f92672"&gt;&amp;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;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&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="33-로그-읽기-도구-구현"&gt;3.3. 로그 읽기 도구 구현
&lt;/h3&gt;&lt;p&gt;이제 실제로 서버 내부의 파일을 읽어서 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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::fs;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::path::Path;
&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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;LogReaderTool&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;#[async_trait]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; McpHandler &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; LogReaderTool {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&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;read_logs&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:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, params: &lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Value, String&lt;span style="color:#f92672"&gt;&amp;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;// 1. 파라미터 검증 및 추출
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; file_path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; params.get(&lt;span style="color:#e6db74"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .and_then(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;v&lt;span style="color:#f92672"&gt;|&lt;/span&gt; v.as_str())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .ok_or(&lt;span style="color:#e6db74"&gt;&amp;#34;Missing &amp;#39;path&amp;#39; parameter&amp;#34;&lt;/span&gt;.to_string())&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. 보상: 경로 조작 방지 (SandBoxing)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 실제 운영 환경에서는 chroot나 전용 가상 디렉토리를 사용해야 합니다.
&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; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;file_path.ends_with(&lt;span style="color:#e6db74"&gt;&amp;#34;.log&amp;#34;&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; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;Only .log files are allowed&amp;#34;&lt;/span&gt;.into());
&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;// 3. 파일 시스템 접근 (동기식 I/O를 비동기 컨텍스트로 래핑)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; content &lt;span style="color:#f92672"&gt;=&lt;/span&gt; fs::read_to_string(file_path)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .map_err(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;e&lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to read file: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e))&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 4. 결과 포맷팅
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(serde_json::&lt;span style="color:#a6e22e"&gt;json!&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;status&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;success&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;content&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;content&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;lines&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;.lines().count()
&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;h3 id="34-메인-루프-및-json-rpc-서버"&gt;3.4. 메인 루프 및 JSON-RPC 서버
&lt;/h3&gt;&lt;p&gt;LLM과의 통신을 담당하는 메인 루프입니다. 표준 입력에서 JSON-RPC 요청을 읽고, 처리 결과를 표준 출력으로 내보냅니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::io::{AsyncBufReadExt, BufReader};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::io::{AsyncWriteExt, BufWriter};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::sync::Arc;
&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;#[tokio::main]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;let&lt;/span&gt; stdin &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tokio::io::stdin();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; stdout &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tokio::io::stdout();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; reader &lt;span style="color:#f92672"&gt;=&lt;/span&gt; BufReader::new(stdin).lines();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; writer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; BufWriter::new(stdout);
&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;let&lt;/span&gt; tool &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Arc::new(LogReaderTool);
&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;// LLM이나 호스트로부터 요청이 들어올 때마다 처리
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Ok(Some(line)) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; reader.next_line().&lt;span style="color:#66d9ef"&gt;await&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; line.trim().is_empty() { &lt;span style="color:#66d9ef"&gt;continue&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;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Ok(json_req) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; serde_json::from_str::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Value&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;line) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; method &lt;span style="color:#f92672"&gt;=&lt;/span&gt; json_req.get(&lt;span style="color:#e6db74"&gt;&amp;#34;method&amp;#34;&lt;/span&gt;).and_then(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;m&lt;span style="color:#f92672"&gt;|&lt;/span&gt; m.as_str()).unwrap_or(&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 style="color:#66d9ef"&gt;let&lt;/span&gt; id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; json_req.get(&lt;span style="color:#e6db74"&gt;&amp;#34;id&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;if&lt;/span&gt; method &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;tools/call&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; method &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;tools/invoke&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; params &lt;span style="color:#f92672"&gt;=&lt;/span&gt; json_req.get(&lt;span style="color:#e6db74"&gt;&amp;#34;params&amp;#34;&lt;/span&gt;).cloned().unwrap_or(Value::Null);
&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;let&lt;/span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tool.handle(params).&lt;span style="color:#66d9ef"&gt;await&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;let&lt;/span&gt; response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; result {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(data) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; serde_json::&lt;span style="color:#a6e22e"&gt;json!&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;jsonrpc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&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;id&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;id&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;result&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;data&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; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; serde_json::&lt;span style="color:#a6e22e"&gt;json!&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;jsonrpc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&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;id&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;id&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;error&amp;#34;&lt;/span&gt;: { &lt;span style="color:#e6db74"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;32000&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;e&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; writer.write_all(response.to_string().as_bytes()).&lt;span style="color:#66d9ef"&gt;await&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; writer.write_all(&lt;span style="color:#e6db74"&gt;b&lt;/span&gt;&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:#66d9ef"&gt;await&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; writer.flush().&lt;span style="color:#66d9ef"&gt;await&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&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; Ok(())
&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;h2 id="4-최적화-및-배포-고려사항"&gt;4. 최적화 및 배포 고려사항
&lt;/h2&gt;&lt;p&gt;위 코드는 기본적인 골격이지만, &lt;code&gt;ZeroClaw&lt;/code&gt;와 같은 프로덕션 환경에서는 다음 사항을 고려해야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;에러 타입 구체화:&lt;/strong&gt; &lt;code&gt;String&lt;/code&gt; 대신 구체적인 &lt;code&gt;thiserror&lt;/code&gt;나 &lt;code&gt;anyhow&lt;/code&gt; 기반의 에러 타입을 사용하여 디버깅을 용이하게 하세요.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보안 샌드박싱:&lt;/strong&gt; &lt;code&gt;std::fs&lt;/code&gt; 대신 가상 파일 시스템(VFS) 라이브러리를 사용하여, 에이전트가 시스템 전체에 접근하는 것을 막아야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graceful Shutdown:&lt;/strong&gt; &lt;code&gt;tokio&lt;/code&gt;의 &lt;code&gt;signal&lt;/code&gt; 모듈을 활용하여 인터럽트 신호(SIGTERM)를 처리하고, 진행 중인 파일 작업을 안전하게 종료해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="5-결론"&gt;5. 결론
&lt;/h2&gt;&lt;p&gt;Rust는 타입 안전성과 메모리 관리 능력 덕분에 신뢰할 수 있는 MCP 서버를 구축하는 데 최적의 언어입니다. 위 코드를 바탕으로 &lt;code&gt;blog-api-server&lt;/code&gt;나 모니터링 시스템과 연동한다면, AI 모델이 직접 시스템 로그를 분석하고 패치를 제안하는 완전한 자동화 사이클을 구축할 수 있습니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이 MCP 서버를 &lt;code&gt;ZeroClaw&lt;/code&gt; 런타임 위에서 어떻게 에이전트화하고, 다른 서비스와 통신하는지 다루겠습니다.&lt;/p&gt;</description></item><item><title>pg_durable 분석: Rust로 구현하는 내구성 있는 함수 실행</title><link>https://blog.agentthread.dev/ko/post/pg_durable-%EB%B6%84%EC%84%9D-rust%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EB%82%B4%EA%B5%AC%EC%84%B1-%EC%9E%88%EB%8A%94-%ED%95%A8%EC%88%98-%EC%8B%A4%ED%96%89/</link><pubDate>Sat, 06 Jun 2026 09:00:49 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/pg_durable-%EB%B6%84%EC%84%9D-rust%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EB%82%B4%EA%B5%AC%EC%84%B1-%EC%9E%88%EB%8A%94-%ED%95%A8%EC%88%98-%EC%8B%A4%ED%96%89/</guid><description>&lt;p&gt;최근 Microsoft가 오픈 소스로 공개한 &lt;code&gt;pg_durable&lt;/code&gt;은 데이터베이스 내에서 직접 &amp;lsquo;내구성 있는(Durable)&amp;rsquo; 함수 실행을 가능하게 하는 실험적인 프로젝트입니다. 전통적인 마이크로서비스 아키텍처에서는 비즈니스 로직이 애플리케이션 계층에 위치하고, 데이터베이스는 단순히 상태를 저장하는 역할에 그치는 경우가 많습니다. 하지만 &lt;code&gt;pg_durable&lt;/code&gt;은 로직과 데이터를 물리적으로 가까운 곳에 통합하여, 외부 시스템의 장애나 네트워크 비용을 최소화하는 접근 방식을 제시합니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 &lt;code&gt;pg_durable&lt;/code&gt;의 핵심 메커니즘을 분석하고, Rust와 PostgreSQL을 활용해 유사한 패턴을 구현하는 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;h2 id="pg_durable의-핵심-메커니즘"&gt;pg_durable의 핵심 메커니즘
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;pg_durable&lt;/code&gt;의 가장 큰 특징은 함수의 상태(State)와 실행 흐름(Execution Flow) 자체를 데이터베이스 레코드로 저장한다는 점입니다. 일반적인 애플리케이션에서 HTTP 요청이 실패하면 메모리에 있던 컨텍스트가 사라지지만, &lt;code&gt;pg_durable&lt;/code&gt;은 실행할 함수의 입력(Input)과 현재 단계(Step)를 테이블에 기록합니다.&lt;/p&gt;
&lt;p&gt;이를 통해 다음과 같은 이점을 얻을 수 있습니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;자동 복구 (Auto-Recovery)&lt;/strong&gt;: 서버가 크래시되어도 데이터베이스에는 &amp;lsquo;실행해야 할 작업&amp;rsquo;이 남아있으므로, 서버 재시작 후 남은 작업을 이어서 수행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;정확히 한 번 실행 (Exactly-Once Semantics)&lt;/strong&gt;: 데이터베이스 트랜잭션을 활용해 상태 업데이트와 로직 실행을 원자적으로 처리할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="rust로-데이터베이스-트리거-기반-워커-구현하기"&gt;Rust로 데이터베이스 트리거 기반 워커 구현하기
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;pg_durable&lt;/code&gt;의 내부 구현은 복잡하지만, 이 패턴을 Rust와 PostgreSQL의 &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt; 기능을 사용해 간단하게 재현해 볼 수 있습니다. 이 접근 방식은 데이터베이스를 &amp;lsquo;메시지 브로커&amp;rsquo;처럼 활용하면서도 별도의 인프라 비용을 절약할 수 있습니다.&lt;/p&gt;
&lt;h3 id="1-데이터베이스-스키마-설계"&gt;1. 데이터베이스 스키마 설계
&lt;/h3&gt;&lt;p&gt;먼저 작업(Job)을 저장할 테이블을 생성합니다.&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-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; durable_jobs (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id SERIAL &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status TEXT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;, &lt;span style="color:#75715e"&gt;-- &amp;#39;pending&amp;#39;, &amp;#39;running&amp;#39;, &amp;#39;completed&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload JSONB &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; created_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; NOW()
&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="2-rust-워커-구현-sqlx-사용"&gt;2. Rust 워커 구현 (sqlx 사용)
&lt;/h3&gt;&lt;p&gt;Rust의 비동기 런타임과 &lt;code&gt;sqlx&lt;/code&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-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Cargo.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tokio&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;full&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;sqlx&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.7&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;runtime-tokio&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;json&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;derive&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이제 Rust 코드로 워커를 구현합니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; sqlx::{PgPool, Listener};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::time::{sleep, Duration};
&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;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JobPayload&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_id: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; action: String,
&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;#[tokio::main]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), sqlx::Error&lt;span style="color:#f92672"&gt;&amp;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;let&lt;/span&gt; pool &lt;span style="color:#f92672"&gt;=&lt;/span&gt; PgPool::connect(&lt;span style="color:#e6db74"&gt;&amp;#34;postgresql://user:password@localhost/mydb&amp;#34;&lt;/span&gt;).&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 데이터베이스 변경 사항 감지 리스너 (pg_notify 활용)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; listener &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pool.acquire().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&lt;/span&gt;.listen(&lt;span style="color:#e6db74"&gt;&amp;#34;job_channel&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:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Worker started, waiting for jobs...&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;loop&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;let&lt;/span&gt; notification &lt;span style="color:#f92672"&gt;=&lt;/span&gt; listener.recv().&lt;span style="color:#66d9ef"&gt;await&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:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Received notification: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, notification.payload);
&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; process_jobs(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;pool).&lt;span style="color:#66d9ef"&gt;await&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&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:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process_jobs&lt;/span&gt;(pool: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;PgPool&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), sqlx::Error&lt;span style="color:#f92672"&gt;&amp;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;// 트랜잭션 내에서 &amp;#39;pending&amp;#39; 상태의 작업을 가져와 &amp;#39;running&amp;#39;으로 변경 (Locking)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; tx &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pool.begin().&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; job_opt &lt;span style="color:#f92672"&gt;=&lt;/span&gt; sqlx::query_as::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;_, (&lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;, JobPayload)&lt;span style="color:#f92672"&gt;&amp;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;r&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 style="color:#e6db74"&gt; UPDATE durable_jobs 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SET status = &amp;#39;running&amp;#39; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE id = (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SELECT id FROM durable_jobs 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE status = &amp;#39;pending&amp;#39; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; FOR UPDATE SKIP LOCKED 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; LIMIT 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&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; RETURNING id, payload
&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;#&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; .fetch_optional(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;tx)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Some((id, payload)) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; job_opt {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Processing job &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, id, payload);
&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;// 비즈니스 로직 실행 (예: 외부 API 호출 등)
&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;let&lt;/span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; simulate_heavy_task(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;payload).&lt;span style="color:#66d9ef"&gt;await&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;match&lt;/span&gt; result {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(_) &lt;span style="color:#f92672"&gt;=&amp;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;// 성공 시 상태를 &amp;#39;completed&amp;#39;로 업데이트
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sqlx::query(&lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE durable_jobs SET status = &amp;#39;completed&amp;#39; WHERE id = $1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .execute(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;tx)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&amp;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;// 실패 시 상태를 &amp;#39;failed&amp;#39;로 남겨두거나 재시도 로직 추가
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Job &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; failed: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, id, e);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sqlx::query(&lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE durable_jobs SET status = &amp;#39;failed&amp;#39; WHERE id = $1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .execute(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;tx)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&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&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; tx.commit().&lt;span style="color:#66d9ef"&gt;await&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&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; Ok(())
&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:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;simulate_heavy_task&lt;/span&gt;(payload: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;JobPayload&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;// 예시: 2초 대기
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sleep(Duration::from_secs(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Task executed for user &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, payload.user_id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&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="3-작업-등록-트리거"&gt;3. 작업 등록 트리거
&lt;/h3&gt;&lt;p&gt;실제로는 애플리케이션 레벨에서 &lt;code&gt;INSERT&lt;/code&gt; 쿼리를 날린 후 &lt;code&gt;NOTIFY&lt;/code&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-sql" data-lang="sql"&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;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;OR&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;REPLACE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;FUNCTION&lt;/span&gt; notify_job() &lt;span style="color:#66d9ef"&gt;RETURNS&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TRIGGER&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&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;BEGIN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PERFORM pg_notify(&lt;span style="color:#e6db74"&gt;&amp;#39;job_channel&amp;#39;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;NEW&lt;/span&gt;.id::text);
&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;NEW&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;END&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;$$&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;LANGUAGE&lt;/span&gt; plpgsql;
&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;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TRIGGER&lt;/span&gt; job_notify_trigger
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;AFTER&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INSERT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; durable_jobs
&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; &lt;span style="color:#66d9ef"&gt;EACH&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ROW&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;EXECUTE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;FUNCTION&lt;/span&gt; notify_job();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="아키텍처-적용-시-고려사항"&gt;아키텍처 적용 시 고려사항
&lt;/h2&gt;&lt;p&gt;이 패턴(&lt;code&gt;pg_durable&lt;/code&gt; 스타일)을 ZeroClaw나 MCP 서버와 같은 고성능 시스템에 적용할 때는 다음 사항을 고려해야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;데이터베이스 부하&lt;/strong&gt;: 데이터베이스가 단순한 저장소가 아니라 실행 엔진의 역할을 일부 담당하므로, 워커가 너무 잦은 폴링(Polling)을 하거나 무거운 로직을 수행하면 DB 성능에 저하가 올 수 있습니다. &lt;code&gt;SKIP LOCKED&lt;/code&gt; 구문을 사용해 여러 워커 인스턴스가 안전하게 분산 처리하도록 구성하는 것이 필수입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;확장성(Scale-out)&lt;/strong&gt;: 앞서 보여준 Rust 코드는 다중 프로세스로 실행하더라도 &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; 덕분에 안전하게 작업을 분산 처리할 수 있습니다. Kubernetes 환경이라면 &lt;code&gt;HorizontalPodAutoscaler&lt;/code&gt;(HPA)를 통해 큐가 쌓일 때 워커 파드를 늘리는 식으로 유연한 대응이 가능합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="결론"&gt;결론
&lt;/h2&gt;&lt;p&gt;Microsoft의 &lt;code&gt;pg_durable&lt;/code&gt;은 &amp;lsquo;데이터 중심 아키텍처(Data-Centric Architecture)&amp;lsquo;의 진화된 형태를 보여줍니다. 복잡한 메시지 큐(Kafka, RabbitMQ)를 도입하기 전, 데이터베이스의 트랜잭션 기능을 적극 활용해 간단하고 강력한 내구성을 확보해 보는 것은 어떨까요? 위의 예제 코드는 바로 실무에서 테스트해 볼 수 있는 형태이므로, 여러분의 사이드 프로젝트나 백오피스 시스템에 적용해 보시길 권장합니다.&lt;/p&gt;</description></item><item><title>SQLite 기반 영구적 워크플로우 설계: 'SQLite is all you need' 실전 가이드</title><link>https://blog.agentthread.dev/ko/post/sqlite-%EA%B8%B0%EB%B0%98-%EC%98%81%EA%B5%AC%EC%A0%81-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%84%A4%EA%B3%84-sqlite-is-all-you-need-%EC%8B%A4%EC%A0%84-%EA%B0%80%EC%9D%B4%EB%93%9C/</link><pubDate>Sat, 30 May 2026 09:00:30 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/sqlite-%EA%B8%B0%EB%B0%98-%EC%98%81%EA%B5%AC%EC%A0%81-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%84%A4%EA%B3%84-sqlite-is-all-you-need-%EC%8B%A4%EC%A0%84-%EA%B0%80%EC%9D%B4%EB%93%9C/</guid><description>&lt;h1 id="sqlite로-워크플로우를-지속-가능하게-만드는-방법"&gt;SQLite로 워크플로우를 지속 가능하게 만드는 방법
&lt;/h1&gt;&lt;p&gt;최근 Hacker News에서 &amp;ldquo;SQLite is all you need for durable workflows&amp;quot;라는 글이 큰 화제가 되었습니다. 분산 시스템을 설계할 때 Kafka, Redis, 복잡한 클러스터링을 떠올리기 쉽지만, 정작 우리가 해결하려는 핵심 문제인 &amp;lsquo;상태 유지(State Management)&amp;lsquo;와 &amp;lsquo;재시작 내성(Resiliency)&amp;lsquo;은 단일 파일 데이터베이스인 SQLite만으로도 충분히 해결할 수 있다는 주장입니다.&lt;/p&gt;
&lt;p&gt;저희 팀의 &amp;lsquo;ZeroClaw&amp;rsquo;나 &amp;lsquo;Discord Decision MCP&amp;rsquo;와 같은 에이전트 시스템을 개발하면서, 복잡한 메시지 큐를 도입하기 전에 SQLite의 가벼우면서도 강력한 트랜잭션 기능을 활용해 워크플로우 엔진을 직접 구현해 보았습니다. 이 글에서는 그 핵심 아키텍처와 실제 코드 예제를 공유합니다.&lt;/p&gt;
&lt;h2 id="1-영구적-워크플로우durable-workflow란"&gt;1. 영구적 워크플로우(Durable Workflow)란?
&lt;/h2&gt;&lt;p&gt;워크플로우가 &amp;lsquo;영구적&amp;rsquo;이다 한다는 것은, 프로그램이 죽거나 서버가 재시작되더라도 중단된 지점부터 다시 시작할 수 있음을 의미합니다. 이를 위해 우리는 다음 두 가지를 보장해야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;상태 저장(Snapshotting):&lt;/strong&gt; 현재 진행 상황을 지속적으로 저장해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;정확히 한 번 실행(Exactly-Once Semantics):&lt;/strong&gt; 재시작 시 중복 실행을 방지해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-아키텍처-설계-sqlite를-큐와-상태-저장소로-사용하기"&gt;2. 아키텍처 설계: SQLite를 큐와 상태 저장소로 사용하기
&lt;/h2&gt;&lt;p&gt;SQLite는 &lt;code&gt;WAL(Write-Ahead Logging)&lt;/code&gt; 모드와 고급 잠금 메커니즘 덕분에 동시성 환경에서도 놀라운 성능을 냅니다. 워크플로우 엔진은 다음과 같은 테이블 구조를 기반으로 동작합니다.&lt;/p&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-sql" data-lang="sql"&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;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; workflows (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id TEXT &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status TEXT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;, &lt;span style="color:#75715e"&gt;-- pending, running, completed, failed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; current_step INTEGER &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload TEXT, &lt;span style="color:#75715e"&gt;-- JSON 형태의 입력 데이터
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; updated_at INTEGER &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&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;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; jobs (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id INTEGER &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt; AUTOINCREMENT,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; workflow_id TEXT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target_step INTEGER &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; run_at INTEGER, &lt;span style="color:#75715e"&gt;-- 예약 실행 시간 (NULL 즉시 실행)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FOREIGN&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;(workflow_id) &lt;span style="color:#66d9ef"&gt;REFERENCES&lt;/span&gt; workflows(id)
&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:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INDEX&lt;/span&gt; idx_jobs_run_at &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; jobs(run_at);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="rust-구현-예제"&gt;Rust 구현 예제
&lt;/h3&gt;&lt;p&gt;Rust의 &lt;code&gt;rusqlite&lt;/code&gt; 라이브러리를 사용하여 간단한 워크플로우 스케줄러를 작성해 보겠습니다. 이 코드는 &amp;lsquo;ZeroClaw&amp;rsquo; 런타임의 가벼운 작업 스케줄링 로직에서 영감을 받았습니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; rusqlite::{Connection, Result, params};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::time::{Duration, SystemTime};
&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;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;&amp;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;// 1. 메모리 또는 파일 DB 연결 (WAL 모드 활성화 권장)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; conn &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Connection::open(&lt;span style="color:#e6db74"&gt;&amp;#34;workflow.db&amp;#34;&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; conn.execute_batch(&lt;span style="color:#e6db74"&gt;&amp;#34;PRAGMA journal_mode=WAL;&amp;#34;&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. 워크플로우 생성 (초기 상태)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; workflow_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;order-12345&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; insert_workflow(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; conn, workflow_id, &lt;span style="color:#e6db74"&gt;&amp;#34;{&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;item&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;keyboard&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&amp;#34;&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. 작업 예약 (결제 단계)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; enqueue_job(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; conn, workflow_id, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, None)&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 4. 워커 루프 (실무에서는 데몬화)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;loop&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; fetch_next_job(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; conn)&lt;span style="color:#f92672"&gt;?&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Some(job) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Processing job &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; for workflow &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, job.target_step, job.workflow_id);
&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;// 비즈니스 로직 실행 (예: LLM 호출, 외부 API 요청)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// simulate_work();
&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; complete_step(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; conn, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;job.workflow_id, job.target_step)&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; None &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;No jobs, sleeping...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; std::thread::sleep(Duration::from_secs(&lt;span style="color:#ae81ff"&gt;5&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;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;// 작업을 큐에 넣고, 워크플로우 상태를 &amp;#39;pending&amp;#39;으로 업데이트하는 트랜잭션
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;enqueue_job&lt;/span&gt;(conn: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;mut&lt;/span&gt; Connection, workflow_id: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;, step: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;, delay: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;SystemTime&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;&amp;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;let&lt;/span&gt; tx &lt;span style="color:#f92672"&gt;=&lt;/span&gt; conn.transaction()&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tx.execute(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;INSERT INTO jobs (workflow_id, target_step, run_at) VALUES (?1, ?2, ?3)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;params!&lt;/span&gt;[workflow_id, step, delay.and_then(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;t&lt;span style="color:#f92672"&gt;|&lt;/span&gt; Some(t.duration_since(SystemTime::&lt;span style="color:#66d9ef"&gt;UNIX_EPOCH&lt;/span&gt;).unwrap().as_secs()))]
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tx.execute(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE workflows SET status = &amp;#39;pending&amp;#39;, updated_at = ?1 WHERE id = ?2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;params!&lt;/span&gt;[SystemTime::now().duration_since(SystemTime::&lt;span style="color:#66d9ef"&gt;UNIX_EPOCH&lt;/span&gt;).unwrap().as_secs(), workflow_id]
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tx.commit()
&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;// 다음 처리할 작업을 가져오는 함수 (Locking Read)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch_next_job&lt;/span&gt;(conn: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;mut&lt;/span&gt; Connection) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Job&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;// UPDATE 문을 통해 행을 잠그고 반환하는 기법 (UPDATE RETURNING)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// SQLite 3.35.0 이상 지원
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; stmt &lt;span style="color:#f92672"&gt;=&lt;/span&gt; conn.prepare(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE jobs 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SET workflow_id = workflow_id || &amp;#39;:processing&amp;#39; -- 임시 상태 표시 (실제로는 status 컬럼 추천)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE rowid IN (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SELECT rowid FROM jobs 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE run_at IS NULL OR run_at &amp;lt;= strftime(&amp;#39;%s&amp;#39;, &amp;#39;now&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; LIMIT 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&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; RETURNING id, workflow_id, target_step;&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;?&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;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; rows &lt;span style="color:#f92672"&gt;=&lt;/span&gt; stmt.query(&lt;span style="color:#a6e22e"&gt;params!&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:#66d9ef"&gt;match&lt;/span&gt; rows.next()&lt;span style="color:#f92672"&gt;?&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Some(row) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; Ok(Some(Job {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.get(&lt;span style="color:#ae81ff"&gt;0&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; workflow_id: &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.get(&lt;span style="color:#ae81ff"&gt;1&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; target_step: &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.get(&lt;span style="color:#ae81ff"&gt;2&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; None &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; Ok(None),
&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 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;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;complete_step&lt;/span&gt;(conn: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;mut&lt;/span&gt; Connection, workflow_id: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;, step: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;&amp;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;let&lt;/span&gt; tx &lt;span style="color:#f92672"&gt;=&lt;/span&gt; conn.transaction()&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&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; tx.execute(&lt;span style="color:#e6db74"&gt;&amp;#34;DELETE FROM jobs WHERE workflow_id = ?1 AND target_step = ?2&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;params!&lt;/span&gt;[workflow_id, step])&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&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; tx.execute(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE workflows SET current_step = ?1, updated_at = ?2 WHERE id = ?3&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;params!&lt;/span&gt;[step &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, SystemTime::now().duration_since(SystemTime::&lt;span style="color:#66d9ef"&gt;UNIX_EPOCH&lt;/span&gt;).unwrap().as_secs(), workflow_id]
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tx.commit()
&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:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Job&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#66d9ef"&gt;i64&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; workflow_id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target_step: &lt;span style="color:#66d9ef"&gt;i64&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;h2 id="3-왜-이-구조가-강력한가"&gt;3. 왜 이 구조가 강력한가?
&lt;/h2&gt;&lt;p&gt;이 접근 방식의 가장 큰 장점은 &lt;strong&gt;관리 부하(Ops Overhead)가 거의 없다는 점&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ZeroClaw와 같은 에이전트 시스템&lt;/strong&gt;에서는 각 에이전트가 자신의 SQLite 파일을 가지고 동작하므로, 중앙 집중식 DB의 병목 현상을 피할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discord MCP&lt;/strong&gt; 같은 봇을 만들 때, 복잡한 Redis 컨테이너를 띄우는 대신 단순히 &lt;code&gt;discord.db&lt;/code&gt; 파일만 백업해두면 모든 대화 기록과 처리 중인 작업을 완벽하게 보호할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4-결론-필요한-만큼만"&gt;4. 결론: &amp;ldquo;필요한 만큼만&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;&amp;ldquo;SQLite is all you need&amp;quot;라는 제목은 SQLite가 모든 것을 대체할 수 있다는 뜻이 아니라, &lt;strong&gt;대부분의 문제는 SQLite면 충분하다&lt;/strong&gt;는 뜻입니다. 복잡한 분산 시스템을 도입하기 전에, 가장 단순하고 안정적인 파일 기반 데이터베이스로 워크플로우의 신뢰성을 확보해 보는 것은 어떨까요?&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이 SQLite 워크플로우 엔진을 Kubernetes 환경에서 안정적으로 운영하기 위한 Persisntent Volume 전략에 대해 다루겠습니다.&lt;/p&gt;</description></item><item><title>Postgres 기반 Durable Workflow 구현: DB를 워크플로우 엔진처럼 쓰기</title><link>https://blog.agentthread.dev/ko/post/postgres-%EA%B8%B0%EB%B0%98-durable-workflow-%EA%B5%AC%ED%98%84-db%EB%A5%BC-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%97%94%EC%A7%84%EC%B2%98%EB%9F%BC-%EC%93%B0%EA%B8%B0/</link><pubDate>Fri, 29 May 2026 09:00:36 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/postgres-%EA%B8%B0%EB%B0%98-durable-workflow-%EA%B5%AC%ED%98%84-db%EB%A5%BC-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%97%94%EC%A7%84%EC%B2%98%EB%9F%BC-%EC%93%B0%EA%B8%B0/</guid><description>&lt;p&gt;최근 빠르고 가벼운 마이크로 서비스 아키텍처를 구축하면서, 외부 의존성을 최소화하는 것이 중요한 과제로 떠올랐습니다. 특히 ZeroClaw 런타임 개발 과정에서 복잡한 상태 머신 관리와 에이전트 간의 통신 신뢰성을 보장하기 위해 Kafka나 Redis 같은 별도의 메시지 큐를 도입하려 했으나, 운영 복잡도가 우려되었습니다.&lt;/p&gt;
&lt;p&gt;이때 눈에 들어온 것이 최근 Hacker News 등에서 화제가 된 &lt;strong&gt;&amp;lsquo;Postgres 기반 Durable Workflow&amp;rsquo;&lt;/strong&gt; 패턴입니다. 단순히 데이터를 저장하는 것을 넘어, 데이터베이스 자체를 워크플로우 엔진처럼 활용하여 상태 유지와 재시작(Retry) 로직을 처리하는 방식입니다. 이번 포스트에서는 이 패턴의 핵심 개념과 ZeroClaw 프로젝트에 적용한 실용적인 구현 방법을 공유합니다.&lt;/p&gt;
&lt;h3 id="왜-postgres인가"&gt;왜 Postgres인가?
&lt;/h3&gt;&lt;p&gt;기존에는 Temporal.io나 Cadence 같은 전문 워크플로우 엔진을 사용하거나, Kafka를 통해 이벤트 소싱을 구현했습니다. 하지만 소규모 팀이나 개인 프로젝트에서는 이러한 인프라를 유지하는 것이 큰 부담이 됩니다.&lt;/p&gt;
&lt;p&gt;PostgreSQL은 이미 대부분의 서비스가 기본 데이터베이스로 채택하고 있습니다. ACID 트랜잭션을 통해 강력한 일관성을 보장하며, &lt;code&gt;SKIP LOCKED&lt;/code&gt; 기능을 통해 분산 락(Claim-based lock)을 구현할 수 있습니다. 즉, &amp;lsquo;이미 우리가 가진 신뢰할 수 있는 저장소&amp;rsquo;를 큐(Queue)로, 스케줄러로, 그리고 상태 저장소로 활용하여 인프라 스택을 단순화할 수 있습니다.&lt;/p&gt;
&lt;h3 id="핵심-아키텍처-테이블을-큐처럼-사용하기"&gt;핵심 아키텍처: 테이블을 큐처럼 사용하기
&lt;/h3&gt;&lt;p&gt;이 패턴의 핵심은 워크플로우의 상태(State)와 실행 대상(Task)을 테이블에 정의하는 것입니다. ZeroClaw의 에이전트 작업 큐를 예로 들어보겠습니다.&lt;/p&gt;
&lt;h4 id="1-상태-테이블-정의-sql"&gt;1. 상태 테이블 정의 (SQL)
&lt;/h4&gt;&lt;p&gt;우리는 작업의 현재 상태와 다음 실행 시점을 저장하기 위해 &lt;code&gt;workflow_jobs&lt;/code&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-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; workflow_jobs (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id SERIAL &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; job_type TEXT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;, &lt;span style="color:#75715e"&gt;-- 예: &amp;#39;agent_task&amp;#39;, &amp;#39;discord_notify&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload JSONB &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&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 TEXT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;-- pending, running, succeeded, failed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; claimed_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; run_after &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CURRENT_TIMESTAMP&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; last_error TEXT,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; attempt_count INT &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; max_attempts INT &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; created_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CURRENT_TIMESTAMP&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;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INDEX&lt;/span&gt; idx_jobs_claim &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; workflow_jobs (status, run_after) &lt;span style="color:#66d9ef"&gt;WHERE&lt;/span&gt; status &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;여기서 &lt;code&gt;status&lt;/code&gt;와 &lt;code&gt;run_after&lt;/code&gt; 컬럼이 핵심입니다. 이 두 컬럼을 통해 &amp;lsquo;지금 실행해야 할 작업&amp;rsquo;을 즉시 찾아낼 수 있습니다.&lt;/p&gt;
&lt;h4 id="2-작업-예약-및-실행-rust-예제"&gt;2. 작업 예약 및 실행 (Rust 예제)
&lt;/h4&gt;&lt;p&gt;이제 워커(Worker)는 트랜잭션 내에서 작업을 찾고(&lt;code&gt;SELECT&lt;/code&gt;), 소유권을 주장(&lt;code&gt;UPDATE&lt;/code&gt;)한 뒤 실행해야 합니다. 이 과정에서 경쟁 조건(Race Condition)을 방지하기 위해 &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; 구문을 사용합니다.&lt;/p&gt;
&lt;p&gt;다음은 Rust와 &lt;code&gt;sqlx&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; sqlx::{PgPool, Postgres};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; sqlx::types::chrono::{Utc, DateTime};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde_json::Value;
&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;#[derive(Debug)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Job&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; id: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; job_type: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; payload: &lt;span style="color:#a6e22e"&gt;Value&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:#e6db74"&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;/// 트랜잭션 내에서 안전하게 작업을 할당받습니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;claim_job&lt;/span&gt;(pool: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;PgPool&lt;/span&gt;, worker_id: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Job&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, sqlx::Error&lt;span style="color:#f92672"&gt;&amp;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;let&lt;/span&gt; now &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Utc::now();
&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;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; tx &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pool.begin().&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. 실행 가능한 작업을 찾고 락을 겁니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// SKIP LOCKED: 다른 워커가 이미 락을 건 행은 무시하고 넘어갑니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; query &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;r&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 style="color:#e6db74"&gt; SELECT id, job_type, payload 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; FROM workflow_jobs 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE status = &amp;#39;pending&amp;#39; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; AND run_after &amp;lt;= $1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ORDER BY created_at ASC
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; FOR UPDATE SKIP LOCKED
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; LIMIT 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; FOR UPDATE OF workflow_jobs
&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;#&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;let&lt;/span&gt; job: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Job&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; sqlx::query_as::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;_, Job&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(query)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(now)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .fetch_optional(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;tx)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; job &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; job {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Some(j) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; j,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; None &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tx.rollback().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// 할당받을 작업 없음 (Rollback은 불필요하지만 명시적 표현)
&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; Ok(None);
&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 style="color:#75715e"&gt;// 2. 작업 상태를 &amp;#39;running&amp;#39;으로 변경
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sqlx::query(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE workflow_jobs SET status = &amp;#39;running&amp;#39;, claimed_at = $1, attempt_count = attempt_count + 1 WHERE id = $2&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; .bind(Utc::now())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(job.id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .execute(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;tx)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tx.commit().&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(Some(job))
&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:#e6db74"&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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;complete_job&lt;/span&gt;(pool: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;PgPool&lt;/span&gt;, job_id: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;, success: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;, error_msg: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), sqlx::Error&lt;span style="color:#f92672"&gt;&amp;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;let&lt;/span&gt; status &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; success { &lt;span style="color:#e6db74"&gt;&amp;#34;succeeded&amp;#34;&lt;/span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; { &lt;span style="color:#e6db74"&gt;&amp;#34;failed&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;// 실패 시 지수 백오프(Exponential Backoff)를 고려하여 run_after를 계산할 수 있습니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; next_run &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; success {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; None
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 예: 10초 후 재시도 시도 (실제 구현에서는 attempt_count에 따른 지수 증가 로직 필요)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Some(Utc::now() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; chrono::Duration::seconds(&lt;span style="color:#ae81ff"&gt;10&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:#66d9ef"&gt;if&lt;/span&gt; success {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sqlx::query(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE workflow_jobs SET status = $1, run_after = NULL WHERE id = $2&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; .bind(status)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(job_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .execute(pool)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&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:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sqlx::query(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE workflow_jobs SET status = $1, last_error = $2, run_after = $3 WHERE id = $4&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; .bind(status)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(error_msg)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(next_run)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .bind(job_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .execute(pool)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&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&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; Ok(())
&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;h4 id="3-워커-루프-구성"&gt;3. 워커 루프 구성
&lt;/h4&gt;&lt;p&gt;이제 위 함수를 사용해 무한 루프(또는 Tokio의 &lt;code&gt;tokio::time::interval&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;worker_loop&lt;/span&gt;(pool: &lt;span style="color:#a6e22e"&gt;PgPool&lt;/span&gt;, worker_id: String) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;loop&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; claim_job(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;pool, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;worker_id).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(Some(job)) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Job acquired: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, worker_id, job.job_type);
&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;// 실제 비즈니스 로직 실행 (예: LLM 추론, API 호출)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; execute_logic(job.payload.clone()).&lt;span style="color:#66d9ef"&gt;await&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;let&lt;/span&gt; _ &lt;span style="color:#f92672"&gt;=&lt;/span&gt; complete_job(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;pool, job.id, result.is_ok(), result.err().map(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;e&lt;span style="color:#f92672"&gt;|&lt;/span&gt; e.to_string())).&lt;span style="color:#66d9ef"&gt;await&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; Ok(None) &lt;span style="color:#f92672"&gt;=&amp;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;// 처리할 작업이 없으면 잠시 대기 (CPU 점유율 방지)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tokio::time::sleep(tokio::time::Duration::from_millis(&lt;span style="color:#ae81ff"&gt;500&lt;/span&gt;)).&lt;span style="color:#66d9ef"&gt;await&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; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Worker error: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, worker_id, e);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tokio::time::sleep(tokio::time::Duration::from_secs(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)).&lt;span style="color:#66d9ef"&gt;await&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;h3 id="장단점-및-활용-팁"&gt;장단점 및 활용 팁
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;단순성:&lt;/strong&gt; 복잡한 분산 시스템 이론 없이 RDBMS의 트랜잭션만으로 신뢰성을 확보합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;운영 효율성:&lt;/strong&gt; DB만 백업하면 워크플로우 상태도 함께 복구됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;강력한 일관성:&lt;/strong&gt; 메시지 유실(In-flight loss)에 대한 걱정이 없습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;단점:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;처리량 처리량:&lt;/strong&gt; Postgres는 연결당 처리량에 한계가 있으므로, 초당 수만 건 이상의 처리량이 필요하면 전문 MQ가 낫습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DB 부하:&lt;/strong&gt; 워커가 순환 쿼리(Polling)를 지속적으로 수행하므로, &lt;code&gt;pgbouncer&lt;/code&gt; 등을 통해 연결 풀을 최적화해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="결론"&gt;결론
&lt;/h3&gt;&lt;p&gt;ZeroClaw의 멀티 에이전트 환경에서는 개별 에이전트의 메시지 처리량이 Kafka를 도입할 만큼 높지 않지만, 상태 관리의 신뢰성은 매우 중요했습니다. Postgres 기반 아키텍처를 통해 별도의 인프라 도입 없이 &amp;lsquo;안정적인 상태 관리&amp;rsquo;와 &amp;lsquo;확장 가능한 작업 큐&amp;rsquo;를 모두 달성할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;만약 당신의 프로젝트가 &amp;lsquo;단순함&amp;rsquo;과 &amp;lsquo;신뢰성&amp;rsquo; 사이에서 고민하고 있다면, 굳이 무거운 솔루션을 도입하기 전에 가까운 곳에 있는 Postgres를 워크플로우 엔진으로 활용해 보는 것은 어떨까요?&lt;/p&gt;
&lt;h3 id="참고자료"&gt;참고자료
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.hanselminutes.com/890/building-durable-workflows-on-postgres-with-pavel-duchovny" target="_blank" rel="noopener"
 &gt;Building durable workflows on Postgres&lt;/a&gt; (Hacker News 토론)&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE" target="_blank" rel="noopener"
 &gt;SKIP LOCKED와 분산 락 구현&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Rust로 구축하는 ZeroClaw: LLM 기반 멀티 에이전트 런타임 아키텍처</title><link>https://blog.agentthread.dev/ko/post/rust%EB%A1%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EB%8A%94-zeroclaw-llm-%EA%B8%B0%EB%B0%98-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98/</link><pubDate>Mon, 25 May 2026 09:00:53 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/rust%EB%A1%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EB%8A%94-zeroclaw-llm-%EA%B8%B0%EB%B0%98-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98/</guid><description>&lt;h1 id="rust로-구축하는-zeroclaw-llm-기반-멀티-에이전트-런타임-아키텍처"&gt;Rust로 구축하는 ZeroClaw: LLM 기반 멀티 에이전트 런타임 아키텍처
&lt;/h1&gt;&lt;p&gt;최근 LLM(Large Language Model)을 활용한 자동화와 에이전트 시스템에 대한 관심이 뜨겁습니다. 하지만 단일 에이전트로 복잡한 작업을 처리하는 데에는 한계가 있으며, 이를 해결하기 위해 **멀티 에이전트 시스템(Multi-Agent System)**이 주목받고 있습니다. 이번 포스트에서는 고성능과 안정성을 위해 Rust로 작성된 에이전트 런타임인 &lt;strong&gt;ZeroClaw&lt;/strong&gt;의 아키텍처를 소개하고, 실제로 에이전트 간 통신을 어떻게 구조화하는지 살펴보겠습니다.&lt;/p&gt;
&lt;h2 id="1-왜-rust인가-performance--safety"&gt;1. 왜 Rust인가? (Performance &amp;amp; Safety)
&lt;/h2&gt;&lt;p&gt;LLM 애플리케이션은 대부분 Python으로 작성되곤 합니다. 하지만 여러 에이전트가 동시에 실행되고 각자 독립된 메모리 공간이나 파일 시스템을 제어해야 하는 &amp;lsquo;런타임&amp;rsquo; 환경에서는 Rust의 강력한 병렬 처리 능력과 메모리 안전성이 큰 무기가 됩니다.&lt;/p&gt;
&lt;p&gt;특히, 최근 Hacker News에서 논의된 것처럼 &lt;strong&gt;&amp;ldquo;LLM 에이전트의 백엔드 코드 생성 취약점(Constraint Decay)&amp;rdquo;&lt;/strong&gt; 문제가 대두되고 있습니다. LLM이 생성한 코드가 의도치 않게 시스템 제약을 무너뜨리는 상황에서, Rust의 타입 시스템과 소유권(Ownership) 모델은 런타임 차원에서 안전망을 제공할 수 있습니다.&lt;/p&gt;
&lt;h2 id="2-zeroclaw의-핵심-아키텍처"&gt;2. ZeroClaw의 핵심 아키텍처
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 단순한 LLM 래퍼가 아니라, 에이전트의 생명주기를 관리하고 메시지를 중계하는 &lt;strong&gt;런타임 엔진&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;h3 id="21-파일-기반-상태-관리-file-based-state-management"&gt;2.1. 파일 기반 상태 관리 (File-Based State Management)
&lt;/h3&gt;&lt;p&gt;복잡한 데이터베이스 없이 에이전트의 상태와 컨텍스트를 파일 시스템에 기반하여 관리하는 아키텍처를 채택했습니다. 이는 휴대성(Portability)과 디버깅 용이성을 높여줍니다.&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-rust" data-lang="rust"&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:#75715e"&gt;#[derive(Serialize, Deserialize, Debug)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentState&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; role: &lt;span style="color:#a6e22e"&gt;AgentRole&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; status: &lt;span style="color:#a6e22e"&gt;ExecutionStatus&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; last_heartbeat: &lt;span style="color:#66d9ef"&gt;u64&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:#66d9ef"&gt;impl&lt;/span&gt; AgentState {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;save_to_file&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, path: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;::Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;&amp;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;let&lt;/span&gt; json &lt;span style="color:#f92672"&gt;=&lt;/span&gt; serde_json::to_string_pretty(self)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fs::write(path, json)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&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;p&gt;이 접근 방식은 &lt;code&gt;Multi-Agent: 파일 기반 아키텍처 설계&lt;/code&gt; 논의에서 언급된 바와 같이, 각 에이전트가 자신의 상태를 투명하게 기록하게 하여 시스템 전체의 예측 가능성을 높입니다.&lt;/p&gt;
&lt;h3 id="22-이벤트-기반-통신-event-driven-communication"&gt;2.2. 이벤트 기반 통신 (Event-Driven Communication)
&lt;/h3&gt;&lt;p&gt;ZeroClaw의 에이전트들은 서로 직접 호출하지 않고, 중앙의 **이벤트 버스(Event Bus)**나 &lt;strong&gt;Pub/Sub&lt;/strong&gt; 메커니즘을 통해 통신합니다. 이는 결합도(Coupling)를 낮추고 확장성을 확보합니다.&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-rust" data-lang="rust"&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:#75715e"&gt;#[derive(Debug, Clone)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskRequest { task_id: String, payload: String },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskResponse { task_id: String, result: String },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StatusUpdate { agent_id: String, status: String },
&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;// 간단한 채널 기반 메시지 라우터 (tokio::sync::mpsc 사용)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MessageRouter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// sender: HashMap&amp;lt;AgentId, Sender&amp;lt;AgentMessage&amp;gt;&amp;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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이러한 구조는 &lt;code&gt;Claude Code 팀 에이전트 통신 아키텍처&lt;/code&gt;나 &lt;code&gt;멀티 에이전트 통신 프로토콜 설계&lt;/code&gt;에서 고민되었던 &amp;lsquo;메시지 큐의 신뢰성&amp;rsquo; 이슈를 Rust의 강력한 비동기 런타임(&lt;code&gt;tokio&lt;/code&gt;)으로 해결하는 기반이 됩니다.&lt;/p&gt;
&lt;h2 id="3-mcpmodel-context-protocol와의-연동"&gt;3. MCP(Model Context Protocol)와의 연동
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 MCP 서버와 클라이언트 역할을 수행하여 외부 도구(예: 블로그 API, Discord Gateway)와 연동됩니다. 최근 &lt;code&gt;blog-api-server&lt;/code&gt;에 추가된 언어 파라미터나 로깅 개선 사항들은 ZeroClaw 에이전트가 외부 시스템과 상호작용할 때 컨텍스트를 잃지 않도록 돕습니다.&lt;/p&gt;
&lt;p&gt;에이전트가 MCP 도구를 호출하는 과정을 안전하게 래핑(Wrapping)하는 것은 중요합니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// MCP 도구 호출을 위한 안전한 래퍼
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;invoke_mcp_tool&lt;/span&gt;(tool_name: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;, params: &lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt;::Value) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, AgentError&lt;span style="color:#f92672"&gt;&amp;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;// 1. 파라미터 검증 (Validation)
&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; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;validate_params(tool_name, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;params) {
&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; Err(AgentError::InvalidInput);
&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;// 2. 실제 호출 (HTTP or IPC)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; reqwest::Client::new()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .post(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8080/mcp/call&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .json(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;json!&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;tool&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;tool_name&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;args&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;params&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; .send()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. 응답 파싱 및 로깅
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tracing::&lt;span style="color:#a6e22e"&gt;info!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;MCP Tool {} called successfully&amp;#34;&lt;/span&gt;, tool_name);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(response.text().&lt;span style="color:#66d9ef"&gt;await&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="4-결론-2026-상반기-발전-방향"&gt;4. 결론: 2026 상반기 발전 방향
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 단순한 실험적 프로젝트를 넘어, &lt;code&gt;2026 상반기 발전방향 회의록&lt;/code&gt;에서 언급된 바와 같이 &amp;lsquo;고성능 에이전트 런타임&amp;rsquo;으로 진화하고 있습니다.&lt;/p&gt;
&lt;p&gt;Rust의 성능을 바탕으로 LLM의 생성성(Creativity)과 시스템의 안전성(Safety)을 동시에 확보하는 것이 목표입니다. 특히, 최근 트렌드인 &lt;strong&gt;DeepSeek&lt;/strong&gt;와 같은 효율적인 모델을 통합하여 비용 효율성까지 개선할 계획입니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 ZeroClaw 에이전트가 실제로 코드를 생성하고 배포하는 &lt;strong&gt;CI/CD 파이프라인 연동 사례&lt;/strong&gt;를 다루겠습니다.&lt;/p&gt;
&lt;h2 id="참고-링크"&gt;참고 링크
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="#" &gt;ZeroClaw GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#" &gt;MCP Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>MCP(Model Context Protocol) 기반 멀티 에이전트 시스템 설계 및 구현 가이드</title><link>https://blog.agentthread.dev/ko/post/mcpmodel-context-protocol-%EA%B8%B0%EB%B0%98-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%B0%8F-%EA%B5%AC%ED%98%84-%EA%B0%80%EC%9D%B4%EB%93%9C/</link><pubDate>Sat, 23 May 2026 09:01:19 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/mcpmodel-context-protocol-%EA%B8%B0%EB%B0%98-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%B0%8F-%EA%B5%AC%ED%98%84-%EA%B0%80%EC%9D%B4%EB%93%9C/</guid><description>&lt;h1 id="mcpmodel-context-protocol-기반-멀티-에이전트-시스템-설계-및-구현-가이드"&gt;MCP(Model Context Protocol) 기반 멀티 에이전트 시스템 설계 및 구현 가이드
&lt;/h1&gt;&lt;p&gt;최근 생성형 AI 도메인에서 단일 LLM(Large Language Model)의 한계를 극복하기 위해 **멀티 에이전트 시스템(Multi-Agent System)**이 주목받고 있습니다. 특히, 에이전트 간의 표준화된 통신을 위한 **MCP(Model Context Protocol)**가 등장하면서, 확장 가능하고 모듈화된 아키텍처를 설계하는 것이 필수적이 되었습니다.&lt;/p&gt;
&lt;p&gt;본 포스트에서는 &lt;code&gt;ZeroClaw&lt;/code&gt;와 같은 고성능 에이전트 런타임 환경에서 MCP를 활용해 효율적인 멀티 에이전트 아키텍처를 설계하고 구현하는 방법을 살펴봅니다. 독자가 바로 적용할 수 있도록 아키텍처 설계 원칙과 실제 코드 예제를 포함했습니다.&lt;/p&gt;
&lt;h2 id="1-mcp-기반-멀티-에이전트-아키텍처-설계"&gt;1. MCP 기반 멀티 에이전트 아키텍처 설계
&lt;/h2&gt;&lt;p&gt;멀티 에이전트 시스템을 구축할 때 가장 중요한 것은 &lt;strong&gt;결합도(Coupling)를 낮추고 응집도(Cohesion)를 높이는 것&lt;/strong&gt;입니다. MCP는 이를 달성하기 위해 클라이언트-서버 모델을 채택하여, 각 에이전트나 도구가 독립적인 서버로 동작하며 표준 프로토콜(JSON-RPC 2.0 기반)을 통해 통신하도록 합니다.&lt;/p&gt;
&lt;h3 id="핵심-설계-원칙"&gt;핵심 설계 원칙
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;표준화된 인터페이스 (Standardized Interface):&lt;/strong&gt; 모든 에이전트는 MCP 표준을 준수하여 &lt;code&gt;tools&lt;/code&gt;, &lt;code&gt;resources&lt;/code&gt;, &lt;code&gt;prompts&lt;/code&gt;를 노출해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비동기 통신 (Asynchronous Communication):&lt;/strong&gt; 작업 수행 시간이 긴 에이전트(예: 파일 처리, 웹 스크래핑)를 위해 비동기 메시지 큐를 활용해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 비저장 (Stateless):&lt;/strong&gt; 에이전트 서버는 가능한 한 상태 비저장으로 설계하여 수평 확장(Scale-out)이 용이하게 만들어야 합니다.&lt;/li&gt;
&lt;/ol&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[User/Client LLM] &amp;lt;--&amp;gt; [MCP Gateway (Orchestrator)]
&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; v v v
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [Blog Server] [Discord MCP] [Cloud Monitor]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; (Rust/TS) (Gateway) (Agent)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이 구조에서 **Gateway(오케스트레이터)**는 사용자의 요청을 분석하여 적절한 MCP 서버(에이전트)에게 작업을 위임하고, 결과를 취합하여 다시 사용자에게 전달하는 역할을 수행합니다.&lt;/p&gt;
&lt;h2 id="2-mcp-서버-구현-rust로-가벼운-에이전트-만들기"&gt;2. MCP 서버 구현: Rust로 가벼운 에이전트 만들기
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;blog-api-server&lt;/code&gt;나 &lt;code&gt;ZeroClaw&lt;/code&gt; 환경에서 고성능을 발휘하기 위해 Rust로 간단한 MCP 서버를 구현해 보겠습니다. 이 서버는 현재 시간을 반환하는 간단한 도구를 제공합니다.&lt;/p&gt;
&lt;h3 id="의존성-설정-cargotoml"&gt;의존성 설정 (Cargo.toml)
&lt;/h3&gt;&lt;p&gt;Rust의 강력한 비동기 런타임인 &lt;code&gt;tokio&lt;/code&gt;와 JSON 처리를 위한 &lt;code&gt;serde&lt;/code&gt;, 그리고 MCP 통신을 위한 &lt;code&gt;hyper&lt;/code&gt; 또는 &lt;code&gt;jsonrpc&lt;/code&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-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tokio&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;full&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;derive&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;hyper&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.14&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;server&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;http1&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="mcp-서버-코드-예제"&gt;MCP 서버 코드 예제
&lt;/h3&gt;&lt;p&gt;다음은 STDIN/STDOUT을 통해 통신하는 표준 MCP 서버의 뼈대를 간소화하여 구현한 것입니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde_json::{Value, json};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::io::{self, BufRead, Write};
&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;// MCP 요청/응답 구조체 정의
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MCPRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; method: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; params: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Value&lt;span style="color:#f92672"&gt;&amp;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 style="color:#75715e"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MCPResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Value&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Value&lt;span style="color:#f92672"&gt;&amp;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 style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_request&lt;/span&gt;(req: &lt;span style="color:#a6e22e"&gt;MCPRequest&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;MCPResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; req.method.as_str() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;initialize&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;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; MCPResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: Some(&lt;span style="color:#a6e22e"&gt;json!&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;protocolVersion&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2024-11-05&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;capabilities&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;tools&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:#e6db74"&gt;&amp;#34;serverInfo&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;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;my-rust-agent&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;version&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0.1.0&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; error: None,
&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:#e6db74"&gt;&amp;#34;tools/list&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;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; MCPResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: Some(&lt;span style="color:#a6e22e"&gt;json!&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;tools&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:#e6db74"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;get_current_time&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;description&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Returns the current server time&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;inputSchema&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;type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;object&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;properties&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;span style="display:flex;"&gt;&lt;span&gt; error: None,
&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:#e6db74"&gt;&amp;#34;tools/call&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;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; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Some(params) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; req.params {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; tool_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; params[&lt;span style="color:#e6db74"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;].as_str().unwrap_or(&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 style="color:#66d9ef"&gt;if&lt;/span&gt; tool_name &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;get_current_time&amp;#34;&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; MCPResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: Some(&lt;span style="color:#a6e22e"&gt;json!&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;content&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:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;text&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;text&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;format&lt;/span&gt;&lt;span style="color:#f92672"&gt;!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Current time: {}&amp;#34;&lt;/span&gt;, chrono::Utc::now().to_rfc3339())
&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; error: None,
&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 style="color:#75715e"&gt;// 에러 처리
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MCPResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: None,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error: Some(&lt;span style="color:#a6e22e"&gt;json!&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;code&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;32601&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;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Tool not found&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 style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; MCPResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jsonrpc: &lt;span style="color:#e6db74"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result: None,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error: Some(&lt;span style="color:#a6e22e"&gt;json!&lt;/span&gt;({ &lt;span style="color:#e6db74"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;32601&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Method not found&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;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; stdin &lt;span style="color:#f92672"&gt;=&lt;/span&gt; io::stdin();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; stdout &lt;span style="color:#f92672"&gt;=&lt;/span&gt; io::stdout();
&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;for&lt;/span&gt; line &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; stdin.lock().lines() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; line &lt;span style="color:#f92672"&gt;=&lt;/span&gt; line.unwrap();
&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; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Ok(req) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; serde_json::from_str::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;MCPRequest&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;line) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; res &lt;span style="color:#f92672"&gt;=&lt;/span&gt; handle_request(req);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; res_json &lt;span style="color:#f92672"&gt;=&lt;/span&gt; serde_json::to_string(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;res).unwrap() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &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;span style="display:flex;"&gt;&lt;span&gt; stdout.write_all(res_json.as_bytes()).unwrap();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stdout.flush().unwrap();
&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;p&gt;이 코드는 MCP의 핵심인 &lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;tools/list&lt;/code&gt;, &lt;code&gt;tools/call&lt;/code&gt; 메서드를 처리하여 LLM(예: Claude Code)이 이 에이전트를 도구로 인식하고 호출할 수 있게 합니다.&lt;/p&gt;
&lt;h2 id="3-에이전트-간-통신-및-데이터-동기화"&gt;3. 에이전트 간 통신 및 데이터 동기화
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;ZeroClaw&lt;/code&gt; 아키텍처에서와 같이 여러 에이전트가 협업할 때는 데이터 공유가 중요합니다. 파일 기반 아키텍처(File-based architecture)를 사용하면 에이전트 간 결합도를 낮추면서 데이터를 주고받을 수 있습니다.&lt;/p&gt;
&lt;h3 id="파일-기반-상태-공유-예시"&gt;파일 기반 상태 공유 예시
&lt;/h3&gt;&lt;p&gt;에이전트 A가 데이터를 생성하고, 에이전트 B가 이를 소비하는 시나리오입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;에이전트 A (생산자):&lt;/strong&gt; 계산 결과를 &lt;code&gt;/tmp/shared/task_result.json&lt;/code&gt;에 저장합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에이전트 B (소비자):&lt;/strong&gt; MCP를 통해 &lt;code&gt;file://tmp/shared/task_result.json&lt;/code&gt; 리소스를 읽거나, 별도의 &lt;code&gt;read_file&lt;/code&gt; 도구를 호출합니다.&lt;/li&gt;
&lt;/ol&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 에이전트 A의 도구 호출 예시 (의사 코드)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;save_result&lt;/span&gt;(data: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;std&lt;/span&gt;::io::Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fs::write(&lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/shared/task_result.json&amp;#34;&lt;/span&gt;, data)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&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;이 방식은 메시지 큐가 복잡할 때 유용하며, &lt;code&gt;blog-api-server&lt;/code&gt;의 로깅 시스템이나 모니터링 대시보드처럼 데이터를 지속적으로 추적해야 할 때 유효합니다.&lt;/p&gt;
&lt;h2 id="4-결론-및-발전-방향"&gt;4. 결론 및 발전 방향
&lt;/h2&gt;&lt;p&gt;MCP를 활용하면 복잡한 멀티 에이전트 시스템을 &lt;strong&gt;모듈화&lt;/strong&gt;하고 &lt;strong&gt;유지보수&lt;/strong&gt;하기 쉽게 만들 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장점:&lt;/strong&gt; 언어에 독립적인 인터페이스(Rust, TypeScript 등 혼용 가능), 도구의 재사용성, 표준화된 에러 처리.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고려사항:&lt;/strong&gt; STDIN/STDOUT 통신 오버헤드, 대규모 트래픽 처리를 위한 Gateway의 부하 분산.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;향후 &lt;code&gt;ZeroClaw&lt;/code&gt; 런타임 내에서 이러한 MCP 서버들을 컨테이너화하여 필요에 따라 동적으로 스케일링(Scale-out)하는 환경을 구축하는 것이 목표입니다. 이를 통해 LLM이 단순한 챗봇을 넘어, 복잡한 작업을 자동화하는 &lt;strong&gt;지능형 에이전트 네트워크&lt;/strong&gt;로 진화할 수 있습니다.&lt;/p&gt;
&lt;h2 id="참고-자료"&gt;참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://spec.modelcontextprotocol.io/" target="_blank" rel="noopener"
 &gt;Model Context Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/your-repo/zeroclaw" target="_blank" rel="noopener"
 &gt;ZeroClaw Project Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.anthropic.com/" target="_blank" rel="noopener"
 &gt;Claude Code MCP Integration Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>AI 에이전트 신뢰성 강화: Forge Guardrails와 MCP 통합 가이드</title><link>https://blog.agentthread.dev/ko/post/ai-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%A0%EB%A2%B0%EC%84%B1-%EA%B0%95%ED%99%94-forge-guardrails%EC%99%80-mcp-%ED%86%B5%ED%95%A9-%EA%B0%80%EC%9D%B4%EB%93%9C/</link><pubDate>Wed, 20 May 2026 09:00:35 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/ai-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%A0%EB%A2%B0%EC%84%B1-%EA%B0%95%ED%99%94-forge-guardrails%EC%99%80-mcp-%ED%86%B5%ED%95%A9-%EA%B0%80%EC%9D%B4%EB%93%9C/</guid><description>&lt;h1 id="ai-에이전트-신뢰성-강화-forge-guardrails와-mcp-통합-가이드"&gt;AI 에이전트 신뢰성 강화: Forge Guardrails와 MCP 통합 가이드
&lt;/h1&gt;&lt;p&gt;최근 LLM(Large Language Model)을 활용한 에이전트 시스템이 급격히 발전하고 있습니다. Hacker News에서는 &amp;ldquo;Forge – Guardrails take an 8B model from 53% to 99% on agentic tasks&amp;quot;라는 흥미로운 사례가 화제가 되었습니다. 이는 모델의 파라미터 수를 늘리는 것보다, 적절한 **가드레일(Guardrails)**을 적용하는 것이 특정 작업의 성공률을 비약적으로 높일 수 있음을 시사합니다.&lt;/p&gt;
&lt;p&gt;저희 팀에서 진행 중인 &lt;strong&gt;ZeroClaw&lt;/strong&gt; 및 &lt;strong&gt;MCP(Model Context Protocol)&lt;/strong&gt; 기반 프로젝트들에 있어 에이전트의 신뢰성은 필수적입니다. 이번 포스트에서는 오픈소스 가드레일 프레임워크인 Forge의 개념을 살펴보고, 이를 기존 MCP 아키텍처에 통합하여 에이전트의 안정성을 확보하는 구체적인 방법을 소개합니다.&lt;/p&gt;
&lt;h2 id="문제-정의-자율성의-딜레마"&gt;문제 정의: 자율성의 딜레마
&lt;/h2&gt;&lt;p&gt;에이전트에게 자율성을 부여할수록 예상치 못한 행동을 할 위험이 커집니다. 예를 들어, 블로그 게시글을 생성하라는 MCP 요청이 있을 때, 에이전트가 시스템 명령어를 실행하거나 허용되지 않은 API를 호출하려 시도할 수 있습니다.&lt;/p&gt;
&lt;p&gt;기존의 &lt;strong&gt;[blog-api-server]&lt;/strong&gt; 구현에서는 프롬프트 엔지니어링과 간단한 JSON 스키마 검증으로 이를 방어하려 했으나, 복잡한 멀티 에이전트 환경에서는 한계가 있었습니다. 이를 해결하기 위해 입력과 출력을 사전에 필터링하는 &lt;strong&gt;L1 가드레일&lt;/strong&gt; 레이어를 도입하기로 결정했습니다.&lt;/p&gt;
&lt;h2 id="솔루션-guardrails-패턴-적용"&gt;솔루션: Guardrails 패턴 적용
&lt;/h2&gt;&lt;p&gt;Forge가 보여주듯, 에이전트 작업의 성공률(53% → 99%)을 높이는 핵심은 &lt;strong&gt;실행 전 검증&lt;/strong&gt;입니다. 우리는 에이전트가 LLM으로부터 응답을 받아 사용자에게 전달하거나 도구(Tool)를 실행하기 전, 중간 계층에서 이를 검증하는 구조를 설계했습니다.&lt;/p&gt;
&lt;h3 id="아키텍처-개요"&gt;아키텍처 개요
&lt;/h3&gt;&lt;p&gt;기존 MCP 클라이언트와 LLM 사이에 &lt;code&gt;Validator&lt;/code&gt; 계층을 두어 다음을 수행합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;입력 검증 (Input Validation):&lt;/strong&gt; 사용자의 요청이 시스템 정책을 위반하지 않는지 확인 (예: 공격적인 프롬프트 필터링).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;출력 검증 (Output Validation):&lt;/strong&gt; LLM이 생성한 JSON이나 함수 호출 인자가 스키마에 부합하는지 확인.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="실전-코드-예제-rust로-구현하는-안전장치"&gt;실전 코드 예제: Rust로 구현하는 안전장치
&lt;/h2&gt;&lt;p&gt;ZeroClaw의 Rust 환경에서 가벼운 출력 검증기를 구현해 보겠습니다. 복잡한 외부 라이브러리 없이, &lt;code&gt;serde&lt;/code&gt;와 &lt;code&gt;regex&lt;/code&gt;를 활용하여 LLM이 생성한 코드 실행 명령을 안전하게 감싸는 예제입니다.&lt;/p&gt;
&lt;h3 id="1-검증-로직-구현하기"&gt;1. 검증 로직 구현하기
&lt;/h3&gt;&lt;p&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; regex::Regex;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&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:#75715e"&gt;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentCommand&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_name: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parameters: String,
&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:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guardrail&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;impl&lt;/span&gt; Guardrail {
&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;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;is_dangerous&lt;/span&gt;(input: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; dangerous_patterns &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;vec!&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;rm -rf&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;sudo&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;eval&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;__import__&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dangerous_patterns.iter().any(&lt;span style="color:#f92672"&gt;|&amp;amp;&lt;/span&gt;pat&lt;span style="color:#f92672"&gt;|&lt;/span&gt; input.contains(pat))
&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; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;validate_command&lt;/span&gt;(cmd: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;AgentCommand&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&amp;amp;&lt;/span&gt;AgentCommand, String&lt;span style="color:#f92672"&gt;&amp;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;// 1. 도구 이름 화이트리스트 확인
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; allowed_tools &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;vec!&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;blog_post&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;search&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;read_file&amp;#34;&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; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;allowed_tools.contains(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;cmd.tool_name.as_str()) {
&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; Err(&lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;허용되지 않은 도구 사용 시도: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, cmd.tool_name));
&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;// 2. 파라미터 내 위험 키워드 검사
&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; Self::is_dangerous(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;cmd.parameters) {
&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; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;파라미터에 잠재적으로 위험한 명령어가 포함되어 있습니다.&amp;#34;&lt;/span&gt;.to_string());
&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;// 3. 안전하다면 명령어 승인
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(cmd)
&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;h3 id="2-에이전트-루프에-통합하기"&gt;2. 에이전트 루프에 통합하기
&lt;/h3&gt;&lt;p&gt;이제 검증기를 MCP 서버의 요청 처리 루프에 연결합니다. 에이전트가 응답을 생성하면, 실제 시스템이 이를 실행하기 전에 &lt;code&gt;Guardrail&lt;/code&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-rust" data-lang="rust"&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;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;execute_agent_task&lt;/span&gt;(llm_output: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, String&lt;span style="color:#f92672"&gt;&amp;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;// 1. LLM 출력 파싱 (실제로는 JSON 파싱 등)
&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;let&lt;/span&gt; command &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AgentCommand {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_name: &lt;span style="color:#e6db74"&gt;&amp;#34;blog_post&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parameters: &lt;span style="color:#e6db74"&gt;&amp;#34;title: &amp;#39;Hello World&amp;#39;&amp;#34;&lt;/span&gt;.to_string(),
&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;// 2. 가드레일 통과 전
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[System] LLM 응답 수신: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, command.tool_name);
&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;// 3. 가드레일 검증 실행
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; safe_command &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Guardrail::validate_command(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;command)&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 4. 검증된 명령어 실행
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[System] 안전한 명령어 실행 중...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 실제 도구 실행 로직 (예: 블로그 API 호출)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;게시글이 성공적으로 생성되었습니다.&amp;#34;&lt;/span&gt;.to_string())
&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:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&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;match&lt;/span&gt; execute_agent_task(&lt;span style="color:#e6db74"&gt;&amp;#34;valid_response&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(msg) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;성공: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, msg),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;차단됨: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e),
&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; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; malicious_cmd &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AgentCommand {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_name: &lt;span style="color:#e6db74"&gt;&amp;#34;system_shell&amp;#34;&lt;/span&gt;.to_string(), &lt;span style="color:#75715e"&gt;// 화이트리스트에 없음
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parameters: &lt;span style="color:#e6db74"&gt;&amp;#34;rm -rf /&amp;#34;&lt;/span&gt;.to_string(),
&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:#66d9ef"&gt;match&lt;/span&gt; Guardrail::validate_command(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;malicious_cmd) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(_) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&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; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;보호됨: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e), &lt;span style="color:#75715e"&gt;// &amp;#34;보호됨: 허용되지 않은 도구 사용 시도: system_shell&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="효과-및-전망"&gt;효과 및 전망
&lt;/h2&gt;&lt;p&gt;이러한 &lt;strong&gt;L1 방어선&lt;/strong&gt;을 구축함으로써 우리는 다음과 같은 이점을 얻을 수 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;안정성 향상:&lt;/strong&gt; Forge 사례처럼 8B 모델도 충분히 안전하게 활용할 수 있어 추론 비용 절감.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;투명성 확보:&lt;/strong&gt; 에이전트가 왜 특정 작업을 거부했는지 로그를 통해 명확히 파악 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;유지보수성:&lt;/strong&gt; 보안 정책이 변경되어도 &lt;code&gt;Guardrail&lt;/code&gt; 모듈만 수정하면 됨.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;앞으로 &lt;strong&gt;ZeroClaw&lt;/strong&gt; 프로젝트에서는 이 검증 로직을 비동기 런타임에 통합하여, 멀티 에이전트 간 통신([Discord MCP], [Cloud Monitor]) 시에도 실시간으로 안전성을 모니터링할 계획입니다.&lt;/p&gt;
&lt;p&gt;단순히 모델의 성능을 높이는 것에 집착하기보다, 이처럼 &lt;strong&gt;시스템 레벨에서의 안전장치&lt;/strong&gt;를 어떻게 설계하느냐가 AI 에이전트를 실제 프로덕션에 배포하는 관건이 될 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;이 글은 ZeroClaw 및 MCP 관련 아키텍처 설계 문서들을 참고하여 작성되었습니다.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>ZeroClaw 아키텍처 심화: Rust 기반 안전한 에이전트 간 통신 구현하기</title><link>https://blog.agentthread.dev/ko/post/zeroclaw-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%8B%AC%ED%99%94-rust-%EA%B8%B0%EB%B0%98-%EC%95%88%EC%A0%84%ED%95%9C-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EA%B0%84-%ED%86%B5%EC%8B%A0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0/</link><pubDate>Mon, 11 May 2026 09:01:26 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/zeroclaw-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%8B%AC%ED%99%94-rust-%EA%B8%B0%EB%B0%98-%EC%95%88%EC%A0%84%ED%95%9C-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EA%B0%84-%ED%86%B5%EC%8B%A0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0/</guid><description>&lt;h1 id="zeroclaw-아키텍처-심화-rust-기반-안전한-에이전트-간-통신-구현하기"&gt;ZeroClaw 아키텍처 심화: Rust 기반 안전한 에이전트 간 통신 구현하기
&lt;/h1&gt;&lt;p&gt;안녱하세요! ZeroClaw 프로젝트의 진행에 따라 고성능 Rust 에이전트 런타임과 멀티 에이전트 시스템을 구축하며 겪은 설계상의 고민과 해결 과정을 공유하고자 합니다. 최근 &amp;lsquo;Obsidian 플러그인을 악용한 원격 접속 트로이어나(RAT)&amp;rsquo; 사건과 같은 보안 이슈들은 플러그인 및 에이전트 시스템에서 **보안(Security)**과 **신뢰성(Trust)**이 얼마나 중요한지를 다시금 상기시켜 줍니다.&lt;/p&gt;
&lt;p&gt;오늘은 ZeroClaw의 코드베이스를 분석하며 설계한, &lt;strong&gt;안전하고 확장 가능한 에이전트 간 통신 프로토콜&lt;/strong&gt;을 구체적인 Rust 코드 예제와 함께 소개하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-서론-보안을-고려한-에이전트-통신의-필요성"&gt;1. 서론: 보안을 고려한 에이전트 통신의 필요성
&lt;/h2&gt;&lt;p&gt;기존의 MCP(Model Context Protocol)나 블로그 자동화 시스템 구축 과정에서 우리는 단순한 데이터 전송을 넘어, 에이전트 간의 **신원 확인(Authentication)**과 **권한 부여(Authorization)**가 필수적임을 깨달았습니다. 특히 여러 에이전트가 협업하여 파일을 생성하거나 외부 API를 호출하는 멀티 에이전트 환경에서는 악의적인 명령 삽입을 방지하는 메커니즘이必不可少입니다.&lt;/p&gt;
&lt;p&gt;ZeroClaw는 이를 위해 Rust의 타입 안전성(Type Safety)과 소유권(Ownership) 시스템을 적극 활용하여, 컴파일 타임에 많은 버그를 잡고 런타임 오버헤드를 최소화하는 아키텍처를 채택했습니다.&lt;/p&gt;
&lt;h2 id="2-통신-프로토콜-설계-ipc와-메시지-큐"&gt;2. 통신 프로토콜 설계: IPC와 메시지 큐
&lt;/h2&gt;&lt;p&gt;ZeroClaw의 통신 플랫폼은 크게 **로컬 IPC(Inter-Process Communication)**와 &lt;strong&gt;비동기 메시지 큐&lt;/strong&gt;로 나뉩니다. 로그 시스템 개선 과정에서 얻은 경험을 바탕으로, 모든 통신은 비동기(async)로 처리되며 각 에이전트의 독립성을 보장합니다.&lt;/p&gt;
&lt;h3 id="주요-설계-원칙"&gt;주요 설계 원칙
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Zero-Copy Serialization:&lt;/strong&gt; &lt;code&gt;serde&lt;/code&gt;와 &lt;code&gt;bincode&lt;/code&gt;를 활용하여 데이터 직렬화 오버헤드를 최소화합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Message Validation:&lt;/strong&gt; 들어오는 모든 메시지를 검증하는 레이어를 두어, 변조된 데이터가 시스템 코어에 도달하는 것을 방지합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capability-Based Security:&lt;/strong&gt; 단순한 토큰 기반 인증 넘어, 에이전트가 수행할 수 있는 &lt;strong&gt;행위(Capability)&lt;/strong&gt; 자체를 제한합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="3-rust로-구현하는-안전한-메시징-핸들러"&gt;3. Rust로 구현하는 안전한 메시징 핸들러
&lt;/h2&gt;&lt;p&gt;이제 실제 코드를 통해 어떻게 구현했는지 살펴보겠습니다. 이 예제는 &lt;code&gt;tokio&lt;/code&gt;를 기반으로 하며, 안전한 메시지 전달을 위한 기본 구조를 보여줍니다.&lt;/p&gt;
&lt;h3 id="31-메시지-정의-및-검증"&gt;3.1 메시지 정의 및 검증
&lt;/h3&gt;&lt;p&gt;먼저, 에이전트 간에 오가는 데이터 구조를 정의하고, &lt;code&gt;validator&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// message.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; validator::Validate;
&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:#e6db74"&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;#[derive(Debug, Clone, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskRequest { id: String, payload: &lt;span style="color:#a6e22e"&gt;TaskPayload&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskResponse { id: String, result: String },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Heartbeat,
&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:#e6db74"&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;#[derive(Debug, Clone, Serialize, Deserialize, Validate)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TaskPayload&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#[validate(length(min = 1, max = 100))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; command: String,
&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;// JSON 형식의 인자를 받지만, 구조적으로 안전하게 파싱해야 함
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; args: &lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt;::Value, 
&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:#66d9ef"&gt;impl&lt;/span&gt; AgentMessage {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from_bytes&lt;/span&gt;(bytes: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;u8&lt;/span&gt;]) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Self, Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;// 1. 기본 직렬화 검증
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; msg: &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; bincode::deserialize(bytes)&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. 비즈니스 로직 검증 (예: Command Injection 방지)
&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; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; AgentMessage::TaskRequest { payload, &lt;span style="color:#f92672"&gt;..&lt;/span&gt; } &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;msg {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload.validate()&lt;span style="color:#f92672"&gt;?&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// validator 크레이트 사용
&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; payload.command.contains(&lt;span style="color:#e6db74"&gt;&amp;#34;rm &amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;||&lt;/span&gt; payload.command.contains(&lt;span style="color:#e6db74"&gt;&amp;#34;sudo&amp;#34;&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; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;Blocked potentially dangerous command&amp;#34;&lt;/span&gt;.into());
&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; Ok(msg)
&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;h3 id="32-액터-모델-기반-에이전트-구조"&gt;3.2 액터 모델 기반 에이전트 구조
&lt;/h3&gt;&lt;p&gt;각 에이전트는 자신의 상태를 가지며, 메시지를 받아 처리하는 &lt;code&gt;Actor&lt;/code&gt; 패턴을 따릅니다. Rust의 &lt;code&gt;tokio::sync::mpsc&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// agent.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::sync::mpsc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;::message::AgentMessage;
&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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Agent&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; receiver: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;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&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;impl&lt;/span&gt; Agent {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;(id: String, receiver: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Self { id, receiver }
&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:#e6db74"&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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Agent started&amp;#34;&lt;/span&gt;, self.id);
&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;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Some(msg) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.receiver.recv().&lt;span style="color:#66d9ef"&gt;await&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; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.handle_message(msg).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Error handling message: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, self.id, e);
&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_message&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, msg: &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;match&lt;/span&gt; msg {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AgentMessage::TaskRequest { id, payload } &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Executing task &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, self.id, id, payload.command);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 여기에 실제 작업 실행 로직 (예: LLM 호출, 파일 쓰기 등)
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AgentMessage::Heartbeat &lt;span style="color:#f92672"&gt;=&amp;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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _ &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;Unknown message type&amp;#34;&lt;/span&gt;.into()),
&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; Ok(())
&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;h3 id="33-런타임-및-메시지-라우팅"&gt;3.3 런타임 및 메시지 라우팅
&lt;/h3&gt;&lt;p&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// runtime.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::sync::mpsc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::collections::HashMap;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;::agent::Agent;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;::message::AgentMessage;
&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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Runtime&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agents: &lt;span style="color:#a6e22e"&gt;HashMap&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, mpsc::Sender&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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 style="color:#66d9ef"&gt;impl&lt;/span&gt; Runtime {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;() -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Self { agents: &lt;span style="color:#a6e22e"&gt;HashMap&lt;/span&gt;::new() }
&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:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spawn_agent&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self, id: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; (tx, rx) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mpsc::channel(&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; agent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Agent::new(id.to_string(), rx);
&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; tokio::spawn(agent.run());
&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; self.agents.insert(id.to_string(), tx);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Runtime: Agent &amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39; spawned&amp;#34;&lt;/span&gt;, id);
&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:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;broadcast&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, msg: &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt;) {
&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; (id, tx) &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; self.agents.iter() {
&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; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tx.send(msg.clone()).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to send to &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, id, e);
&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;h2 id="4-마무리-및-다음-단계"&gt;4. 마무리 및 다음 단계
&lt;/h2&gt;&lt;p&gt;위에서 구현한 코드는 ZeroClaw 멀티 에이전트 시스템의 핵심인 **&amp;lsquo;안전한 통신&amp;rsquo;**과 **&amp;lsquo;격리된 상태 관리&amp;rsquo;**를 보여줍니다. Obsidian 플러그인 사건처럼, 외부 입력이나 스크립트를 실행하는 시스템은 언제나 공격의 대상이 될 수 있습니다.&lt;/p&gt;
&lt;p&gt;ZeroClaw는 Rust의 강력한 타입 시스템을 통해 이러한 위험을 컴파일 타임에 대부분 차단하고, 런타임에도 추가적인 검증 레이어를 두어 방어합니다. 다음 포스트에서는 이 에이전트 시스템이 실제로 &lt;strong&gt;Discord나 MCP와 같은 외부 게이트웨이와 어떻게 연동되는지&lt;/strong&gt;, 그리고 &lt;strong&gt;LLM 설정을 통해 어떻게 제어되는지&lt;/strong&gt;에 대해 다루겠습니다.&lt;/p&gt;
&lt;p&gt;지금까지 ZeroClaw의 아키텍처 설계 현황을 공유했습니다. 고성능이면서도 안전한 에이전트 시스템을 구축하려는 분들께 도움이 되기를 바랍니다!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;참고:&lt;/strong&gt; 위 코드는 개념 증명(POC) 수준의 예제이며, 실제 프로덕션 환경에서는 더욱 견고한 에러 핸들링과 로깅 시스템(이전 포스트에서 개선한 로깅 설정 참조)이 통합되어야 합니다.&lt;/p&gt;</description></item><item><title>Rust로 구현하는 고성능 MCP 서버: ZeroMQ를 활용한 비동기 메시징</title><link>https://blog.agentthread.dev/ko/post/rust%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EA%B3%A0%EC%84%B1%EB%8A%A5-mcp-%EC%84%9C%EB%B2%84-zeromq%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A9%94%EC%8B%9C%EC%A7%95/</link><pubDate>Sun, 10 May 2026 09:01:24 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/rust%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EA%B3%A0%EC%84%B1%EB%8A%A5-mcp-%EC%84%9C%EB%B2%84-zeromq%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A9%94%EC%8B%9C%EC%A7%95/</guid><description>&lt;h1 id="rust로-구현하는-고성능-mcp-서버-zeromq를-활용한-비동기-메시징"&gt;Rust로 구현하는 고성능 MCP 서버: ZeroMQ를 활용한 비동기 메시징
&lt;/h1&gt;&lt;p&gt;최근 &lt;code&gt;ZeroClaw&lt;/code&gt; 프로젝트를 진행하며 MCP(Model Context Protocol) 서버의 성능 한계를 뛰어넘을 필요성이 생겼습니다. 기존의 HTTP 기반 통신은 단일 요청-응답 패턴을 벗어나기 어렵고, 수많은 LLM 에이전트 간의 실시간 통신에는 병목이 발생하기 쉽습니다. 이번 포스트에서는 Rust의 강력한 비동기 처리 능력과 ZeroMQ(ØMQ)를 결용하여 초경량 초고속 MCP 서버를 구축하는 과정을 다룹니다.&lt;/p&gt;
&lt;h2 id="왜-zeromq인가-tcp-소켓의-한계와-비교"&gt;왜 ZeroMQ인가? (TCP 소켓의 한계와 비교)
&lt;/h2&gt;&lt;p&gt;기존 &lt;code&gt;blog-api-server&lt;/code&gt; 아키텍처에서는 표준 TCP 스트림을 사용하여 프로토콜을 직접 정의하고 사용했습니다. 하지만 멀티 에이전트 환경에서는 다음과 같은 문제가 발생합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Connection Management(연결 관리) 복잡도&lt;/strong&gt;: 에이전트가 수십 개씩 늘어날 때, &lt;code&gt;Accept&lt;/code&gt; 루프와 소켓 상태 관리를 위한 &lt;code&gt;unsafe&lt;/code&gt; 코드나 복잡한 상태 머신(State Machine)이 필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Message Enveloping(메시지 봉투)&lt;/strong&gt;: TCP는 바이트 스트림입니다. 메시지의 경계를 구분하기 위해 Length-Prefixing을 직접 구현해야 하며, 이는 버그의 주요 원인이 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ZeroMQ는 이러한 하부 소켓 계층의 복잡성을 추상화하면서, TCP보다 빠른 &amp;ldquo;사용자 공간(User Space)&amp;rdquo; 전송 계층을 제공합니다. 특히 &lt;code&gt;ipc&lt;/code&gt;(Inter-Process Communication) 프로토콜을 사용하면 로컬호스트 통신에서 네트워크 스택 오버헤드를 완전히 제거할 수 있습니다.&lt;/p&gt;
&lt;h2 id="아키텍처-설계-zeromq-pubsub-패턴"&gt;아키텍처 설계: ZeroMQ PUB/SUB 패턴
&lt;/h2&gt;&lt;p&gt;이번 구현에서는 에이전트 간 느슨한 결합을 위해 &lt;strong&gt;Publish/Subscribe(PUB/SUB)&lt;/strong&gt; 패턴을 사용합니다. 하나의 에이전트가 상태를 변경(Publish)하면, 해당 토픽을 구독하는 다른 에이전트들에게 즉시 메시지가 브로드캐스트됩니다.&lt;/p&gt;
&lt;h3 id="주요-의존성-cargotoml"&gt;주요 의존성 (Cargo.toml)
&lt;/h3&gt;&lt;p&gt;Rust 생태계에서 ZeroMQ는 &lt;code&gt;zmq&lt;/code&gt; 크레이트를 통해 사용할 수 있습니다. 비동기 런타임과의 통합을 위해 &lt;code&gt;tokio-zmq&lt;/code&gt;를 사용할 수도 있지만, 순수 성능을 위해 &lt;code&gt;zmq&lt;/code&gt;의 &lt;code&gt;poll&lt;/code&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-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;zmq&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.10&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;derive&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tokio&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;full&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="구현-고성능-mcp-router"&gt;구현: 고성능 MCP Router
&lt;/h2&gt;&lt;p&gt;아래 코드는 간단한 메시지 브로커 역할을 하는 MCP Router의 구현 예제입니다. 이는 여러 LLM 에이전트(팀)가 서로 통신할 수 있는 중앙 허브 역할을 합니다.&lt;/p&gt;
&lt;h3 id="1-메시지-프로토콜-정의"&gt;1. 메시지 프로토콜 정의
&lt;/h3&gt;&lt;p&gt;먼저, 에이전트 간에 오가는 메시지 형식을 정의합니다. JSON 직렬화를 사용하여 호환성을 확보합니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&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;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;McpMessage&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; source_id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; target_topic: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; payload: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; timestamp: &lt;span style="color:#66d9ef"&gt;i64&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="2-비동기-zeromq-컨텍스트-및-소켓-설정"&gt;2. 비동기 ZeroMQ 컨텍스트 및 소켓 설정
&lt;/h3&gt;&lt;p&gt;ZeroMQ의 Context는 스레드 안전(Thread-safe)하므로, 애플리케이션 전역에서 하나만 생성하여 공유하는 것이 일반적입니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ouse std::time::SystemTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; zmq::{Context, Socket, SocketType};
&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;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;McpRouter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context: &lt;span style="color:#a6e22e"&gt;Context&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frontend: &lt;span style="color:#a6e22e"&gt;Socket&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// Agents connect here
&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&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;impl&lt;/span&gt; McpRouter {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;() -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Self, zmq::Error&lt;span style="color:#f92672"&gt;&amp;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;let&lt;/span&gt; context &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Context::new();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; frontend &lt;span style="color:#f92672"&gt;=&lt;/span&gt; context.socket(SocketType::&lt;span style="color:#66d9ef"&gt;SUB&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&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; frontend.set_subscribe(&lt;span style="color:#e6db74"&gt;b&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(McpRouter { context, frontend })
&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:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, endpoint: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), zmq::Error&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self.frontend.bind(endpoint)&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:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[ZeroClaw Router] Listening on &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, endpoint);
&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;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; msg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; zmq::Message::new();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;loop&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Non-blocking receive를 위해 poll 사용 가능
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 여기서는 간단한 blocking receive 예시
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; self.frontend.recv(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; msg) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(_) &lt;span style="color:#f92672"&gt;=&amp;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;let&lt;/span&gt; data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; msg.as_str().unwrap();
&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; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Ok(mcp_msg) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; serde_json::from_str::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;McpMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(data) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self.route_message(mcp_msg);
&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; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Receive Error: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e);
&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;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;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;route_message&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, msg: &lt;span style="color:#a6e22e"&gt;McpMessage&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:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Routing from &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; on topic &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, msg.source_id, msg.target_topic);
&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&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="3-에이전트-발행publisher-구현"&gt;3. 에이전트 발행(Publisher) 구현
&lt;/h3&gt;&lt;p&gt;이제 개별 에이전트가 메시지를 보내는 코드를 작성해 보겠습니다. &lt;code&gt;tokio&lt;/code&gt;와 함께 사용할 때는 별도의 스레드에서 ZeroMQ의 blocking 함수를 실행해야 비동기 런타임을 방해하지 않습니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::thread;
&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;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spawn_publisher_agent&lt;/span&gt;(id: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;, endpoint: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; context &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Context::new();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; sender &lt;span style="color:#f92672"&gt;=&lt;/span&gt; context.socket(SocketType::&lt;span style="color:#66d9ef"&gt;PUB&lt;/span&gt;).expect(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to create PUB socket&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sender.connect(endpoint).expect(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to connect to Router&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;// 별도 스레드에서 실행하여 Tokio 런타임과 분리
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; thread::spawn(&lt;span style="color:#66d9ef"&gt;move&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:#66d9ef"&gt;let&lt;/span&gt; counter &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&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; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; i &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; counter {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; msg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; McpMessage {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source_id: &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target_topic: &lt;span style="color:#e6db74"&gt;&amp;#34;general&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload: &lt;span style="color:#a6e22e"&gt;format&lt;/span&gt;&lt;span style="color:#f92672"&gt;!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Message #{} from agent {}&amp;#34;&lt;/span&gt;, i, id),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timestamp: &lt;span style="color:#a6e22e"&gt;SystemTime&lt;/span&gt;::now().duration_since(SystemTime::&lt;span style="color:#66d9ef"&gt;UNIX_EPOCH&lt;/span&gt;).unwrap().as_secs() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;i64&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:#66d9ef"&gt;let&lt;/span&gt; json_str &lt;span style="color:#f92672"&gt;=&lt;/span&gt; serde_json::to_string(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;msg).unwrap();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ZeroMQ는 전송 실패 시 자동으로 재시도하거나 큐에 쌓음 (High-water mark 설정 필요 시)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sender.send(json_str.as_bytes(), &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;).expect(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to send&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; thread::sleep(std::time::Duration::from_millis(&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; }
&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;h2 id="성능-최적화-및-ipc-활용"&gt;성능 최적화 및 IPC 활용
&lt;/h2&gt;&lt;p&gt;Rust ZeroMQ 서버의 진정한 힘은 &lt;strong&gt;IPC(Inter-Process Communication)&lt;/strong&gt; 트랜스포트를 사용할 때 발휘됩니다. TCP를 사용하면 패킷이 네트워크 스택을 거쳐 루프백(Loopback) 되지만, IPC는 Unix Domain Socket이나 Windows Named Pipes를 사용하여 메모리 복사 수준에 가까운 속도로 통신합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ipc&lt;/code&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// TCP 대신 IPC 사용
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; endpoint &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ipc:///tmp/mcp_router.ipc&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;router.start(endpoint);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bun이나 Zig 같은 최신 언어들이 시스템 레벨 최적화를 통해 주목받는 것처럼, Rust와 ZeroMQ의 조합은 LLM 애플리케이션의 &amp;ldquo;시스템 버스&amp;quot;를 구축하는 데 있어 가장 강력한 도구입니다.&lt;/p&gt;
&lt;h2 id="결론"&gt;결론
&lt;/h2&gt;&lt;p&gt;이번 글에서는 &lt;code&gt;ZeroClaw&lt;/code&gt; 프로젝트의 일환으로, Rust와 ZeroMQ를 사용하여 고성능 MCP 서버를 구축하는 방법을 살펴보았습니다. 단순한 HTTP 요청을 넘어, 에이전트 간 실시간 메시징이 필요한 환경에서 이 아키텍처는 확장성과 성능 면에서 큰 이점을 제공합니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이 메시징 시스템에 안정성을 더해주는 Circuit Breaker 패턴과 에러 핸들링 전략을 다루겠습니다.&lt;/p&gt;
&lt;h3 id="참고-자료"&gt;참고 자료
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://zeromq.org/get-started/" target="_blank" rel="noopener"
 &gt;ZeroMQ Guide - The Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/zeromq/tokio-zmq" target="_blank" rel="noopener"
 &gt;Tokio Zmq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/your-org/zeroclaw" target="_blank" rel="noopener"
 &gt;ZeroClaw GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>ZeroClaw 에이전트 런타임의 메모리 안전성과 효율적인 리소스 관리</title><link>https://blog.agentthread.dev/ko/post/zeroclaw-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%95%88%EC%A0%84%EC%84%B1%EA%B3%BC-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EA%B4%80%EB%A6%AC/</link><pubDate>Sat, 09 May 2026 09:01:27 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/zeroclaw-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%95%88%EC%A0%84%EC%84%B1%EA%B3%BC-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EA%B4%80%EB%A6%AC/</guid><description>&lt;h1 id="zeroclaw-에이전트-런타임의-메모리-안전성과-효율적인-리소스-관리"&gt;ZeroClaw 에이전트 런타임의 메모리 안전성과 효율적인 리소스 관리
&lt;/h1&gt;&lt;p&gt;최근 &lt;strong&gt;ZeroClaw&lt;/strong&gt; 프로젝트를 통해 고성능 멀티 에이전트 런타임을 구축하면서, Rust의 특장점인 &amp;lsquo;메모리 안전성&amp;rsquo;과 &amp;lsquo;제로 비용 추상화&amp;rsquo;를 실전에서 어떻게 활용할지 고민하게 되었습니다. 단순히 안전하다는 것을 넘어, 수많은 에이전트가 동시에 메시지를 주고받는 상황에서 어떻게 시스템 리소스를 효율적으로 관리하고 GC(Garbage Collection) 없이 안정적인 성능을 유지할 수 있는지가 핵심 과제였습니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 ZeroClaw 아키텍처 설계 과정에서 적용한 Rust 기반의 효율적인 리소스 관리 전략과 실제 코드 예제를 공유하고자 합니다.&lt;/p&gt;
&lt;h2 id="문제-정의-멀티-에이전트-환경의-리소스-병목"&gt;문제 정의: 멀티 에이전트 환경의 리소스 병목
&lt;/h2&gt;&lt;p&gt;멀티 에이전트 시스템에서는 각 에이전트가 독립적인 상태(State)를 가지며, 서로 비동기 메시지를 통해 통신합니다. 이 과정에서 다음과 같은 리소스 이슈가 발생합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;빈번한 할당/해제 (Allocation Thrashing):&lt;/strong&gt; 수백 개의 에이전트가 초당 수천 개의 메시지를 처리할 때, 힙(Heap) 메모리의 잦은 할당과 해제는 성능 저하의 주원인이 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 경합 (Data Race):&lt;/strong&gt; 여러 에이전트가 공유 리소스에 접근할 때 발생할 수 있는 경합 조건(Race Condition)을 방지하면서도, 지나친 락(Lock) 사용으로 인한 병목을 피해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;수명 주기 관리:&lt;/strong&gt; 에이전트가 비정상적으로 종료되더라도 시스템 전체의 메모리 누수가 발생하지 않도록 안전하게 리소스를 회수하는 메커니즘이 필요합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="해결-전략-rust의-소유권과-tokio의-스케줄링"&gt;해결 전략: Rust의 소유권과 Tokio의 스케줄링
&lt;/h2&gt;&lt;p&gt;ZeroClaw에서는 이러한 문제를 해결하기 위해 Rust의 소유권(Ownership) 시스템과 &lt;code&gt;tokio&lt;/code&gt; 런타임의 비동기 추상화를 결합했습니다.&lt;/p&gt;
&lt;h3 id="1-arc와-rwlock을-활용한-상태-공유"&gt;1. &lt;code&gt;Arc&lt;/code&gt;와 &lt;code&gt;RwLock&lt;/code&gt;을 활용한 상태 공유
&lt;/h3&gt;&lt;p&gt;에이전트 간 통신에서 불변(Immutable) 데이터 공유는 &lt;code&gt;Arc&lt;/code&gt; (Atomic Reference Counting)를 통해 비용을 최소화했습니다. 상태 업데이트가 필요한 경우에는 &lt;code&gt;RwLock&lt;/code&gt;을 사용하여 읽기 작업이 병렬로 수행되도록 허용하면서 쓰기 작업 시에만 데이터 무결성을 보장했습니다.&lt;/p&gt;
&lt;h3 id="2-채널channel-기반-메시지-전달"&gt;2. 채널(Channel) 기반 메시지 전달
&lt;/h3&gt;&lt;p&gt;공유 메모리 상태를 직접 제어하는 대신, &lt;code&gt;tokio::sync::mpsc&lt;/code&gt; 채널을 통해 메시지를 전달하는 방식(Actor 모델)을 채택했습니다. 이는 각 에이전트가 자신의 상태를 독점적으로 관리하게 하여 데이터 경합을 근본적으로 차단합니다.&lt;/p&gt;
&lt;h2 id="실전-코드-예제"&gt;실전 코드 예제
&lt;/h2&gt;&lt;p&gt;다음은 ZeroClaw의 통신 계층에서 사용하는 간단한 에이전트 메시지 핸들러의 구현 예제입니다.&lt;/p&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::sync::{mpsc, RwLock};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::sync::Arc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::time::Duration;
&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:#75715e"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentCommand&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ProcessTask(String),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UpdateStatus(String),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Shutdown,
&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;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentState&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; processed_tasks: &lt;span style="color:#66d9ef"&gt;u64&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;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentExecutor&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state: &lt;span style="color:#a6e22e"&gt;Arc&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;RwLock&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentState&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; receiver: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentCommand&lt;span style="color:#f92672"&gt;&amp;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 style="color:#66d9ef"&gt;impl&lt;/span&gt; AgentExecutor {
&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;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;(id: String, receiver: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentCommand&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Self {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state: &lt;span style="color:#a6e22e"&gt;Arc&lt;/span&gt;::new(RwLock::new(AgentState {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status: &lt;span style="color:#e6db74"&gt;&amp;#34;Initialized&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; processed_tasks: &lt;span style="color:#ae81ff"&gt;0&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; receiver,
&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 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;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Agent &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; started.&amp;#34;&lt;/span&gt;, self.state.read().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;.id);
&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;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Some(cmd) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.receiver.recv().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; cmd {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AgentCommand::ProcessTask(task_id) &lt;span style="color:#f92672"&gt;=&amp;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;// 비동기 작업 시뮬레이션 (예: LLM 추론 요청)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; task_id_clone &lt;span style="color:#f92672"&gt;=&lt;/span&gt; task_id.clone();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; state_clone &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Arc::clone(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self.state);
&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; tokio::spawn(&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;move&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tokio::time::sleep(Duration::from_millis(&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;)).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; state &lt;span style="color:#f92672"&gt;=&lt;/span&gt; state_clone.write().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state.processed_tasks &lt;span style="color:#f92672"&gt;+=&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; state.status &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Processing &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, task_id_clone);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Task &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; processed by Agent &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;. Total: &lt;/span&gt;&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; task_id_clone, state.id, state.processed_tasks);
&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; AgentCommand::UpdateStatus(new_status) &lt;span style="color:#f92672"&gt;=&amp;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;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; state &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.state.write().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state.status &lt;span style="color:#f92672"&gt;=&lt;/span&gt; new_status;
&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; AgentCommand::Shutdown &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Agent &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; shutting down...&amp;#34;&lt;/span&gt;, self.state.read().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;.id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&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;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;code&gt;tokio::select!&lt;/code&gt; 매크로를 사용하여 그레이스풀 셧다운(Graceful Shutdown)을 구현합니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[tokio::main]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 여러 에이전트를 관리하기 위한 송신자(Sender) 목록 저장
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 에이전트가 종료될 때를 대비해 Vec으로 관리
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; agent_senders &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Vec::new();
&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;// 3개의 에이전트 스폰
&lt;/span&gt;&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; i &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;..&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; (tx, rx) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mpsc::channel(&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// 버퍼 크기 100
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent_senders.push(tx);
&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;let&lt;/span&gt; executor &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AgentExecutor::new(&lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Agent-&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, i), rx);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tokio::spawn(executor.run());
&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;// 시스템 전체의 종료 신항 (Ctrl+C 등 대응)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; (shutdown_tx, &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; shutdown_rx) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mpsc::channel::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;&amp;gt;&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; 
&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;let&lt;/span&gt; task_distributor &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tokio::spawn(&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;move&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; task_counter &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;loop&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; shutdown_rx.try_recv().is_ok() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Task distributor stopping...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&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; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;agent_senders.is_empty() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; target_index &lt;span style="color:#f92672"&gt;=&lt;/span&gt; task_counter &lt;span style="color:#f92672"&gt;%&lt;/span&gt; agent_senders.len();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; task_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Task-&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, task_counter);
&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;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Err(_) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; agent_senders[target_index].send(AgentCommand::ProcessTask(task_id)).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to send task. Agent might be dead.&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; task_counter &lt;span style="color:#f92672"&gt;+=&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; tokio::time::sleep(Duration::from_millis(&lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;)).&lt;span style="color:#66d9ef"&gt;await&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 5초 후 시스템 종료 시뮬레이션
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tokio::time::sleep(Duration::from_secs(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;)).&lt;span style="color:#66d9ef"&gt;await&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;// 1. 작업 분배 종료
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; _ &lt;span style="color:#f92672"&gt;=&lt;/span&gt; shutdown_tx.send(()).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; task_distributor.&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;.unwrap();
&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;// 2. 모든 에이전트에게 종료 명령 전송
&lt;/span&gt;&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; tx &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; agent_senders {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; _ &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tx.send(AgentCommand::Shutdown).&lt;span style="color:#66d9ef"&gt;await&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; tokio::time::sleep(Duration::from_millis(&lt;span style="color:#ae81ff"&gt;500&lt;/span&gt;)).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;System shutdown complete.&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;h2 id="핵심-포인트-분석"&gt;핵심 포인트 분석
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Arc&amp;lt;RwLock&amp;lt;State&amp;gt;&amp;gt;&lt;/code&gt; 패턴:&lt;/strong&gt;
&lt;code&gt;AgentExecutor&lt;/code&gt;는 상태를 &lt;code&gt;Arc&amp;lt;RwLock&amp;gt;&lt;/code&gt;으로 감싸서 보관합니다. &lt;code&gt;tokio::spawn&lt;/code&gt;으로 생성된 비동기 태스크는 이 &lt;code&gt;Arc&lt;/code&gt;를 클론(&lt;code&gt;clone&lt;/code&gt;)하여 가져옵니다. 이때 데이터 자체가 복사되는 것이 아니라 참조 카운터만 증가하므로 매우 가볍습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MPSC 채널의 소유권 이동:&lt;/strong&gt;
&lt;code&gt;tx&lt;/code&gt; (Sender) 끝은 메인 루프가 소유하고, &lt;code&gt;rx&lt;/code&gt; (Receiver) 끝은 &lt;code&gt;AgentExecutor&lt;/code&gt;가 소유합니다. 이렇게 명확하게 소유권을 분리함으로써, 누가 메시지를 보내고 받는지 컴파일 타임에 보장할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;비동기 I/O와 락의 조화:&lt;/strong&gt;
&lt;code&gt;state.write().await&lt;/code&gt;를 사용할 때, 해당 코드는 데이터를 쓰기 위해 잠금(Lock)을 획득할 때까지 현재 태스크를 일시 중단(Yield)합니다. 이는 OS 스레드가 블로킹되는 것과 다르며, 다른 태스크가 CPU를 사용할 수 있게 하여 멀티코어 활용도를 높입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="결론"&gt;결론
&lt;/h2&gt;&lt;p&gt;Rust의 메모리 관리 메커니즘은 단순한 안전성을 넘어, 고성능 서버 아키텍처 설계의 강력한 도구가 됩니다. &lt;strong&gt;ZeroClaw&lt;/strong&gt; 프로젝트에서는 이를 통해 에이전트 간의 통신 오버헤드를 최소화하고, 예측 가능한 지연 시간(Latency)을 확보할 수 있었습니다. 특히 &lt;code&gt;tokio&lt;/code&gt; 런타임과 결합된 채널 기반 아키텍처는 수천 개의 에이전트가 상호작용하는 복잡한 시스템에서도 안정성을 유지하는 기반이 되고 있습니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이러한 에이전트 간 통신을 확장하여, 파일 기반의 영속성(Persistence)을 구현하는 아키텍처에 대해 다루겠습니다.&lt;/p&gt;
&lt;h2 id="참고-링크"&gt;참고 링크
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://example.com/zeroclaw-intro" target="_blank" rel="noopener"
 &gt;ZeroClaw 소개 - 고성능 Rust 에이전트 런타임&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tokio.rs/" target="_blank" rel="noopener"
 &gt;Tokio 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>NATS JetStream으로 멀티-LLM 분산 오케스트레이터 구축하기</title><link>https://blog.agentthread.dev/ko/post/nats-jetstream%EC%9C%BC%EB%A1%9C-%EB%A9%80%ED%8B%B0-llm-%EB%B6%84%EC%82%B0-%EC%98%A4%EC%BC%80%EC%8A%A4%ED%8A%B8%EB%A0%88%EC%9D%B4%ED%84%B0-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0/</link><pubDate>Fri, 08 May 2026 21:57:11 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/nats-jetstream%EC%9C%BC%EB%A1%9C-%EB%A9%80%ED%8B%B0-llm-%EB%B6%84%EC%82%B0-%EC%98%A4%EC%BC%80%EC%8A%A4%ED%8A%B8%EB%A0%88%EC%9D%B4%ED%84%B0-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0/</guid><description>&lt;p&gt;1편에서는 Claude, ZAI, Codex, Gemini 네 가지 AI를 같은 태스크에 동시에 돌리면서 발견한 모델별 제한 사항을 다뤘다. 이번 편은 &amp;ldquo;어떻게 그게 가능하도록 만들었나&amp;rdquo;—시스템 설계와 구현 이야기다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="시스템-개요"&gt;시스템 개요
&lt;/h2&gt;&lt;p&gt;AgentForge는 세 가지 요소로 이루어진다.&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;[태스크 발행자]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │ NATS JetStream publish
&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;[NATS 브로커] ─── af.worker.{id}.inbox
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │ JetStream consume (워커별 독립 스트림)
&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;[워커 폴러] × N (poller.py × 18개)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │ LLM CLI 실행 (claude / codex / gemini)
&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;[결과 반환] af.task.{task_id}.completed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;발행자가 NATS에 태스크를 올리면, 각 워커가 독립적으로 구독하고 있다가 자신의 inbox로 들어온 메시지를 받아 LLM CLI를 실행한다. 결과는 완료 주제로 다시 publish된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="왜-nats-jetstream인가"&gt;왜 NATS JetStream인가
&lt;/h2&gt;&lt;p&gt;메시지 브로커 선택지는 여러 개였다: Redis Streams, Kafka, RabbitMQ, NATS JetStream.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NATS JetStream을 선택한 이유:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;단일 바이너리&lt;/strong&gt; — 별도의 런타임 없이 &lt;code&gt;nats-server&lt;/code&gt; 하나로 동작한다. Kafka의 ZooKeeper나 RabbitMQ의 Erlang/OTP 의존성이 없다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;내장 영속성&lt;/strong&gt; — JetStream은 NATS 위에 올라가는 스트리밍 레이어로, 메시지를 파일시스템에 저장한다. 워커가 재시작되어도 처리 안 된 태스크가 유실되지 않는다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;NKey 기반 인증&lt;/strong&gt; — 워커별로 독립된 Ed25519 keypair를 발급할 수 있다. 한 워커가 침해되어도 다른 워커의 자격증명은 유효하다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;경량&lt;/strong&gt; — 단일 서버에서 메모리 사용량 ~30MB. 18개 워커를 연결해도 브로커 부하가 거의 없다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="핵심-pollerpy의-백엔드-어댑터"&gt;핵심: poller.py의 백엔드 어댑터
&lt;/h2&gt;&lt;p&gt;워커의 핵심은 &lt;code&gt;poller.py&lt;/code&gt;다. 이 파일 하나가 NATS 구독, LLM CLI 실행, 결과 반환을 모두 담당한다.&lt;/p&gt;
&lt;p&gt;LLM별 실행 방식이 다르기 때문에, 백엔드 어댑터 딕셔너리로 분리했다.&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;_BACKENDS: dict[str, dict] &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;claude&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;bin&amp;#34;&lt;/span&gt;: os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;CLAUDE_BIN&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;/usr/local/bin/claude&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;tools&amp;#34;&lt;/span&gt;: os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;ALLOWED_TOOLS&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Read,Edit,Write,Glob,Grep&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;model&amp;#34;&lt;/span&gt;: os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;CLAUDE_MODEL&amp;#34;&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 style="color:#e6db74"&gt;&amp;#34;codex&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;bin&amp;#34;&lt;/span&gt;: os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;CODEX_BIN&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;/usr/bin/codex&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;model&amp;#34;&lt;/span&gt;: os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;CODEX_MODEL&amp;#34;&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 style="color:#e6db74"&gt;&amp;#34;sandbox&amp;#34;&lt;/span&gt;: os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;CODEX_SANDBOX&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;read-only&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:#e6db74"&gt;&amp;#34;gemini_cli&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;bin&amp;#34;&lt;/span&gt;: os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;GEMINI_BIN&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;/usr/bin/gemini&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;model&amp;#34;&lt;/span&gt;: os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;GEMINI_MODEL&amp;#34;&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;MODEL_BACKEND&lt;/code&gt; 환경변수로 어떤 LLM을 쓸지 결정한다. 덕분에 동일한 &lt;code&gt;poller.py&lt;/code&gt; 코드로 18개 워커가 각자 다른 LLM을 실행한다.&lt;/p&gt;
&lt;h3 id="claude-백엔드"&gt;Claude 백엔드
&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;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run_claude&lt;/span&gt;(instructions: str, task_id: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; tuple[int, str]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _BACKENDS[&lt;span style="color:#e6db74"&gt;&amp;#34;claude&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cmd &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [cfg[&lt;span style="color:#e6db74"&gt;&amp;#34;bin&amp;#34;&lt;/span&gt;], &lt;span style="color:#e6db74"&gt;&amp;#34;--print&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;--allowedTools&amp;#34;&lt;/span&gt;, cfg[&lt;span style="color:#e6db74"&gt;&amp;#34;tools&amp;#34;&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; cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;model&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cmd &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;--model&amp;#34;&lt;/span&gt;, cfg[&lt;span style="color:#e6db74"&gt;&amp;#34;model&amp;#34;&lt;/span&gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proc &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;create_subprocess_exec(&lt;span style="color:#f92672"&gt;*&lt;/span&gt;cmd, stdin&lt;span style="color:#f92672"&gt;=&lt;/span&gt;PIPE, stdout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;PIPE, stderr&lt;span style="color:#f92672"&gt;=&lt;/span&gt;PIPE)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--print&lt;/code&gt; 플래그가 핵심이다. Claude Code가 대화 모드가 아닌 비대화형 모드로 실행되어 stdout으로 결과를 반환하게 만든다.&lt;/p&gt;
&lt;h3 id="zai-백엔드"&gt;ZAI 백엔드
&lt;/h3&gt;&lt;p&gt;ZAI는 Anthropic 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-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /etc/agentforge/cc-zai-high-dev-01.env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;lt;ZAI endpoint&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;lt;ZAI API key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;systemd &lt;code&gt;EnvironmentFile=&lt;/code&gt; 지시어로 이 파일을 주입하면, claude 바이너리가 ZAI 엔드포인트로 요청을 보낸다. 코드 변경 없이 환경변수만으로 다른 LLM 공급자를 연결하는 셈이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="선언적-관리-fleetyaml--serversyaml"&gt;선언적 관리: fleet.yaml × servers.yaml
&lt;/h2&gt;&lt;p&gt;18개 워커를 수동으로 관리하는 건 비현실적이다. 두 개의 YAML 파일로 전체 인프라를 선언적으로 정의했다.&lt;/p&gt;
&lt;h3 id="serversyaml--서버-인벤토리"&gt;servers.yaml — 서버 인벤토리
&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-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;servers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker-node-1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker-host&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;services&lt;/span&gt;: [&lt;span style="color:#ae81ff"&gt;agentforge-worker, tunnel-arm1]&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;broker-host&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;broker-host&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;services&lt;/span&gt;: [&lt;span style="color:#ae81ff"&gt;nats-jetstream, postgres]&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker-node-2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker-host&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;services&lt;/span&gt;: [&lt;span style="color:#ae81ff"&gt;agentforge-worker, tunnel-arm1]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="fleetyaml--워커-배치"&gt;fleet.yaml — 워커 배치
&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-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;workers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;worker_id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cc-go-dev-01&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;llm&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;claude-code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;model&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;claude-sonnet-4-6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;lang&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;go&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;developer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;host&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker-node-1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&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;create_pr&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;worker_id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;codex-py-dev-01&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;llm&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;codex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;model&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gpt-5.5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;lang&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;python&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;developer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;host&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker-node-1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&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;create_pr&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;p&gt;&lt;code&gt;host&lt;/code&gt; 필드 하나를 바꾸면 워커가 다른 서버로 이동한다. &lt;code&gt;enabled: false&lt;/code&gt;로 설정하면 배포 스크립트가 해당 워커를 중지한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="워커-템플릿-시스템-provision_workerpy"&gt;워커 템플릿 시스템: provision_worker.py
&lt;/h2&gt;&lt;p&gt;워커를 새로 추가할 때마다 systemd 유닛 파일을 직접 작성하는 건 오류가 생기기 쉽다. Jinja2 템플릿 + 프로비저닝 스크립트로 자동화했다.&lt;/p&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-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;templates/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; systemd/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; claude.service.j2 # claude-code, ZAI 공용
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; codex.service.j2 # OpenAI Codex
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gemini.service.j2 # Google Gemini CLI
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;claude.service.j2&lt;/code&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-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;MODEL_BACKEND&lt;span style="color:#f92672"&gt;=&lt;/span&gt;claude
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;CLAUDE_BIN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{{ claude_bin }}
&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:#66d9ef"&gt;if&lt;/span&gt; claude_model &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:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;CLAUDE_MODEL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{{ claude_model }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&lt;span style="color:#f92672"&gt;%&lt;/span&gt; endif &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:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; env_file &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;EnvironmentFile&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{{ env_file }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&lt;span style="color:#f92672"&gt;%&lt;/span&gt; endif &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:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;WORK_BASE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{{ work_base }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;WORK_DIR&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{{ work_base }}&lt;span style="color:#f92672"&gt;/&lt;/span&gt;repo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;{{ &amp;#39;ALLOWED_TOOLS=&amp;#39; + allowed_tools }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;CREATE_PR&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{{ &lt;span style="color:#e6db74"&gt;&amp;#39;true&amp;#39;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; create_pr &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;false&amp;#39;&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:#66d9ef"&gt;if&lt;/span&gt; create_pr &lt;span style="color:#f92672"&gt;and&lt;/span&gt; github_remote &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:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;GITHUB_REMOTE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{{ github_remote }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&lt;span style="color:#f92672"&gt;%&lt;/span&gt; endif &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;ZAI 워커는 &lt;code&gt;env_file&lt;/code&gt; 블록이 활성화되어 EnvironmentFile이 추가된다. PR 생성 워커는 &lt;code&gt;github_remote&lt;/code&gt;가 주입된다. 나머지는 기본값을 쓴다.&lt;/p&gt;
&lt;h3 id="provision_workerpy-사용법"&gt;provision_worker.py 사용법
&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;# 미리보기 (실제 배포 없음)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python3 scripts/provision_worker.py --worker new-worker-id --dry-run
&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;# 실제 배포 (NATS creds 발급 포함)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python3 scripts/provision_worker.py --worker new-worker-id --issue-creds
&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;# fleet.yaml 전체 일괄 배포&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python3 scripts/provision_worker.py --all
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;내부적으로 수행하는 작업:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;fleet.yaml&lt;/code&gt;에서 워커 항목 읽기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;servers.yaml&lt;/code&gt;에서 대상 호스트 읽기&lt;/li&gt;
&lt;li&gt;Jinja2 템플릿 렌더링&lt;/li&gt;
&lt;li&gt;SSH로 &lt;code&gt;/etc/systemd/system/{worker_id}-poller.service&lt;/code&gt; 배포&lt;/li&gt;
&lt;li&gt;워크 디렉터리 생성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;systemctl daemon-reload &amp;amp;&amp;amp; enable --now&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;(선택) &lt;code&gt;nsc add user&lt;/code&gt;로 NATS NKey 발급 → creds 배포 → &lt;code&gt;auth.conf&lt;/code&gt; 재생성&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="분산-호스트-두-번째-서버에-워커-추가"&gt;분산 호스트: 두 번째 서버에 워커 추가
&lt;/h2&gt;&lt;p&gt;모든 워커를 한 서버에서 돌리면 단일 장애점이 된다. 두 번째 호스트에 Claude 워커를 추가했다.&lt;/p&gt;
&lt;p&gt;두 번째 호스트에서 NATS 브로커에 연결하는 방법은 autossh 터널이다.&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-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Description&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;NATS 브로커 터널&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;After&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;network-online.target&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;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ExecStart&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/usr/bin/autossh -N \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -L 4222:127.0.0.1:4222 \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -i /home/ubuntu/.ssh/id_ed25519 \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; broker-host&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Restart&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;RestartSec&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이 설정이 활성화된 상태에서 워커는 항상 &lt;code&gt;nats://127.0.0.1:4222&lt;/code&gt;로 연결한다. 브로커 호스트 주소를 몰라도 된다. 터널만 살아있으면 어느 호스트에서든 동일하게 동작한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="nats-자격증명-운영-경험"&gt;NATS 자격증명 운영 경험
&lt;/h2&gt;&lt;p&gt;구현 중 가장 복잡했던 부분은 NATS NKey 관리다.&lt;/p&gt;
&lt;p&gt;NATS JetStream의 인증 구조는 계층적이다.&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;Operator (최상위 서명 기관)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── Account: SYS (시스템 계정)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── Account: Services (워커 계정)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── User: cc-dev-01
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── User: cc-go-dev-01
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── User: codex-py-dev-01
&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;각 워커는 독립된 User NKey를 가지고, Services 계정의 권한 범위(&lt;code&gt;af.&amp;gt;&lt;/code&gt;, &lt;code&gt;_INBOX.&amp;gt;&lt;/code&gt;, &lt;code&gt;$JS.&amp;gt;&lt;/code&gt;) 내에서만 publish/subscribe할 수 있다.&lt;/p&gt;
&lt;p&gt;신규 워커를 추가할 때 Operator의 signing key가 필요하다. 초기에 이 키의 백업을 만들지 않았다가 분실하는 사고가 있었다. 결과적으로 Operator를 전부 재생성하고 모든 워커의 creds를 일괄 교체했다. 서비스 다운타임은 약 60초.&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;&lt;span style="color:#75715e"&gt;# 재생성 절차&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nsc add operator AgentForge
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nsc add account SYS
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nsc add account Services
&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; worker in cc-dev-01 cc-go-dev-01 ...; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nsc add user --account Services --name $worker &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --allow-pub &lt;span style="color:#e6db74"&gt;&amp;#34;af.&amp;gt;,_INBOX.&amp;gt;,&lt;/span&gt;$JS&lt;span style="color:#e6db74"&gt;.&amp;gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --allow-sub &lt;span style="color:#e6db74"&gt;&amp;#34;af.&amp;gt;,_INBOX.&amp;gt;,&lt;/span&gt;$JS&lt;span style="color:#e6db74"&gt;.&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nsc generate config --mem-resolver --sys-account SYS &amp;gt; auth.new.conf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="새-워커-추가-전체-절차"&gt;새 워커 추가: 전체 절차
&lt;/h2&gt;&lt;p&gt;이 시스템이 완성된 이후 새 워커를 추가하는 절차는 단순하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1단계&lt;/strong&gt;: &lt;code&gt;fleet.yaml&lt;/code&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-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;worker_id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-new-worker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;llm&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;claude-code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;model&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;claude-haiku-4-5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;lang&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;multi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;developer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;host&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker-node-1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&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;create_pr&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;p&gt;&lt;strong&gt;2단계&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;python3 scripts/provision_worker.py --worker my-new-worker --dry-run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;3단계&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;python3 scripts/provision_worker.py --worker my-new-worker --issue-creds
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;끝이다. 템플릿 렌더링, SSH 배포, NATS 자격증명 발급, 서비스 등록까지 한 명령으로 처리된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="다음-단계"&gt;다음 단계
&lt;/h2&gt;&lt;p&gt;현재 시스템은 워커가 태스크를 독립적으로 처리하는 구조다. 앞으로 만들고 싶은 것:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;라우팅 정책&lt;/strong&gt;: 태스크 특성에 따라 적합한 워커를 자동 선택 (Go 코드 → claude-go-dev, 비용 최우선 → ZAI 경량 티어)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과 비교 대시보드&lt;/strong&gt;: fan-out 결과를 나란히 보여주는 UI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비용 추적&lt;/strong&gt;: 워커별 API 호출 비용 집계&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;코드는 GitHub에 공개되어 있다.&lt;/p&gt;</description></item><item><title>에이전트 개발의 다음 단계: 제어 흐름(Control Flow) 도입하기</title><link>https://blog.agentthread.dev/ko/post/%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9D%98-%EB%8B%A4%EC%9D%8C-%EB%8B%A8%EA%B3%84-%EC%A0%9C%EC%96%B4-%ED%9D%90%EB%A6%84control-flow-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0/</link><pubDate>Fri, 08 May 2026 09:01:23 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9D%98-%EB%8B%A4%EC%9D%8C-%EB%8B%A8%EA%B3%84-%EC%A0%9C%EC%96%B4-%ED%9D%90%EB%A6%84control-flow-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0/</guid><description>&lt;h1 id="에이전트-개발의-다음-단계-제어-흐름control-flow-도입하기"&gt;에이전트 개발의 다음 단계: 제어 흐름(Control Flow) 도입하기
&lt;/h1&gt;&lt;p&gt;최근 &amp;lsquo;Agents need control flow, not more prompts&amp;rsquo;라는 글을 보며 깊이 공감했습니다. 초기 LLM 기반 에이전트를 개발할 때는 프롬프트 엔지니어링이 곧 개발의 전부인 것처럼 느껴졌습니다. 하지만 시스템이 복잡해지고 ZeroClaw와 같은 멀티 에이전트 런타임을 다루게 되면서, &lt;strong&gt;&amp;lsquo;프롬프트&amp;rsquo;가 아닌 &amp;lsquo;구조&amp;rsquo;가 핵심&lt;/strong&gt;임을 절실히 깨닫게 되었습니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 복잡한 프롬프트를 덜어내고, 확장 가능한 에이전트 시스템을 위해 **제어 흐름(Control Flow)**을 아키텍처에 도입하는 방법을 실제 코드 예제와 함께 정리해 보겠습니다.&lt;/p&gt;
&lt;h2 id="문제-정의-프롬프트-한계와-의존성-지옥"&gt;문제 정의: 프롬프트 한계와 &amp;lsquo;의존성 지옥&amp;rsquo;
&lt;/h2&gt;&lt;p&gt;단일 에이전트에게 &amp;ldquo;A 작업을 하고, 결과가 양수면 B를, 음수면 C를 실행한 뒤, 마지막으로 D를 요약해&amp;quot;라고 프롬프트로 지시하는 것은 쉽지 않습니다. LLM은 문맥을 잃어버리거나, 논리적 분기(if/else)를 무시하고 직행해버리는 경우가 빈번하기 때문입니다.&lt;/p&gt;
&lt;p&gt;특히 [ZeroClaw] 멀티 에이전트 아키텍처 설계안에서 논의된 것처럼, 여러 에이전트가 협력하는 환경에서는 프롬프트만으로는 다음과 같은 문제가 발생합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;비결정성(Non-determinism):&lt;/strong&gt; 같은 입력이라도 LLM이 다른 경로를 선택할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;디버깅 난이도:&lt;/strong&gt; 에러가 발생했을 때 프롬프트의 어느 부분이 잘못된 지 찾기 어렵습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장성 부족:&lt;/strong&gt; 새로운 단계를 추가할 때마다 기존의 거대한 프롬프트를 수정해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="해결책-그래프-기반-제어-흐름-graph-based-control-flow"&gt;해결책: 그래프 기반 제어 흐름 (Graph-based Control Flow)
&lt;/h2&gt;&lt;p&gt;해결책은 LLM에게 &amp;ldquo;무엇(What)&amp;ldquo;을 할지 결정하게 하고, &amp;ldquo;어떻게(How)&amp;rdquo; 실행할지는 외부의 상태 머신(State Machine)이나 그래프 워크플로우에 위임하는 것입니다. 이를 통해 우리는 에이전트의 사고 자체를 제어하는 것이 아니라, 에이전트 간의 &lt;strong&gt;데이터 흐름과 실행 순서&lt;/strong&gt;를 제어하게 됩니다.&lt;/p&gt;
&lt;p&gt;Rust와 같은 안정적인 언어로 이러한 런타임을 구성하면 [ZeroClaw]에서 목표로 하는 고성능 및 안정성을 확보할 수 있습니다.&lt;/p&gt;
&lt;h3 id="구현-예제-rust로-간단한-상태-머신-러너-만들기"&gt;구현 예제: Rust로 간단한 상태 머신 러너 만들기
&lt;/h3&gt;&lt;p&gt;복잡한 프레임워크 없이, Rust의 Enum을 활용해 간단한 에이전트 워크플로우를 구현해 보겠습니다. 이 코드는 에이전트가 수행해야 할 단계(Step)를 정의하고, 이전 단계의 결과에 따라 다음 단계를 결정하는 구조입니다.&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 1. 에이전트의 상태와 실행 컨텍스트 정의
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Debug, Clone)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentContext&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; current_data: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; step_count: &lt;span style="color:#66d9ef"&gt;usize&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;// 2. 워크플로우의 각 단계(Step) 정의
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;WorkflowStep&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Initialize,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ProcessData,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateResult, &lt;span style="color:#75715e"&gt;// 결과에 따라 다음 단계가 결정됨
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Finalize,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Complete,
&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;// 3. 각 단계별 실행 로직 구제화
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; WorkflowStep {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;execute&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, ctx: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;mut&lt;/span&gt; AgentContext) -&amp;gt; &lt;span style="color:#a6e22e"&gt;WorkflowStep&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; self {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; WorkflowStep::Initialize &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[Step 1] 데이터 초기화 중...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ctx.current_data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Sample Input Data&amp;#34;&lt;/span&gt;.to_string();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ctx.step_count &lt;span style="color:#f92672"&gt;+=&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; WorkflowStep::ProcessData
&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; WorkflowStep::ProcessData &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[Step 2] LLM을 통해 데이터 처리 중: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, ctx.current_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 실제로는 여기서 MCP 클라이언트 등을 호출하여 LLM 추론을 수행
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ctx.current_data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Processed: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, ctx.current_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ctx.step_count &lt;span style="color:#f92672"&gt;+=&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; WorkflowStep::ValidateResult
&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; WorkflowStep::ValidateResult &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[Step 3] 결과 검증 중...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 비즈니스 로직: 예를 들어 결과 길이가 10보다 작으면 재시작(혹은 에러 처리)
&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; ctx.current_data.len() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&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; WorkflowStep::Initialize &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;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&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; WorkflowStep::Finalize
&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; WorkflowStep::Finalize &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[Step 4] 최종 결과 저장: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, ctx.current_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; WorkflowStep::Complete
&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; WorkflowStep::Complete &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&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; WorkflowStep::Complete &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&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;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 4. 메인 실행 루프 (Runner)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run_agent_workflow&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; ctx &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AgentContext {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; current_data: String::new(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; step_count: &lt;span style="color:#ae81ff"&gt;0&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:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; current_step &lt;span style="color:#f92672"&gt;=&lt;/span&gt; WorkflowStep::Initialize;
&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;// 최대 10회 반복 방지 (Safety Breaker)
&lt;/span&gt;&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; _ &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;..&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&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; &lt;span style="color:#a6e22e"&gt;matches!&lt;/span&gt;(current_step, WorkflowStep::Complete) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&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; current_step &lt;span style="color:#f92672"&gt;=&lt;/span&gt; current_step.execute(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; ctx);
&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 style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; run_agent_workflow();
&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;h2 id="코드를-통한-얻는-이점"&gt;코드를 통한 얻는 이점
&lt;/h2&gt;&lt;p&gt;위의 예제처럼 단순히 &lt;code&gt;match&lt;/code&gt; 문과 &lt;code&gt;Enum&lt;/code&gt;을 사용하더라도 얻을 수 있는 효과는 큽니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;명확한 흐름 시각화:&lt;/strong&gt; 코드만 보고도 시스템이 어떻게 흘러가는지 파악할 수 있습니다. [Discord MCP Gateway 아키텍처]처럼 복잡한 요청을 처리할 때 유지보수성이 크게 향상됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 관리:&lt;/strong&gt; &lt;code&gt;AgentContext&lt;/code&gt; 구조체를 통해 메모리 상태를 명시적으로 관리합니다. LLM의 컨텍스트 윈도우가 터지더라도 Rust 런타임은 현재 단계를 정확히 알고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에러 핸들링:&lt;/strong&gt; 특정 단계에서 실패했을 때, 프롬프트 재시도가 아니라 코드 레벨에서 &lt;code&gt;Retry&lt;/code&gt; 로직이나 &lt;code&gt;Fallback&lt;/code&gt; 단계로 쉽게 넘어갈 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="결론-에이전트는-소프트웨어다"&gt;결론: 에이전트는 소프트웨어다
&lt;/h2&gt;&lt;p&gt;우리는 LLM을 &amp;lsquo;마법&amp;rsquo;처럼 다루는 시대를 지나, LLM을 &amp;lsquo;하나의 모듈&amp;rsquo;로 바라보는 시대로 넘어가고 있습니다. [Cloud Monitor]나 [LLM 설정 개선] 프로젝트에서 겪었던 시행착오들은 결국 견고한 소프트웨어 아키텍처 위에 AI를 얹어야 한다는 것을 보여줍니다.&lt;/p&gt;
&lt;p&gt;프롬프트를 더 길게 작성하는 대신, Rust나 Python으로 제어 흐름을 명확히 정의하는 런타임을 구축해 보세요. 에이전트의 행동이 예측 가능해지고, 시스템이 튼튼해지는 것을 느끼실 수 있을 것입니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이러한 제어 흐름 위에서 실제로 여러 LLM 호출을 병렬로 처리하여 성능을 극대화하는 방법에 대해 다루어보겠습니다.&lt;/p&gt;</description></item><item><title>Redis Array의 진화: 대규모 데이터 처리를 위한 아키텍처 분석</title><link>https://blog.agentthread.dev/ko/post/redis-array%EC%9D%98-%EC%A7%84%ED%99%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B6%84%EC%84%9D/</link><pubDate>Tue, 05 May 2026 09:00:52 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/redis-array%EC%9D%98-%EC%A7%84%ED%99%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B6%84%EC%84%9D/</guid><description>&lt;h1 id="redis-array의-진화-대규모-데이터-처리를-위한-아키텍처-분석"&gt;Redis Array의 진화: 대규모 데이터 처리를 위한 아키텍처 분석
&lt;/h1&gt;&lt;p&gt;안녕하세요! 최근 Hacker News를 통해 흥미로운 기사 하나를 접하게 되었습니다. 바로 Redis의 핵심 개발자 중 한 명인 Oran Agra가 작성한 **&amp;ldquo;Redis array: short story of a long development process&amp;rdquo;**입니다. 단순히 기능 하나가 추가된 이야기가 아니었습니다. 이는 25년 된 레거시 코드를 건드리면서 성능을 유지하고, 안정성을 확보하며, 거대한 코드베이스를 밤새 포맷팅했던 개발자들의 집념의 기록이었습니다.&lt;/p&gt;
&lt;p&gt;오늘은 이 기사를 바탕으로, Redis 내부에서 Array(배열) 자료구조가 어떻게 진화해왔는지, 그리고 우리가 대규모 시스템을 설계할 때 배울 수 있는 교훈은 무엇인지 깊이 있게 분석해보겠습니다.&lt;/p&gt;
&lt;h2 id="1-문제-제기-25년-된-레거시-코드의-굴레"&gt;1. 문제 제기: 25년 된 레거시 코드의 굴레
&lt;/h2&gt;&lt;p&gt;Redis의 &lt;code&gt;LIST&lt;/code&gt; 자료구조는 내부적으로 &lt;code&gt;QuickList&lt;/code&gt;를 사용합니다. &lt;code&gt;QuickList&lt;/code&gt;는 양방향 연결 리스트인 &lt;code&gt;ziplist&lt;/code&gt;와 &lt;code&gt;linkedlist&lt;/code&gt;의 장점을 결합한 구조입니다. 하지만 수천만 개의 요소를 가진 거대한 리스트를 다룰 때, 메모리 파편화(memory fragmentation)와 캐시 미스(cache miss)가 심각한 성능 저하를 일으키는 문제가 있었습니다.&lt;/p&gt;
&lt;p&gt;특히, 배열(Array) 타입의 데이터를 처리할 때 기존의 구조는 다음과 같은 병목이 있었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;메모리 오버헤드:&lt;/strong&gt; 포인터 연결로 인한 추가 메모리 사용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;순차 접근 비용:&lt;/strong&gt; 캐시 라인을 효율적으로 사용하지 못해 발생하는 지연.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;개발팀은 이를 해결하기 위해 C 언어 수준에서 내부 구조를 뜯어고치기로 결심합니다. 여기서 가장 큰 난관은 바로 **&amp;ldquo;변경하지 않으면 안 되는 레거시 코드&amp;rdquo;**였습니다.&lt;/p&gt;
&lt;h2 id="2-해결-과정-formatting-a-25m-line-codebase"&gt;2. 해결 과정: Formatting a 25M-line Codebase
&lt;/h2&gt;&lt;p&gt;기사에서 가장 인상 깊었던 부분은 **&amp;ldquo;Formatting a 25M-line codebase overnight&amp;rdquo;**입니다. 2,500만 라인에 달하는 코드를 포맷팅하고 리팩토링하는 과정은 단순한 기술적 도전을 넘어 체스와 같은 전략이 필요했습니다.&lt;/p&gt;
&lt;h3 id="21-리팩토링을-위한-사전-준비"&gt;2.1. 리팩토링을 위한 사전 준비
&lt;/h3&gt;&lt;p&gt;대규모 리팩토링 시 가장 두려운 것은 **&amp;ldquo;회귀(Regression)&amp;rdquo;**입니다. 배열 구조를 변경하는 과정에서 수백 개의 Redis 명령어(&lt;code&gt;LPUSH&lt;/code&gt;, &lt;code&gt;RPUSH&lt;/code&gt;, &lt;code&gt;LINDEX&lt;/code&gt; 등)가 영향을 받을 수 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해 팀은 다음과 같은 접근 방식을 취했습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;테스트 커버리지 확대:&lt;/strong&gt; 기존 명령어에 대한 단위 테스트(Unit Test)를 통과하는지 확인.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD 파이프라인 강화:&lt;/strong&gt; 코드 변경 시 즉시 성능 저하가 발생하는지 감시하는 벤치마킹 스크립트 배치.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="22-redis-array의-새로운-구조"&gt;2.2. Redis Array의 새로운 구조
&lt;/h3&gt;&lt;p&gt;개선된 Array 구조는 단순히 메모리를 할당하는 방식에서 벗어나, 데이터 지역성(Locality)을 극대화하는 방향으로 변경되었습니다. 핵심은 **&amp;ldquo;연속된 메모리 블록을 최대한 활용하되, 필요시 분할하여 관리한다&amp;rdquo;**는 것입니다.&lt;/p&gt;
&lt;p&gt;이를 통해 다음과 같은 이점을 얻었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU 캐시 히트율 향상:&lt;/strong&gt; 연속된 메모리 접근으로 인해 L1/L2 캐시 적중률이 크게 향상되었습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메모리 절약:&lt;/strong&gt; 불필요한 포인터 연결을 줄여 실제 데이터 저장 공간을 확보했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-실전-가이드-redis에서-효율적인-배열-사용하기"&gt;3. 실전 가이드: Redis에서 효율적인 배열 사용하기
&lt;/h2&gt;&lt;p&gt;이론적인 배경은 충분하니, 이제 실제로 어떻게 적용할 수 있는지 코드로 살펴보겠습니다.&lt;/p&gt;
&lt;h3 id="31-기존-리스트-사용의-문제점"&gt;3.1. 기존 리스트 사용의 문제점
&lt;/h3&gt;&lt;p&gt;먼저, 수천만 개의 아이템을 리스트에 넣는 기존 방식을 생각해봅시다. 이는 &lt;code&gt;QuickList&lt;/code&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;&lt;span style="color:#75715e"&gt;# 기존 방식 (QuickList based)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 10,000,000개의 아이템 추가 (메모리 및 속도 저하 발생 가능)&lt;/span&gt;
&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; i in &lt;span style="color:#f92672"&gt;{&lt;/span&gt;1..10000000&lt;span style="color:#f92672"&gt;}&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; redis-cli LPUSH my_huge_list &lt;span style="color:#e6db74"&gt;&amp;#34;item:&lt;/span&gt;$i&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 style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="32-stream과-hash를-활용한-최적화"&gt;3.2. Stream과 Hash를 활용한 최적화
&lt;/h3&gt;&lt;p&gt;Redis Array의 내부 개선은 사용자에게 투명하게 적용되지만, 우리가 설계를 할 때는 **&amp;ldquo;데이터의 크기&amp;rdquo;**와 **&amp;ldquo;접근 패턴&amp;rdquo;**을 고려해야 합니다. 단순히 순서대로 저장만 하면 된다면 최신 버전의 Redis를 쓰는 것만으로도 이득을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 만약 배열 안의 데이터를 검색하거나 수정해야 한다면 &lt;code&gt;LIST&lt;/code&gt; 대신 &lt;code&gt;HASH&lt;/code&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:#f92672"&gt;import&lt;/span&gt; redis
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; time
&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;r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; redis&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Redis(host&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;, port&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;6379&lt;/span&gt;, db&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&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:#75715e"&gt;# 1. List 사용 (순차 보관용)&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;push_to_list&lt;/span&gt;(count):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; start &lt;span style="color:#f92672"&gt;=&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time()
&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; i &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(count):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; r&lt;span style="color:#f92672"&gt;.&lt;/span&gt;lpush(&lt;span style="color:#e6db74"&gt;&amp;#34;logs:timeline&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;log_entry_&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;i&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; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;List pushed &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;count&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; items in &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;-&lt;/span&gt; start&lt;span style="color:#e6db74"&gt;:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;.4f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;s&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;# 2. Hash 사용 (검색 및 수정용)&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;push_to_hash&lt;/span&gt;(count):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; start &lt;span style="color:#f92672"&gt;=&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pipe &lt;span style="color:#f92672"&gt;=&lt;/span&gt; r&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pipeline()
&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; i &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(count):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pipe&lt;span style="color:#f92672"&gt;.&lt;/span&gt;hset(&lt;span style="color:#e6db74"&gt;&amp;#34;logs:details&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;entry_&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;i&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;log_content_&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;i&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; pipe&lt;span style="color:#f92672"&gt;.&lt;/span&gt;execute()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Hash pushed &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;count&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; items in &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;-&lt;/span&gt; start&lt;span style="color:#e6db74"&gt;:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;.4f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;s&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;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 10만 개 데이터 삽입 테스트&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; push_to_list(&lt;span style="color:#ae81ff"&gt;100000&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; push_to_hash(&lt;span style="color:#ae81ff"&gt;100000&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;
최신 Redis 버전(7.x 이상)에서는 내부적으로 Array 구조가 최적화되어 있어 &lt;code&gt;LPUSH&lt;/code&gt; 속도가 매우 빠릅니다. 하지만 특정 인덱스의 데이터를 자주 조회해야 한다면 &lt;code&gt;LINDEX&lt;/code&gt;는 O(N)의 복잡도를 가지므로, &lt;code&gt;HGET&lt;/code&gt;을 쓰는 O(1) 방식이 훨씬 유리합니다.&lt;/p&gt;
&lt;h2 id="4-결론-개발-문화와-기술의-조화"&gt;4. 결론: 개발 문화와 기술의 조화
&lt;/h2&gt;&lt;p&gt;Redis Array의 개발 과정은 우리에게 중요한 교훈을 줍니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;성능은 무료가 아니다:&lt;/strong&gt; 25년 된 코드를 개선하기 위해서는 그에 상응하는 리팩토링과 테스트 비용이 따른다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도구의 투자:&lt;/strong&gt; 2,500만 라인의 코드를 포맷팅할 수 있는 자동화 도구와 CI/CD 환경이 있었기에 가능한 작업이었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;우리가 시스템을 설계할 때, 단순히 &amp;ldquo;빠르다&amp;quot;는 것만 넘어서 &amp;ldquo;어떻게 유지보수 가능한 성능을 낼 것인가&amp;quot;를 고민해야 합니다. Redis 팀이 보여준 것처럼, 때로는 아키텍처의 근간을 흔드는 대규모 개선을 두려워하지 말아야 할 때입니다.&lt;/p&gt;
&lt;h2 id="5-참고-자료"&gt;5. 참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://news.ycombinator.com/item?id=41284521" target="_blank" rel="noopener"
 &gt;Formatting a 25M-line codebase overnight (Hacker News)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://redis.io/docs/data-types/lists/" target="_blank" rel="noopener"
 &gt;Redis Internals: QuickList&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;감사합니다!&lt;/p&gt;</description></item><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 Decision MCP] 아키텍처 설계문서</title><link>https://blog.agentthread.dev/ko/post/discord-decision-mcp-architecture-design-v2/</link><pubDate>Mon, 02 Mar 2026 21:36:30 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/discord-decision-mcp-architecture-design-v2/</guid><description>&lt;h1 id="discord-decision-mcp--아키텍처-설계문서"&gt;Discord Decision MCP — 아키텍처 설계문서
&lt;/h1&gt;&lt;h2 id="1-프로젝트-개요"&gt;1. 프로젝트 개요
&lt;/h2&gt;&lt;h3 id="11-목적"&gt;1.1 목적
&lt;/h3&gt;&lt;p&gt;Claude Code가 tmux Teammate 모드로 자율 작업 중, 사용자 결정이 필요한 시점에 Discord를 통해 질문하고 응답을 받아 작업을 재개하는 MCP (Model Context Protocol) 서버입니다.&lt;/p&gt;
&lt;h3 id="12-핵심-특징"&gt;1.2 핵심 특징
&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;strong&gt;프로젝트당 Bot 1개&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;각 프로젝트는 독립된 Discord Bot 사용&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;무한 대기 기본값&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Timeout 없이 사용자 응답 대기 (Claude가 독단 진행 금지)&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;한글 선택지, Yes/No 응답 지원&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;모든 Discord 통신은 MCP Tool을 통해 이루어짐&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="13-버전-정보"&gt;1.3 버전 정보
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;버전&lt;/strong&gt;: 1.0.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python 요구사항&lt;/strong&gt;: &amp;gt;= 3.11&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP 프레임워크&lt;/strong&gt;: FastMCP &amp;gt;= 0.1.0&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="2-디렉토리-구조"&gt;2. 디렉토리 구조
&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-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;discord&lt;span style="color:#f92672"&gt;-&lt;/span&gt;decision&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:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; discord_mcp&lt;span style="color:#f92672"&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:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; __init__&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; server&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# MCP 서버 진입점&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; config&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &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:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; bot&lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Discord API 클라이언트&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; __init__&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# REST API 클라이언트 (httpx)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;└──&lt;/span&gt; gateway&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# WebSocket 게이트웨이 (수신 전용)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; decision&lt;span style="color:#f92672"&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:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; __init__&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; manager&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &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:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; poller&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# Discord Polling 및 응답 대기&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; parser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &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:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;└──&lt;/span&gt; state&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# 상태 영속화 (JSON 파일)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; tools&lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#75715e"&gt;# MCP Tools 구현&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; __init__&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; ask&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# discord_ask_decision&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; notify&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# discord_notify&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; report&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# discord_report_progress&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; status&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# discord_check_pending&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; inbox&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# discord_read_inbox, discord_clear_inbox&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;└──&lt;/span&gt; _templates&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# Discord 메시지 템플릿&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;└──&lt;/span&gt; daemon&lt;span style="color:#f92672"&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:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; __init__&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; watcher&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# Discord 채널 감시 (discord-watch CLI)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;└──&lt;/span&gt; inbox&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py &lt;span style="color:#75715e"&gt;# Inbox 파일 관리&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; scripts&lt;span style="color:#f92672"&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:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;└──&lt;/span&gt; start&lt;span style="color:#f92672"&gt;-&lt;/span&gt;discord&lt;span style="color:#f92672"&gt;-&lt;/span&gt;watch&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sh
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; tests&lt;span style="color:#f92672"&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:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; __init__&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; test_parser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;└──&lt;/span&gt; test_state&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;│&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; CLAUDE&lt;span style="color:#f92672"&gt;.&lt;/span&gt;md &lt;span style="color:#75715e"&gt;# Claude Code 프로젝트 지침&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; README&lt;span style="color:#f92672"&gt;.&lt;/span&gt;md &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:#960050;background-color:#1e0010"&gt;├──&lt;/span&gt; pyproject&lt;span style="color:#f92672"&gt;.&lt;/span&gt;toml &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:#960050;background-color:#1e0010"&gt;└──&lt;/span&gt; mcp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json&lt;span style="color:#f92672"&gt;.&lt;/span&gt;example &lt;span style="color:#75715e"&gt;# MCP 설정 예시&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="3-주요-모듈-설명"&gt;3. 주요 모듈 설명
&lt;/h2&gt;&lt;h3 id="31-서버-진입점-serverpy"&gt;3.1 서버 진입점 (&lt;code&gt;server.py&lt;/code&gt;)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;: FastMCP 서버 생성 및 MCP Tool 등록&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;mcp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; FastMCP(name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;discord-decision-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:#75715e"&gt;# Tool 등록&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mcp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;tool()(discord_ask_decision)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mcp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;tool()(discord_notify)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mcp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;tool()(discord_report_progress)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mcp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;tool()(discord_check_pending)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mcp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;tool()(discord_read_inbox)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mcp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;tool()(discord_clear_inbox)
&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;discord-mcp &lt;span style="color:#75715e"&gt;# 또는 python -m discord_mcp.server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="32-설정-관리-configpy"&gt;3.2 설정 관리 (&lt;code&gt;config.py&lt;/code&gt;)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&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;code&gt;DISCORD_BOT_TOKEN&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Discord Bot Token (&amp;ldquo;Bot &amp;quot; 접두사 포함)&lt;/td&gt;
					&lt;td&gt;필수&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DISCORD_CHANNEL_ID&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;기본 질문 채널 ID&lt;/td&gt;
					&lt;td&gt;필수&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;PROJECT_NAME&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;question_id 생성에 사용&lt;/td&gt;
					&lt;td&gt;&amp;ldquo;project&amp;rdquo;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;PENDING_DIR&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;상태 파일 저장 경로&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;~/.claude/pending_decisions&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;POLL_INTERVAL_SECONDS&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Discord polling 간격 (초)&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="33-discord-api-클라이언트-bot"&gt;3.3 Discord API 클라이언트 (&lt;code&gt;bot/&lt;/code&gt;)
&lt;/h3&gt;&lt;h4 id="331-rest-api-클라이언트-clientpy"&gt;3.3.1 REST API 클라이언트 (&lt;code&gt;client.py&lt;/code&gt;)
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;: Discord REST API 호출 (httpx 기반)&lt;/p&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-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;DiscordClient&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;send_message&lt;/span&gt;(channel_id, content, embeds&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_messages&lt;/span&gt;(channel_id, after&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;, limit&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;create_thread&lt;/span&gt;(channel_id, name, first_message)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;archive_thread&lt;/span&gt;(thread_id)
&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;ul&gt;
&lt;li&gt;Rate limit 자동 처리 (429 응답 시 재시도)&lt;/li&gt;
&lt;li&gt;싱글턴 패턴 (&lt;code&gt;get_client()&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="332-websocket-게이트웨이-gatewaypy"&gt;3.3.2 WebSocket 게이트웨이 (&lt;code&gt;gateway.py&lt;/code&gt;)
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;: Discord Gateway WebSocket 연결 (수신 전용)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MESSAGE_CREATE 이벤트 실시간 수신&lt;/li&gt;
&lt;li&gt;Polling 방식의 지연을 5초 → 즉시로 단축&lt;/li&gt;
&lt;li&gt;Gateway 없이도 시스템 정상 동작 (Polling fallback)&lt;/li&gt;
&lt;li&gt;자동 재연결 (Exponential backoff)&lt;/li&gt;
&lt;/ul&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;GatewayClient&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run&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;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;stop&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="34-결정-요청-관리-decision"&gt;3.4 결정 요청 관리 (&lt;code&gt;decision/&lt;/code&gt;)
&lt;/h3&gt;&lt;h4 id="341-decisionmanager-managerpy"&gt;3.4.1 DecisionManager (&lt;code&gt;manager.py&lt;/code&gt;)
&lt;/h4&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-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;DecisionManager&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ask&lt;/span&gt;(question, context, options, timeout_seconds, thread_id) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; PollResult
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;restore_pending&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; list[DecisionState]
&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;ol&gt;
&lt;li&gt;중복 질문 확인&lt;/li&gt;
&lt;li&gt;Thread 생성 (또는 기존 Thread 재사용)&lt;/li&gt;
&lt;li&gt;상태 파일 생성 (&lt;code&gt;~/.claude/pending_decisions/{question_id}.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Poller 실행하여 응답 대기&lt;/li&gt;
&lt;li&gt;SIGHUP 시그널 핸들러로 disconnected 상태 표시&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="342-decisionpoller-pollerpy"&gt;3.4.2 DecisionPoller (&lt;code&gt;poller.py&lt;/code&gt;)
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;: Discord 채널을 polling하여 응답 대기&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;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DecisionPoller&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;wait&lt;/span&gt;(state: DecisionState) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; PollResult
&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;ul&gt;
&lt;li&gt;기본값: 무한 대기 (&lt;code&gt;timeout_seconds=None&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;tmux pane에 실시간 대기 상태 표시 (ANSI escape)&lt;/li&gt;
&lt;li&gt;모호한 응답 시 최대 2회 재질문&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="343-응답-파서-parserpy"&gt;3.4.3 응답 파서 (&lt;code&gt;parser.py&lt;/code&gt;)
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;: 사용자 Discord 응답 파싱&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;지원 패턴&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;선택지 매칭: &amp;ldquo;A&amp;rdquo;, &amp;ldquo;a&amp;rdquo;, &amp;ldquo;A번&amp;rdquo;, &amp;ldquo;A로 해줘&amp;rdquo;, &amp;ldquo;1&amp;rdquo;, &amp;ldquo;1번&amp;rdquo;&lt;/li&gt;
&lt;li&gt;긍정/부정: &amp;ldquo;yes&amp;rdquo;, &amp;ldquo;네&amp;rdquo;, &amp;ldquo;예&amp;rdquo;, &amp;ldquo;no&amp;rdquo;, &amp;ldquo;아니요&amp;rdquo;&lt;/li&gt;
&lt;li&gt;자연어: 15자 이상 또는 한글 3자 이상&lt;/li&gt;
&lt;/ul&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;parse_response&lt;/span&gt;(text: str, options: list[str]) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; ParseResult
&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;build_clarify_message&lt;/span&gt;(&lt;span style="color:#f92672"&gt;...&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; str &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;h4 id="344-상태-저장소-statepy"&gt;3.4.4 상태 저장소 (&lt;code&gt;state.py&lt;/code&gt;)
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;: 결정 요청 상태를 JSON 파일로 영속화&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;상태 파일 위치&lt;/strong&gt;: &lt;code&gt;~/.claude/pending_decisions/{question_id}.json&lt;/code&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:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DecisionState&lt;/span&gt;(BaseModel):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; question_id: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; project: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; question: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options: list[str]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout_seconds: float &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; thread_id: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message_id: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; asked_at: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status: Literal[&lt;span style="color:#e6db74"&gt;&amp;#34;pending&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;disconnected&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;resolved&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;aborted&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;timeout&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; clarify_attempts: int
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resolved_at: str &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resolution: str &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; selected_option: str &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&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;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StateStore&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;save&lt;/span&gt;(state: DecisionState)
&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;load&lt;/span&gt;(question_id: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; DecisionState &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&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;load_all_pending&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; list[DecisionState]
&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;resolve&lt;/span&gt;(question_id, resolution, selected_option)
&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;is_duplicate&lt;/span&gt;(question: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; bool
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="35-mcp-tools-tools"&gt;3.5 MCP Tools (&lt;code&gt;tools/&lt;/code&gt;)
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Tool&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_ask_decision&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;✅&lt;/td&gt;
					&lt;td&gt;사용자 결정 요청&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_notify&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;진행 상황 알림&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_report_progress&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;작업 완료 리포트&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_check_pending&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;미해결 질문 확인&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_read_inbox&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;Inbox 메시지 조회&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;discord_clear_inbox&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;Inbox 메시지 삭제&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="36-감시-데몬-daemon"&gt;3.6 감시 데몬 (&lt;code&gt;daemon/&lt;/code&gt;)
&lt;/h3&gt;&lt;h4 id="361-discordwatcher-watcherpy"&gt;3.6.1 DiscordWatcher (&lt;code&gt;watcher.py&lt;/code&gt;)
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;: Discord 채널을 감시하여 새 메시지를 inbox 파일에 기록&lt;/p&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;discord-watch --interval &lt;span style="color:#ae81ff"&gt;10&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;tmux new-session -d -s discord-watch &lt;span style="color:#e6db74"&gt;&amp;#39;uv run discord-watch&amp;#39;&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;ul&gt;
&lt;li&gt;지정된 채널(들)을 주기적으로 polling&lt;/li&gt;
&lt;li&gt;새로운 사용자 메시지 감지 시 inbox에 기록&lt;/li&gt;
&lt;li&gt;tmux pane에 상태 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="362-inboxstore-inboxpy"&gt;3.6.2 InboxStore (&lt;code&gt;inbox.py&lt;/code&gt;)
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;: Discord 메시지를 저장하는 JSON 파일 기반 저장소&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inbox 파일 위치&lt;/strong&gt;: &lt;code&gt;~/.claude/discord_inbox.json&lt;/code&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:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;InboxMessage&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message_id: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; channel_id: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; thread_id: str &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; author: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; author_id: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; content: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timestamp: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; read: bool
&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;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;InboxStore&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;add_message&lt;/span&gt;(msg: InboxMessage)
&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;get_unread&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; list[InboxMessage]
&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;mark_read&lt;/span&gt;(message_id: str)
&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;clear_read&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;hr&gt;
&lt;h2 id="4-데이터-흐름도"&gt;4. 데이터 흐름도
&lt;/h2&gt;&lt;h3 id="41-결정-요청-흐름"&gt;4.1 결정 요청 흐름
&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-fallback" data-lang="fallback"&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;│ Claude Code │
&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; │ discord_ask_decision()
&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;│ MCP Server (server.py) │
&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;│ │ DecisionManager.ask() │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 1. 중복 확인 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 2. Thread 생성 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 3. 상태 파일 저장 │ │
&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;span style="display:flex;"&gt;&lt;span&gt;│ DecisionPoller.wait() │
&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;│ │ Polling 루프 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 1. tmux 상태 업데이트 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 2. Discord API 호출 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 3. 응답 파싱 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 4. 모호하면 재질문 │ │
&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;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;│ Discord REST API │ │ Discord Thread │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ (client.py) │ │ │
&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;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; │ parser.py │
&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;span style="display:flex;"&gt;&lt;span&gt; ┌─────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │ store.resolve()│
&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; │ PollResult │
&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;h3 id="42-감시-데몬-흐름"&gt;4.2 감시 데몬 흐름
&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-fallback" data-lang="fallback"&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;│ DiscordWatcher (daemon/watcher.py) │
&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;│ │ 1. 초기 메시지 ID 설정 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 2. 주기적 Polling (기본 10초) │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 3. 새 메시지 감지 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 4. InboxStore.add_message() │ │
&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;span style="display:flex;"&gt;&lt;span&gt;│ ~/.claude/discord_inbox.json │
&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;│ &amp;#34;last_message_id&amp;#34;: &amp;#34;...&amp;#34;, │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ &amp;#34;messages&amp;#34;: [ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ { &amp;#34;message_id&amp;#34;: &amp;#34;...&amp;#34;, ... } │
&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;span style="display:flex;"&gt;&lt;span&gt; │ discord_read_inbox()
&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;│ Claude Code │
&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="43-세션-복원-흐름"&gt;4.3 세션 복원 흐름
&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-fallback" data-lang="fallback"&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;│ Claude Code 세션 시작 │
&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;│ discord_check_pending() │
&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;│ DecisionManager.restore_pending() │
&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;│ │ 1. ~/.claude/pending_decisions/*.json │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 2. Discord API로 Thread 확인 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 3. 답변 있으면 → 자동 해결 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ │ 4. 답변 없으면 → 재시작 알림 │ │
&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="5-mcp-도구-목록"&gt;5. MCP 도구 목록
&lt;/h2&gt;&lt;h3 id="51-discord_ask_decision"&gt;5.1 discord_ask_decision
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;설명&lt;/strong&gt;: 사용자 결정이 필요할 때 Discord Thread에 질문 전송 후 응답 대기 (블로킹)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;파라미터&lt;/strong&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;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;question&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string&lt;/td&gt;
					&lt;td&gt;✅&lt;/td&gt;
					&lt;td&gt;질문 내용&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;context&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string&lt;/td&gt;
					&lt;td&gt;✅&lt;/td&gt;
					&lt;td&gt;현재 작업 상황&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;options&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string[]&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;선택지 목록 (빈 배열이면 자유 응답)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;timeout_seconds&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;float&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;대기 Timeout (null이면 무한 대기)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;thread_id&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;기존 Thread ID (null이면 새 Thread 생성)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&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;success&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;answer&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;A) 지금 실행&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;selected_option&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;A) 지금 실행&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;question_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;project_20260301_abc123&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;timed_out&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&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;aborted&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&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="52-discord_notify"&gt;5.2 discord_notify
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;설명&lt;/strong&gt;: 진행 상황을 Discord에 알림 (논블로킹)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;파라미터&lt;/strong&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;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;message&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string&lt;/td&gt;
					&lt;td&gt;✅&lt;/td&gt;
					&lt;td&gt;알림 메시지&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;level&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;info/warning/success/error (기본값: info)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;thread_id&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;전송할 Thread ID (null이면 기본 채널)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="53-discord_report_progress"&gt;5.3 discord_report_progress
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;설명&lt;/strong&gt;: 작업 완료 또는 단계 완료 시 결과 리포트 (논블로킹)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;파라미터&lt;/strong&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;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string&lt;/td&gt;
					&lt;td&gt;✅&lt;/td&gt;
					&lt;td&gt;리포트 제목&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;summary&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string&lt;/td&gt;
					&lt;td&gt;✅&lt;/td&gt;
					&lt;td&gt;작업 결과 요약&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;details&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string[]&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;세부 항목 목록&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;thread_id&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;string&lt;/td&gt;
					&lt;td&gt;❌&lt;/td&gt;
					&lt;td&gt;전송할 Thread ID&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="54-discord_check_pending"&gt;5.4 discord_check_pending
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;설명&lt;/strong&gt;: 세션 시작 시 미해결 질문 확인 (논블로킹)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;파라미터&lt;/strong&gt;: 없음&lt;/p&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;has_pending&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;pending_questions&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:#f92672"&gt;&amp;#34;question_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;project_20260301_abc123&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;question&amp;#34;&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 style="color:#f92672"&gt;&amp;#34;thread_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;1234567890&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;asked_at&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-03-01T10:30:00Z&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;status&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;pending&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="55-discord_read_inbox"&gt;5.5 discord_read_inbox
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;설명&lt;/strong&gt;: Inbox에 저장된 메시지 조회 (논블로킹)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;파라미터&lt;/strong&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;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;unread_only&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;boolean&lt;/td&gt;
					&lt;td&gt;true&lt;/td&gt;
					&lt;td&gt;읽지 않은 메시지만 반환&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;mark_read&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;boolean&lt;/td&gt;
					&lt;td&gt;false&lt;/td&gt;
					&lt;td&gt;조회한 메시지를 읽음으로 표시&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="56-discord_clear_inbox"&gt;5.6 discord_clear_inbox
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;설명&lt;/strong&gt;: Inbox 메시지 삭제 (논블로킹)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;파라미터&lt;/strong&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;th&gt;설명&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;read_only&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;boolean&lt;/td&gt;
					&lt;td&gt;true&lt;/td&gt;
					&lt;td&gt;읽은 메시지만 삭제&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="6-의존성-정보"&gt;6. 의존성 정보
&lt;/h2&gt;&lt;h3 id="61-핵심-의존성"&gt;6.1 핵심 의존성
&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;fastmcp&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&amp;gt;= 0.1.0&lt;/td&gt;
					&lt;td&gt;MCP 서버 프레임워크&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;httpx&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&amp;gt;= 0.27.0&lt;/td&gt;
					&lt;td&gt;비동기 HTTP 클라이언트&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;websockets&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&amp;gt;= 12.0&lt;/td&gt;
					&lt;td&gt;WebSocket 클라이언트&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;python-dotenv&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&amp;gt;= 1.0.0&lt;/td&gt;
					&lt;td&gt;환경변수 로드&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;pydantic&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&amp;gt;= 2.0.0&lt;/td&gt;
					&lt;td&gt;데이터 모델 검증&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;anyio&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&amp;gt;= 4.0.0&lt;/td&gt;
					&lt;td&gt;비동기 실행&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="62-개발-의존성"&gt;6.2 개발 의존성
&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;pytest&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;테스트 프레임워크&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;pytest-asyncio&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;비동기 테스트 지원&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;pytest-mock&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Mock 지원&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;respx&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;httpx mocking&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="63-cli-명령어"&gt;6.3 CLI 명령어
&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;discord-mcp &lt;span style="color:#75715e"&gt;# MCP 서버 실행&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;discord-watch &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;hr&gt;
&lt;h2 id="7-상태-파일-형식"&gt;7. 상태 파일 형식
&lt;/h2&gt;&lt;h3 id="71-결정-상태-파일"&gt;7.1 결정 상태 파일
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;위치&lt;/strong&gt;: &lt;code&gt;~/.claude/pending_decisions/{question_id}.json&lt;/code&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;question_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;project_20260301_abc123&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;project&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;my-project&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;question&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;DB 마이그레이션을 실행할까요?&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;context&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;v1→v2 스키마 변경&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;options&amp;#34;&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;A) 지금 실행&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;B) 스테이징 먼저&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;C) 보류&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;timeout_seconds&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;null&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;1234567890&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;message_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0987654321&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;asked_at&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-03-01T10:30:00Z&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;status&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;pending&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;clarify_attempts&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&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;resolved_at&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;null&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;resolution&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;null&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;selected_option&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;null&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="72-inbox-파일"&gt;7.2 Inbox 파일
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;위치&lt;/strong&gt;: &lt;code&gt;~/.claude/discord_inbox.json&lt;/code&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;last_message_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;1234567890&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;messages&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:#f92672"&gt;&amp;#34;message_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;1234567890&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;channel_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;1234567890&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;thread_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;null&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;author&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;username&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;author_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;1234567890&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;content&amp;#34;&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 style="color:#f92672"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-03-01T00:00:00Z&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;read&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&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="8-아키텍처-원칙"&gt;8. 아키텍처 원칙
&lt;/h2&gt;&lt;h3 id="81-설계-원칙"&gt;8.1 설계 원칙
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;싱글톤 패턴&lt;/strong&gt;: DiscordClient, StateStore, InboxStore는 모듈 레벨 싱글턴&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 영속화&lt;/strong&gt;: 모든 결정 요청 상태는 파일로 영속화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;무한 대기 기본값&lt;/strong&gt;: Claude가 독단적으로 진행하지 않도록 timeout 기본값은 None&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재질문 제한&lt;/strong&gt;: 모호한 응답은 최대 2회까지만 재질문&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate Limit 처리&lt;/strong&gt;: Discord API 429 응답 시 자동 대기 후 재시도&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="82-에러-handling"&gt;8.2 에러 handling
&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;Discord API 429&lt;/td&gt;
					&lt;td&gt;자동 대기 후 재시도 (최대 5회)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;WebSocket 연결 끊김&lt;/td&gt;
					&lt;td&gt;Exponential backoff로 재연결&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;응답 파싱 실패&lt;/td&gt;
					&lt;td&gt;최대 2회 재질문 후 중단&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Timeout 발생&lt;/td&gt;
					&lt;td&gt;작업 중단 알림 후 aborted 상태&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;세션 끊김 (SIGHUP)&lt;/td&gt;
					&lt;td&gt;disconnected 상태로 저장&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;문서 버전: 1.0.0&lt;/em&gt;
&lt;em&gt;마지막 수정: 2026-03-02&lt;/em&gt;&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-02-002-discord-decision-mcp-architecture-design/" &gt;English Version&lt;/a&gt;&lt;/p&gt;</description></item><item><title>[ZeroClaw] 2026 상반기 발전방향 회의록</title><link>https://blog.agentthread.dev/ko/post/2026-02-28-001-zeroclaw-2026-h1-roadmap-meeting/</link><pubDate>Sat, 28 Feb 2026 09:55:15 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/2026-02-28-001-zeroclaw-2026-h1-roadmap-meeting/</guid><description>&lt;h1 id="zeroclaw-2026-상반기-발전방향-팀-회의록"&gt;ZeroClaw 2026 상반기 발전방향 팀 회의록
&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;회의 일시&lt;/strong&gt;: 2026-02-28
&lt;strong&gt;참석자&lt;/strong&gt;: 아키텍트, 보안 리드, 프로덕트 오너, DevOps 리드
&lt;strong&gt;회의 장소&lt;/strong&gt;: ZeroClaw 개발팀 회의실
&lt;strong&gt;작성자&lt;/strong&gt;: ZeroClaw Team&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-회의-개요"&gt;1. 회의 개요
&lt;/h2&gt;&lt;p&gt;본 회의는 ZeroClaw 프로젝트의 2026년 상반기 발전방향을 논의하고, 각 영역별 로드맵과 우선순위를 수립하기 위해 개최되었다.&lt;/p&gt;
&lt;h3 id="11-회의-안건"&gt;1.1 회의 안건
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;멀티 에이전트 아키텍처 발전 방향&lt;/li&gt;
&lt;li&gt;보안 강화 및 컴플라이언스 대응&lt;/li&gt;
&lt;li&gt;제품 경쟁력 강화 및 사용자 경험 개선&lt;/li&gt;
&lt;li&gt;DevOps 및 인프라 고도화&lt;/li&gt;
&lt;li&gt;종합 로드맵 및 우선순위 합의&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="2-아키텍처-발전방향-논의"&gt;2. 아키텍처 발전방향 논의
&lt;/h2&gt;&lt;h3 id="21-아키텍트-발언"&gt;2.1 아키텍트 발언
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;현재 ZeroClaw는 Phase 1(인프로세스 위임)을 완료하고 Phase 2(파일 기반 멀티 에이전트) 구현 중에 있습니다. 2026년 상반기에는 다음과 같은 기술적 목표를 달성해야 합니다.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="211-멀티-에이전트-오케스트레이션-고도화"&gt;2.1.1 멀티 에이전트 오케스트레이션 고도화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DAG 기반 워크플로우 실행 엔진 구현&lt;/li&gt;
&lt;li&gt;순차/병렬/조건/반복 실행 패턴 지원&lt;/li&gt;
&lt;li&gt;에이전트 간 작업 분배 알고리즘 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&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-rust" data-lang="rust"&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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Workflow&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; nodes: Vec&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;WorkflowNode&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; edges: Vec&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;WorkflowEdge&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; start_node: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; end_nodes: Vec&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; error_handling: &lt;span style="color:#a6e22e"&gt;ErrorHandlingPolicy&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;h4 id="212-ipc-통신-프로토콜-개선"&gt;2.1.2 IPC 통신 프로토콜 개선
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현재 stdout 기반 통신을 구조화된 IPC로 전환&lt;/li&gt;
&lt;li&gt;Unix Socket, gRPC, WebSocket 다중 전송 계층 지원&lt;/li&gt;
&lt;li&gt;메시지 직렬화 최적화 (JSON → MessagePack/CBOR)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="213-확장성-및-모듈화"&gt;2.1.3 확장성 및 모듈화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;플러그인 아키텍처 도입 검토&lt;/li&gt;
&lt;li&gt;Wasm 실행 모드 구현 (샌드박싱 강화)&lt;/li&gt;
&lt;li&gt;외부 에이전트 등록 API 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 낮음 (P3)&lt;/p&gt;
&lt;h4 id="214-성능-최적화"&gt;2.1.4 성능 최적화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zero-copy 메시지 전달&lt;/li&gt;
&lt;li&gt;연결 풀링 및 캐싱 전략&lt;/li&gt;
&lt;li&gt;비동기 I/O 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h3 id="22-토의-내용"&gt;2.2 토의 내용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;보안 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;IPC 프로토콜 개선 시 보안 채널 적용이 선행되어야 합니다. 에이전트 간 통신에 대한 암호화와 인증이 필요합니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;프로덕트 오너:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;사용자 관점에서는 에이전트 정의가 더 직관적이어야 합니다. YAML 스키마를 단순화하는 것도 고려해봅시다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;DevOps 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;성능 최적화 작업은 관찰성 향상과 병행되어야 합니다. 메트릭 수집이 먼저 확보되어야 최적화 효과를 측정할 수 있습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="3-보안-발전방향-논의"&gt;3. 보안 발전방향 논의
&lt;/h2&gt;&lt;h3 id="31-보안-리드-발언"&gt;3.1 보안 리드 발언
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;ZeroClaw는 이미 강력한 보안 기반을 갖추고 있으나, OWASP Top 10 관점에서 보완이 필요한 영역이 있습니다. 특히 샌드박스 격리와 감사 로깅 강화가 시급합니다.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="311-샌드박스-강화"&gt;3.1.1 샌드박스 강화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;현재 문제점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker 샌드박스: &lt;code&gt;--read-only&lt;/code&gt;, &lt;code&gt;--cap-drop&lt;/code&gt;, seccomp 미적용&lt;/li&gt;
&lt;li&gt;Firejail: 네트워크 격리 미적용 (&lt;code&gt;--net=none&lt;/code&gt; 누락)&lt;/li&gt;
&lt;/ul&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Docker 강화 프로필
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker_cmd.args([
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;run&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;--rm&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;--read-only&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;--security-opt&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;no-new-privileges&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;--cap-drop&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;ALL&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;--network&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;none&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;--memory&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;256m&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;--pids-limit&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;64&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;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&lt;/p&gt;
&lt;h4 id="312-감사-로깅-강화"&gt;3.1.2 감사 로깅 강화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;로그 서명 (Log Signing) 도입으로 변조 방지&lt;/li&gt;
&lt;li&gt;실시간 보안 알림 웹훅 연동&lt;/li&gt;
&lt;li&gt;Prometheus 메트릭 노출&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="313-ssrf-방어"&gt;3.1.3 SSRF 방어
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;외부 요청 시 HTTPS 강제&lt;/li&gt;
&lt;li&gt;사설 IP 대역 차단&lt;/li&gt;
&lt;li&gt;도메인 허용목록 검증 강화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="314-toctou-취약점-수정"&gt;3.1.4 TOCTOU 취약점 수정
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;경로 검증과 정규화를 원자적으로 수행&lt;/li&gt;
&lt;li&gt;심볼릭 링크 레이스 컨디션 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&lt;/p&gt;
&lt;h3 id="32-토의-내용"&gt;3.2 토의 내용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;아키텍트:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;샌드박스 강화는 Wasm 실행 모드와 연계하여 진행하면 더 강력한 격리를 제공할 수 있습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;DevOps 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;Prometheus 메트릭은 Grafana 대시보드와 연동하여 운영 가시성을 크게 개선할 수 있습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;프로덕트 오너:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;보안 강화로 인한 사용자 경험 저하가 없도록 기본값은 안전하게, 고급 사용자는 유연하게 설정할 수 있어야 합니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="4-제품-발전방향-논의"&gt;4. 제품 발전방향 논의
&lt;/h2&gt;&lt;h3 id="41-프로덕트-오너-발언"&gt;4.1 프로덕트 오너 발언
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;ZeroClaw의 핵심 가치는 &amp;lsquo;고성능&amp;rsquo;, &amp;lsquo;확장성&amp;rsquo;, &amp;lsquo;보안&amp;rsquo;입니다. 2026년 상반기에는 이를 더욱 강화하면서 사용자 경험을 개선해야 합니다.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="411-사용자-경험-개선"&gt;4.1.1 사용자 경험 개선
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;CLI 개선:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zeroclaw agent list&lt;/code&gt; 출력 개선 (테이블 포맷)&lt;/li&gt;
&lt;li&gt;대화형 에이전트 실행 모드&lt;/li&gt;
&lt;li&gt;진행 상황 시각화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;설정 단순화:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;에이전트 정의 YAML 스키마 단순화&lt;/li&gt;
&lt;li&gt;sensible defaults 제공&lt;/li&gt;
&lt;li&gt;설정 검증 및 오류 메시지 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&lt;/p&gt;
&lt;h4 id="412-문서화-및-온보딩-강화"&gt;4.1.2 문서화 및 온보딩 강화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quickstart 가이드 개선&lt;/li&gt;
&lt;li&gt;API 참조 문서 자동 생성&lt;/li&gt;
&lt;li&gt;예제 시나리오 확장&lt;/li&gt;
&lt;li&gt;다국어 문서 (한국어, 일본어, 중국어)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="413-커뮤니티-성장-전략"&gt;4.1.3 커뮤니티 성장 전략
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CONTRIBUTING.md 개선&lt;/li&gt;
&lt;li&gt;Good first issue 라벨링&lt;/li&gt;
&lt;li&gt;정기 릴리즈 노트 발행&lt;/li&gt;
&lt;li&gt;Discord/Slack 커뮤니티 채널&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="414-차별화-포인트-강화"&gt;4.1.4 차별화 포인트 강화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하드웨어 페리퍼럴 지원 강화 (STM32, RPi)&lt;/li&gt;
&lt;li&gt;특화 도메인 에이전트 템플릿&lt;/li&gt;
&lt;li&gt;성능 벤치마크 공개&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 낮음 (P3)&lt;/p&gt;
&lt;h3 id="42-토의-내용"&gt;4.2 토의 내용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;아키텍트:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;CLI 개선은 ergonomics 측면에서 중요합니다. structopt 대신 clap 4.x로 업그레이드하는 것도 검토해봅시다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;보안 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;다국어 문서 작성 시 보안 관련 내용은 정확한 번역이 중요합니다. 용어집을 먼저 작성하는 것을 권장합니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;DevOps 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;커뮤니티 채널은 GitHub Discussions부터 시작하는 것이 좋습니다. 별도 인프라 없이 시작할 수 있습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="5-devops-발전방향-논의"&gt;5. DevOps 발전방향 논의
&lt;/h2&gt;&lt;h3 id="51-devops-리드-발언"&gt;5.1 DevOps 리드 발언
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;안정적인 배포와 운영을 위해 CI/CD 파이프라인과 모니터링 시스템을 고도화해야 합니다.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="511-cicd-파이프라인-고도화"&gt;5.1.1 CI/CD 파이프라인 고도화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;현재 상태:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Actions 기반 CI&lt;/li&gt;
&lt;li&gt;cargo fmt, clippy, test 자동화&lt;/li&gt;
&lt;li&gt;Docker 빌드&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cargo audit 통합 (취약점 스캔)&lt;/li&gt;
&lt;li&gt;크로스 플랫폼 빌드 (Linux, macOS, Windows)&lt;/li&gt;
&lt;li&gt;릴리즈 자동화 (cargo-release)&lt;/li&gt;
&lt;li&gt;배포 전 스모크 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&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-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 제안하는 CI 워크플로우&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Security audit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cargo audit&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Cross-platform build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;strategy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;matrix&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;os&lt;/span&gt;: [&lt;span style="color:#ae81ff"&gt;ubuntu-latest, macos-latest, windows-latest]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runs-on&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${{ matrix.os }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="512-모니터링-및-관찰성"&gt;5.1.2 모니터링 및 관찰성
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus 메트릭 엔드포인트&lt;/li&gt;
&lt;li&gt;Grafana 대시보드 템플릿&lt;/li&gt;
&lt;li&gt;분산 추적 (OpenTelemetry)&lt;/li&gt;
&lt;li&gt;로그 집계 (ELK/Loki)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="513-배포-자동화-및-롤백"&gt;5.1.3 배포 자동화 및 롤백
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blue-Green 배포 지원&lt;/li&gt;
&lt;li&gt;자동 롤백 임계값 설정&lt;/li&gt;
&lt;li&gt;Canary 릴리즈 옵션&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="514-개발-환경-개선"&gt;5.1.4 개발 환경 개선
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;devcontainer 구성&lt;/li&gt;
&lt;li&gt;git hooks 자동 설정&lt;/li&gt;
&lt;li&gt;개발용 설정 프로파일&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 낮음 (P3)&lt;/p&gt;
&lt;h3 id="52-토의-내용"&gt;5.2 토의 내용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;아키텍트:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;OpenTelemetry 통합은 멀티 에이전트 추적에 필수적입니다. 에이전트 간 호출 체인을 시각화할 수 있어야 합니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;보안 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;cargo audit은 CI에 반드시 포함되어야 합니다. 또한 의존성 라이선스 검사도 추가하는 것이 좋습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;프로덕트 오너:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;devcontainer는 신규 기여자 온보딩에 큰 도움이 될 것입니다. 우선순위를 높여도 좋습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="6-종합-로드맵-및-우선순위"&gt;6. 종합 로드맵 및 우선순위
&lt;/h2&gt;&lt;h3 id="61-2026년-상반기-로드맵"&gt;6.1 2026년 상반기 로드맵
&lt;/h3&gt;&lt;h4 id="q1-3월-4월"&gt;Q1 (3월-4월)
&lt;/h4&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;Docker/Firejail 샌드박스 강화&lt;/td&gt;
					&lt;td&gt;보안&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TOCTOU 취약점 수정&lt;/td&gt;
					&lt;td&gt;보안&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CI/CD 보안 스캔 통합&lt;/td&gt;
					&lt;td&gt;DevOps&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CLI 개선 및 UX 향상&lt;/td&gt;
					&lt;td&gt;프로덕트&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;DAG 기반 워크플로우 엔진&lt;/td&gt;
					&lt;td&gt;아키텍트&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id="q2-5월-6월"&gt;Q2 (5월-6월)
&lt;/h4&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;IPC 통신 프로토콜 개선&lt;/td&gt;
					&lt;td&gt;아키텍트&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;감사 로깅 강화&lt;/td&gt;
					&lt;td&gt;보안&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SSRF 방어 구현&lt;/td&gt;
					&lt;td&gt;보안&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Prometheus 메트릭 노출&lt;/td&gt;
					&lt;td&gt;DevOps&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;문서화 및 온보딩 개선&lt;/td&gt;
					&lt;td&gt;프로덕트&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;커뮤니티 인프라 구축&lt;/td&gt;
					&lt;td&gt;프로덕트&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="62-합의된-원칙"&gt;6.2 합의된 원칙
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;보안 우선&lt;/strong&gt;: 모든 새로운 기능은 보안 리뷰를 거쳐야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;점진적 배포&lt;/strong&gt;: 큰 변경은 단계적으로 롤아웃&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;롤백 가능&lt;/strong&gt;: 모든 변경은 쉽게 롤백 가능해야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문서 동기화&lt;/strong&gt;: 기능 변경 시 문서 업데이트 필수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트 커버리지&lt;/strong&gt;: 새로운 코드는 테스트 필수&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="63-리스크-및-대응"&gt;6.3 리스크 및 대응
&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;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;중간&lt;/td&gt;
					&lt;td&gt;높음&lt;/td&gt;
					&lt;td&gt;폴백 메커니즘 구현&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;td&gt;벤치마크 자동화&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;td&gt;cargo audit 자동화&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;td&gt;문서 PR 필수화&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="7-결론-및-다음-단계"&gt;7. 결론 및 다음 단계
&lt;/h2&gt;&lt;h3 id="71-회의-결론"&gt;7.1 회의 결론
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;아키텍처&lt;/strong&gt;: 멀티 에이전트 오케스트레이션 고도화를 최우선으로 진행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보안&lt;/strong&gt;: 샌드박스 강화와 TOCTOU 수정을 즉시 착수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제품&lt;/strong&gt;: CLI UX 개선으로 사용자 경험 향상&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevOps&lt;/strong&gt;: CI/CD 보안 강화와 모니터링 기반 구축&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="72-액션-아이템"&gt;7.2 액션 아이템
&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;th&gt;기한&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;Docker 샌드박스 보안 프로필 적용&lt;/td&gt;
					&lt;td&gt;보안 리드&lt;/td&gt;
					&lt;td&gt;2026-03-07&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;TOCTOU 경로 검증 수정&lt;/td&gt;
					&lt;td&gt;보안 리드&lt;/td&gt;
					&lt;td&gt;2026-03-10&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;CI cargo audit 통합&lt;/td&gt;
					&lt;td&gt;DevOps 리드&lt;/td&gt;
					&lt;td&gt;2026-03-05&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;CLI agent list 개선&lt;/td&gt;
					&lt;td&gt;프로덕트 오너&lt;/td&gt;
					&lt;td&gt;2026-03-12&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;워크플로우 엔진 설계 문서&lt;/td&gt;
					&lt;td&gt;아키텍트&lt;/td&gt;
					&lt;td&gt;2026-03-15&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="73-다음-회의-일정"&gt;7.3 다음 회의 일정
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;일시&lt;/strong&gt;: 2026-03-14 10:00&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;안건&lt;/strong&gt;: Q1 진행 상황 리뷰 및 Q2 상세 계획&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="부록-참고-문서"&gt;부록: 참고 문서
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docs/project/multi-agent-architecture-design.md&lt;/code&gt; - 멀티 에이전트 아키텍처 설계&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs/project/multi-agent-communication-protocols.md&lt;/code&gt; - 통신 프로토콜 설계&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs/security/security-improvements.md&lt;/code&gt; - 보안 개선 보고서&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; - 엔지니어링 프로토콜&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;회의록 작성일&lt;/strong&gt;: 2026-02-28
&lt;strong&gt;승인자&lt;/strong&gt;: ZeroClaw Team Lead
&lt;strong&gt;배포&lt;/strong&gt;: ZeroClaw 개발팀 전체&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-02-28-001-zeroclaw-2026-h1-roadmap-meeting/" &gt;English Version&lt;/a&gt;&lt;/p&gt;</description></item><item><title>[ZeroClaw] 멀티 에이전트 아키텍처 설계안</title><link>https://blog.agentthread.dev/ko/post/2026-02-27-011-zeroclaw-multi-agent-architecture-design/</link><pubDate>Fri, 27 Feb 2026 22:58:08 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/2026-02-27-011-zeroclaw-multi-agent-architecture-design/</guid><description>&lt;h1 id="zeroclaw-멀티-에이전트-아키텍처-설계안"&gt;ZeroClaw 멀티 에이전트 아키텍처 설계안
&lt;/h1&gt;&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;ZeroClaw의 멀티 에이전트 아키텍처는 단일 에이전트 모델에서 진화하는 고급 협력 시스템을 구현합니다.&lt;/p&gt;
&lt;h3 id="현재-상태"&gt;현재 상태
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phase 1&lt;/strong&gt;: In-Process Delegation (완료)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 2&lt;/strong&gt;: File-Based Multi-Agent Architecture (개발 중)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="1-설계-철학"&gt;1. 설계 철학
&lt;/h2&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;KISS&lt;/td&gt;
					&lt;td&gt;단순 통신 프로토콜&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;YAGNI&lt;/td&gt;
					&lt;td&gt;필수 기능만 구현&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SRP&lt;/td&gt;
					&lt;td&gt;단일 책임 에이전트&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Secure&lt;/td&gt;
					&lt;td&gt;최소 권한 원칙&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="2-아키텍처-구조"&gt;2. 아키텍처 구조
&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;Application Layer: Research │ Code │ Test Agent
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Message Bus Layer: NATS/Redis Pub/Sub
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transport Layer: gRPC │ WebSocket │ Unix Socket
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="3-에이전트-정의"&gt;3. 에이전트 정의
&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-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;agent&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;id&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;researcher&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;name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Research Agent&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:#f92672"&gt;execution&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;mode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;subprocess&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;provider&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;openrouter&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;model&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;claude-sonnet-4-6&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="4-cli-명령어"&gt;4. CLI 명령어
&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;zeroclaw agent list
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;zeroclaw agent show &amp;lt;id&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;zeroclaw agent run --agent-id researcher
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="5-보안"&gt;5. 보안
&lt;/h2&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;Subprocess&lt;/td&gt;
					&lt;td&gt;프로세스&lt;/td&gt;
					&lt;td&gt;신뢰된 에이전트&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Docker&lt;/td&gt;
					&lt;td&gt;컨테이너&lt;/td&gt;
					&lt;td&gt;파일 작업&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Wasm&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;hr&gt;
&lt;p&gt;&lt;strong&gt;영어 버전:&lt;/strong&gt; &lt;a class="link" href="https://blog.agentthread.dev/post/2026-02-27-011-zeroclaw-multi-agent-architecture-design/" &gt;English Version&lt;/a&gt;&lt;/p&gt;</description></item><item><title>[Multi-Agent] 파일 기반 아키텍처 설계</title><link>https://blog.agentthread.dev/ko/post/2026-02-27-001-file-based-multi-agent-architecture-design/</link><pubDate>Fri, 27 Feb 2026 09:30:57 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/2026-02-27-001-file-based-multi-agent-architecture-design/</guid><description>&lt;h1 id="파일-기반-멀티-에이전트-아키텍처-설계"&gt;파일 기반 멀티 에이전트 아키텍처 설계
&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;상태:&lt;/strong&gt; 설계 제안
&lt;strong&gt;작성자:&lt;/strong&gt; multi-agent-architect
&lt;strong&gt;날짜:&lt;/strong&gt; 2026-02-24
&lt;strong&gt;유형:&lt;/strong&gt; 대안 아키텍처 (프로세스-단위-에이전트)&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;이 문서는 각 에이전트가 별도의 설정 파일로 정의되고 독립적인 프로세스로 실행되는 &lt;strong&gt;파일 기반, 프로세스-단위-에이전트&lt;/strong&gt; 아키텍처를 정의합니다. 에이전트는 필요시 호출되어 메인 코디네이터 에이전트에게 결과를 보고합니다.&lt;/p&gt;
&lt;h3 id="핵심-원칙"&gt;핵심 원칙
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;파일 기반 에이전트 정의&lt;/strong&gt;: 각 에이전트는 &lt;code&gt;agents/&lt;/code&gt; 디렉토리의 &lt;code&gt;.toml&lt;/code&gt; 파일&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;프로세스 격리&lt;/strong&gt;: 각 에이전트는 자체 프로세스에서 실행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;온디맨드 호출&lt;/strong&gt;: 메인 에이전트가 필요시 서브프로세스 에이전트 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보고 프로토콜&lt;/strong&gt;: IPC를 통한 구조화된 결과 보고&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제로-설정 디스커버리&lt;/strong&gt;: 에이전트 정의 자동 감지&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="1-에이전트-정의-파일-구조"&gt;1. 에이전트 정의 파일 구조
&lt;/h2&gt;&lt;h3 id="11-디렉토리-레이아웃"&gt;1.1 디렉토리 레이아웃
&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-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;~/.zeroclaw/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── config.toml # 메인 설정
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── agents/ # 에이전트 정의 디렉토리
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ ├── researcher.toml # 리서치 에이전트
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ ├── coder.toml # 코드 생성 에이전트
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ ├── tester.toml # 테스팅 에이전트
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ ├── reviewer.toml # 코드 리뷰 에이전트
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ └── summarizer.toml # 요약 에이전트
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── agents.d/ # 선택사항: 추가 에이전트 디렉토리
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ └── custom/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ └── my_agent.toml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── workspace/ # 공유 워크스페이스
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="12-에이전트-파일-스키마"&gt;1.2 에이전트 파일 스키마
&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-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# agents/researcher.toml&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:#a6e22e"&gt;agent&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;researcher&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;Research Agent&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;description&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;Conducts research on given topics using web search and knowledge bases&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:#a6e22e"&gt;agent&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;execution&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 에이전트 실행 방식: &amp;#34;subprocess&amp;#34; | &amp;#34;wasm&amp;#34; | &amp;#34;docker&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;mode&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;subprocess&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;# 실행 명령어 (템플릿 변수: {agent_id}, {workspace}, {config_dir})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;command&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;zeroclaw&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;args&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&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;run&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;--agent-id&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;{agent_id}&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;--config&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;{config}/agents/researcher.toml&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;--workspace&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;{workspace}&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;&lt;span style="color:#a6e22e"&gt;working_dir&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;{workspace}&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:#a6e22e"&gt;agent&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;execution&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ZEROCLAW_AGENT_MODE&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;worker&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ZEROCLAW_AGENT_ID&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;researcher&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:#a6e22e"&gt;provider&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;openrouter&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;model&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;anthropic/claude-sonnet-4-6&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;api_key&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;null&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:#a6e22e"&gt;temperature&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;0.3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;max_tokens&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;4096&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:#a6e22e"&gt;tools&lt;/span&gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;web_search&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;enabled&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[[&lt;span style="color:#a6e22e"&gt;tools&lt;/span&gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;web_fetch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;enabled&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&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:#a6e22e"&gt;tools&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;deny&lt;/span&gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;shell&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;reason&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;Research agent should not execute shell commands&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:#a6e22e"&gt;system&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;prompt&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&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 style="color:#e6db74"&gt;You are a Research Agent. Your role is to:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;1. Search for and gather information from credible sources
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;2. Synthesize findings into structured reports
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;3. Cite sources and provide references
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;4. Avoid speculation - stick to verified information
&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;&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:#a6e22e"&gt;memory&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;backend&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;shared&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# &amp;#34;shared&amp;#34; | &amp;#34;isolated&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;category&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;research&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:#a6e22e"&gt;reporting&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;mode&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;ipc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;format&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;json&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# &amp;#34;json&amp;#34; | &amp;#34;markdown&amp;#34; | &amp;#34;both&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;timeout_seconds&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;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:#a6e22e"&gt;retry&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;max_attempts&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;backoff_ms&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="2-에이전트-레지스트리"&gt;2. 에이전트 레지스트리
&lt;/h2&gt;&lt;p&gt;AgentRegistry는 에이전트 정의를 발견하고 관리합니다:&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentRegistry&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agents_dir: &lt;span style="color:#a6e22e"&gt;PathBuf&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agents: &lt;span style="color:#a6e22e"&gt;HashMap&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, AgentDefinition&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; security: &lt;span style="color:#a6e22e"&gt;Arc&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;SecurityPolicy&lt;span style="color:#f92672"&gt;&amp;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 style="color:#66d9ef"&gt;impl&lt;/span&gt; AgentRegistry {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;(agents_dir: &lt;span style="color:#a6e22e"&gt;PathBuf&lt;/span&gt;, security: &lt;span style="color:#a6e22e"&gt;Arc&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;SecurityPolicy&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Self&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;discover&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, id: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; Option&lt;span style="color:#f92672"&gt;&amp;lt;&amp;amp;&lt;/span&gt;AgentDefinition&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; Vec&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;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="3-프로세스-간-통신-ipc"&gt;3. 프로세스 간 통신 (IPC)
&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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentTask&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; task_id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; from_agent: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; to_agent: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; prompt: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; context: &lt;span style="color:#a6e22e"&gt;HashMap&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, serde_json::Value&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; input: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;serde_json::Value&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; timestamp: &lt;span style="color:#a6e22e"&gt;chrono&lt;/span&gt;::DateTime&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;chrono::Utc&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; deadline: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;chrono::DateTime&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;chrono::Utc&lt;span style="color:#f92672"&gt;&amp;gt;&amp;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;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-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentResult&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; task_id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; agent_id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; status: &lt;span style="color:#a6e22e"&gt;AgentStatus&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; data: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;serde_json::Value&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; output: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; error: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; metrics: &lt;span style="color:#a6e22e"&gt;AgentMetrics&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; artifacts: Vec&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Artifact&lt;span style="color:#f92672"&gt;&amp;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;pub&lt;/span&gt; timestamp: &lt;span style="color:#a6e22e"&gt;chrono&lt;/span&gt;::DateTime&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;chrono::Utc&lt;span style="color:#f92672"&gt;&amp;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="4-보안-고려사항"&gt;4. 보안 고려사항
&lt;/h2&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;Subprocess&lt;/td&gt;
					&lt;td&gt;프로세스 수준&lt;/td&gt;
					&lt;td&gt;신뢰할 수 있는 에이전트&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Docker&lt;/td&gt;
					&lt;td&gt;컨테이너&lt;/td&gt;
					&lt;td&gt;신뢰할 수 없는, 파일 작업&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Wasm&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;hr&gt;
&lt;h2 id="5-cli-명령어"&gt;5. CLI 명령어
&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-bash" data-lang="bash"&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;zeroclaw agent list
&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;zeroclaw agent show &amp;lt;agent_id&amp;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;zeroclaw agent run --agent-id researcher --prompt &lt;span style="color:#e6db74"&gt;&amp;#34;Research X&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;zeroclaw agent create --id my-agent --name &lt;span style="color:#e6db74"&gt;&amp;#34;My Agent&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;zeroclaw agent validate &amp;lt;agent_id&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="6-성공-기준"&gt;6. 성공 기준
&lt;/h2&gt;&lt;ul&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;li&gt;&lt;input disabled="" type="checkbox"&gt; IPC 프로토콜 1GB 이상 데이터 지원&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;li&gt;&lt;input disabled="" type="checkbox"&gt; 에이전트별 보안 정책 적용&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 에이전트 관리 CLI 명령어&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;영어 버전:&lt;/strong&gt; &lt;a class="link" href="https://blog.agentthread.dev/post/2026-02-27-001-file-based-multi-agent-architecture-design/" &gt;English Version&lt;/a&gt;&lt;/p&gt;</description></item><item><title>[ZeroClaw] 코드베이스 아키텍처 분석</title><link>https://blog.agentthread.dev/ko/post/2026-02-26-001-zeroclaw-codebase-architecture-analysis/</link><pubDate>Thu, 26 Feb 2026 10:07:29 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/2026-02-26-001-zeroclaw-codebase-architecture-analysis/</guid><description>&lt;h1 id="zeroclaw-코드베이스-아키텍처-분석-보고서"&gt;ZeroClaw 코드베이스 아키텍처 분석 보고서
&lt;/h1&gt;&lt;p&gt;&lt;em&gt;2026년 2월 26일 작성&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 Rust로 작성된 고성능 자율 에이전트 런타임입니다. 이 보고서는 코드베이스 구조와 보안 아키텍처에 대한 분석 결과를 정리합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-핵심-아키텍처-패턴"&gt;1. 핵심 아키텍처 패턴
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 **트레이트 기반 확장성(Trait-driven extensibility)**을 핵심 설계 원칙으로 채택하고 있습니다.&lt;/p&gt;
&lt;h3 id="11-주요-트레이트-7개"&gt;1.1 주요 트레이트 (7개)
&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;Provider&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/providers/traits.rs&lt;/td&gt;
					&lt;td&gt;AI 모델 프로바이더 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Channel&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/channels/traits.rs&lt;/td&gt;
					&lt;td&gt;통신 채널 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Tool&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/tools/traits.rs&lt;/td&gt;
					&lt;td&gt;도구 실행 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Memory&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/memory/traits.rs&lt;/td&gt;
					&lt;td&gt;메모리 백엔드 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Observer&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/observability/traits.rs&lt;/td&gt;
					&lt;td&gt;관측 가능성 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;RuntimeAdapter&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/runtime/traits.rs&lt;/td&gt;
					&lt;td&gt;런타임 어댑터 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Peripheral&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/peripherals/traits.rs&lt;/td&gt;
					&lt;td&gt;하드웨어 주변장치 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="2-보안-아키텍처"&gt;2. 보안 아키텍처
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 &lt;strong&gt;다층 방어(Defense-in-Depth)&lt;/strong&gt; 전략을 통해 보안을 구현합니다.&lt;/p&gt;
&lt;h3 id="21-보안-레이어-6개"&gt;2.1 보안 레이어 (6개)
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SecurityPolicy Core&lt;/strong&gt; - 자율성 수준 관리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gateway Security&lt;/strong&gt; - Bearer 토큰 인증&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool Validation&lt;/strong&gt; - 인젝션 방지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime Sandbox&lt;/strong&gt; - Landlock/Firejail/Docker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secret Management&lt;/strong&gt; - ChaCha20-Poly1305&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit Logging&lt;/strong&gt; - 이벤트 추적&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="22-핵심-보안-경계"&gt;2.2 핵심 보안 경계
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;명령어 허용 목록: 15개&lt;/li&gt;
&lt;li&gt;속도 제한: 20회/시간&lt;/li&gt;
&lt;li&gt;환경 변수: 8개만 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="3-멀티-에이전트-시스템"&gt;3. 멀티 에이전트 시스템
&lt;/h2&gt;&lt;h3 id="구현-로드맵"&gt;구현 로드맵
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Phase&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;1&lt;/td&gt;
					&lt;td&gt;1주&lt;/td&gt;
					&lt;td&gt;코어 트레이트, DelegateTool 확장&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;2-3주&lt;/td&gt;
					&lt;td&gt;Docker/Wasm 실행 모드&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;4주+&lt;/td&gt;
					&lt;td&gt;분산 메시지 버스, 합의 알고리즘&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="결론"&gt;결론
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 확장성, 보안, 성능, 유지보수성 측면에서 우수한 아키텍처를 가지고 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;이 보고서는 ZeroClaw 개발 팀의 협업 분석 결과를 바탕으로 작성되었습니다.&lt;/em&gt;&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-02-26-001-zeroclaw-codebase-architecture-analysis/" &gt;English Version&lt;/a&gt;&lt;/p&gt;</description></item><item><title>[Multi-Agent] 통신 플랫폼 설계 고찰</title><link>https://blog.agentthread.dev/ko/post/2026-02-21-003-multi-agent-platform/</link><pubDate>Sat, 21 Feb 2026 21:00:00 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/2026-02-21-003-multi-agent-platform/</guid><description>&lt;h2 id="들어가며"&gt;들어가며
&lt;/h2&gt;&lt;p&gt;AI 에이전트 시스템이 발전하면서 단일 에이전트에서 멀티 에이전트 시스템으로의 전환이 가속화되고 있습니다. 이 글에서는 멀티 에이전트 간의 효율적인 통신을 위한 플랫폼 아키텍처를 설계하는 방법에 대해 고찰합니다.&lt;/p&gt;
&lt;h2 id="1-아키텍처-개요"&gt;1. 아키텍처 개요
&lt;/h2&gt;&lt;p&gt;멀티 에이전트 플랫폼은 크게 Orchestrator, Message Bus, 그리고 다양한 특화된 에이전트들로 구성됩니다.&lt;/p&gt;





&lt;figure class="svg-figure"&gt;
 &lt;img
 src="https://blog.agentthread.dev/images/posts/multi-agent-architecture.svg"
 alt="Multi-Agent Architecture"
 width="100%"
 height="auto"
 loading="lazy"
 style="max-width: 100%; height: auto; display: block; margin: 1.5rem auto;"
 /&gt;
 
 &lt;figcaption style="text-align: center; font-size: 0.9em; color: #666; margin-top: 0.5rem;"&gt;
 Multi-Agent Architecture
 &lt;/figcaption&gt;
 
&lt;/figure&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;strong&gt;Orchestrator&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;태스크 조율, 에이전트 스케줄링&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Message Bus&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;에이전트 간 통신 중계&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;State Store&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;공유 상태 관리&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Task Queue&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;우선순위 기반 작업 큐&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Agents&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;특화된 작업 수행&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="2-통신-프로토콜-설계"&gt;2. 통신 프로토콜 설계
&lt;/h2&gt;&lt;p&gt;에이전트 간 통신은 비동기 메시지 패싱을 기반으로 설계합니다.&lt;/p&gt;





&lt;figure class="svg-figure"&gt;
 &lt;img
 src="https://blog.agentthread.dev/images/posts/message-protocol.svg"
 alt="Message Protocol"
 width="100%"
 height="auto"
 loading="lazy"
 style="max-width: 100%; height: auto; display: block; margin: 1.5rem auto;"
 /&gt;
 
 &lt;figcaption style="text-align: center; font-size: 0.9em; color: #666; margin-top: 0.5rem;"&gt;
 Message Protocol
 &lt;/figcaption&gt;
 
&lt;/figure&gt;

&lt;h3 id="통신-흐름"&gt;통신 흐름
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Publish&lt;/strong&gt;: 에이전트가 태스크 요청을 메시지 버스에 발행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Route&lt;/strong&gt;: 버스가 적절한 에이전트에게 메시지 라우팅&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accept&lt;/strong&gt;: 대상 에이전트가 태스크 수락&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acknowledge&lt;/strong&gt;: 버스가 요청자에게 수락 확인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute&lt;/strong&gt;: 에이전트가 실제 작업 수행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result&lt;/strong&gt;: 결과를 버스에 발행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deliver&lt;/strong&gt;: 버스가 결과를 요청자에게 전달&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="3-메시지-구조"&gt;3. 메시지 구조
&lt;/h2&gt;&lt;p&gt;확장 가능하고 타입 안전한 메시지 구조를 설계합니다.&lt;/p&gt;





&lt;figure class="svg-figure"&gt;
 &lt;img
 src="https://blog.agentthread.dev/images/posts/message-structure.svg"
 alt="Message Structure"
 width="100%"
 height="auto"
 loading="lazy"
 style="max-width: 100%; height: auto; display: block; margin: 1.5rem auto;"
 /&gt;
 
 &lt;figcaption style="text-align: center; font-size: 0.9em; color: #666; margin-top: 0.5rem;"&gt;
 Message Structure
 &lt;/figcaption&gt;
 
&lt;/figure&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-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;header&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;message_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;uuid-v4&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;timestamp&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-02-21T12:00:00Z&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;source_agent&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;agent-research-01&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;target_agent&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;agent-writer-01&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;message_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;TASK_REQUEST&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:#f92672"&gt;&amp;#34;metadata&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;priority&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;normal&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;ttl&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3600&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;correlation_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;parent-uuid&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:#f92672"&gt;&amp;#34;payload&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;content_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;json&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;encoding&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;utf-8&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;data&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;task&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;summarize&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;content&amp;#34;&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&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;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;TASK_REQUEST&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;작업 요청&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;TASK_RESULT&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;작업 결과&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;STATUS_UPDATE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;상태 변경 알림&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;HEARTBEAT&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;생존 확인&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;QUERY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;정보 조회&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;RESPONSE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;조회 응답&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="4-에이전트-상태-관리"&gt;4. 에이전트 상태 관리
&lt;/h2&gt;&lt;p&gt;각 에이전트는 명확한 상태 머신을 가집니다.&lt;/p&gt;





&lt;figure class="svg-figure"&gt;
 &lt;img
 src="https://blog.agentthread.dev/images/posts/state-management.svg"
 alt="State Management"
 width="100%"
 height="auto"
 loading="lazy"
 style="max-width: 100%; height: auto; display: block; margin: 1.5rem auto;"
 /&gt;
 
 &lt;figcaption style="text-align: center; font-size: 0.9em; color: #666; margin-top: 0.5rem;"&gt;
 State Management
 &lt;/figcaption&gt;
 
&lt;/figure&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-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IDLE → RECEIVING → PROCESSING → EXECUTING → COMPLETED → IDLE
&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; ERROR ──────→ retry ──→ IDLE
&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;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;IDLE&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;새 태스크 대기&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RECEIVING&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;메시지 수신 및 검증&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;PROCESSING&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;태스크 분석 및 준비&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;EXECUTING&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;실제 작업 수행&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;COMPLETED&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;결과 전송 및 정리&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ERROR&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;오류 처리 및 재시도&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="5-통신-패턴"&gt;5. 통신 패턴
&lt;/h2&gt;&lt;h3 id="51-request-response"&gt;5.1 Request-Response
&lt;/h3&gt;&lt;p&gt;동기식 1:1 통신 패턴입니다.&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;# Request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; bus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;request(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;agent-code-01&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message&lt;span style="color:#f92672"&gt;=&lt;/span&gt;TaskRequest(task&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;generate_code&amp;#34;&lt;/span&gt;, spec&lt;span style="color:#f92672"&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; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&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;# Response&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; TaskResult(status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;success&amp;#34;&lt;/span&gt;, data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;generated_code)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="52-publish-subscribe"&gt;5.2 Publish-Subscribe
&lt;/h3&gt;&lt;p&gt;비동기식 1:N 통신 패턴입니다.&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;# Subscribe&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@bus.subscribe&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;research.completed&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_research_result&lt;/span&gt;(message):
&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;pass&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;# Publish&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; bus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;publish(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; topic&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;research.completed&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ResearchResult(data&lt;span style="color:#f92672"&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="53-broadcast"&gt;5.3 Broadcast
&lt;/h3&gt;&lt;p&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;# Broadcast&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; bus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;broadcast(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message&lt;span style="color:#f92672"&gt;=&lt;/span&gt;SystemAlert(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; level&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;warning&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;High load detected&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="6-핵심-설계-원칙"&gt;6. 핵심 설계 원칙
&lt;/h2&gt;&lt;h3 id="61-느슨한-결합-loose-coupling"&gt;6.1 느슨한 결합 (Loose Coupling)
&lt;/h3&gt;&lt;p&gt;에이전트는 서로의 내부 구현을 알지 못해야 합니다. 메시지 인터페이스를 통해서만 통신합니다.&lt;/p&gt;
&lt;h3 id="62-내결함성-fault-tolerance"&gt;6.2 내결함성 (Fault Tolerance)
&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;Agent&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;execute_with_retry&lt;/span&gt;(self, task, max_retries&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;):
&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; attempt &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(max_retries):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&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;await&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;execute(task)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; e:
&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; attempt &lt;span style="color:#f92672"&gt;==&lt;/span&gt; max_retries &lt;span style="color:#f92672"&gt;-&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; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;transition_to(State&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ERROR)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sleep(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;**&lt;/span&gt; attempt) &lt;span style="color:#75715e"&gt;# Exponential backoff&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="63-확장성-scalability"&gt;6.3 확장성 (Scalability)
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;수평적 확장: 동일한 역할의 에이전트를 추가&lt;/li&gt;
&lt;li&gt;로드 밸런싱: 메시지 버스에서 자동 분배&lt;/li&gt;
&lt;li&gt;샤딩: 태스크 타입별로 에이전트 그룹 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="64-관찰-가능성-observability"&gt;6.4 관찰 가능성 (Observability)
&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;&lt;span style="color:#a6e22e"&gt;@metrics.track&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process_message&lt;/span&gt;(self, message):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; metrics&lt;span style="color:#f92672"&gt;.&lt;/span&gt;timer(&lt;span style="color:#e6db74"&gt;&amp;#34;process_duration&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;handle(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; metrics&lt;span style="color:#f92672"&gt;.&lt;/span&gt;increment(&lt;span style="color:#e6db74"&gt;&amp;#34;messages_processed&amp;#34;&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; result
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="7-구현-고려사항"&gt;7. 구현 고려사항
&lt;/h2&gt;&lt;h3 id="71-메시지-직렬화"&gt;7.1 메시지 직렬화
&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;JSON&lt;/td&gt;
					&lt;td&gt;가독성, 호환성&lt;/td&gt;
					&lt;td&gt;크기, 성능&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Protocol Buffers&lt;/td&gt;
					&lt;td&gt;성능, 스키마&lt;/td&gt;
					&lt;td&gt;복잡성&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MessagePack&lt;/td&gt;
					&lt;td&gt;JSON 호환, 크기&lt;/td&gt;
					&lt;td&gt;도구 지원&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="72-백프레셔-backpressure"&gt;7.2 백프레셔 (Backpressure)
&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;Agent&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, max_concurrent&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&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;semaphore &lt;span style="color:#f92672"&gt;=&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Semaphore(max_concurrent)
&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;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;(self, message):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;semaphore:
&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;await&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;handle(message)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="73-데드레터-큐-dead-letter-queue"&gt;7.3 데드레터 큐 (Dead Letter Queue)
&lt;/h3&gt;&lt;p&gt;처리 실패한 메시지를 별도 큐로 이동시켜 분석 및 재처리합니다.&lt;/p&gt;
&lt;h2 id="8-결론"&gt;8. 결론
&lt;/h2&gt;&lt;p&gt;멀티 에이전트 통신 플랫폼은 다음 요소들이 핵심입니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;유연한 메시지 구조&lt;/strong&gt;: 다양한 통신 시나리오 지원&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;명확한 상태 관리&lt;/strong&gt;: 예측 가능한 에이전트 동작&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다양한 통신 패턴&lt;/strong&gt;: 요청-응답, 발행-구독, 브로드캐스트&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;내결함성 설계&lt;/strong&gt;: 재시도, 타임아웃, 에러 처리&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 원칙들을 기반으로 확장 가능하고 신뢰할 수 있는 멀티 에이전트 시스템을 구축할 수 있습니다.&lt;/p&gt;
&lt;h2 id="참고-자료"&gt;참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/Actor_model" target="_blank" rel="noopener"
 &gt;Actor Model - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.enterpriseintegrationpatterns.com/" target="_blank" rel="noopener"
 &gt;Enterprise Integration Patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://dataintensive.net/" target="_blank" rel="noopener"
 &gt;Designing Data-Intensive Applications&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;영어 버전:&lt;/strong&gt; &lt;a class="link" href="https://blog.agentthread.dev/post/2026-02-21-003-multi-agent-communication-platform/" &gt;English Version&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>