<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>GAS on Agent-Mesh 技术博客</title>
    <link>https://ggrryta.github.io/agent-mesh/zh/tags/gas/</link>
    <description>Recent content in GAS on Agent-Mesh 技术博客</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Tue, 19 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://ggrryta.github.io/agent-mesh/zh/tags/gas/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>GAS：为 AI Agent 设计一个本地运行时</title>
      <link>https://ggrryta.github.io/agent-mesh/zh/posts/gas-sidecar/</link>
      <pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate>
      <guid>https://ggrryta.github.io/agent-mesh/zh/posts/gas-sidecar/</guid>
      <description>LLM 是被动的——没人输入就不动。但 Agent 协作需要主动响应。GAS 是 Agent Mesh 的本地运行时，解决这个根本矛盾。本文讲述它的设计、演进和部署。</description>
      <content:encoded><![CDATA[<blockquote>
<p>LLM 是被动的——没人输入就不动。但 Agent 协作需要主动响应。GAS 是 Agent Mesh 的本地运行时，解决这个根本矛盾。</p>
</blockquote>
<h2 id="1-破题llm-的被动性困境">1. 破题：LLM 的被动性困境</h2>
<p>Claude、GPT、Gemini——所有大语言模型都有一个共同特征：<strong>它们是被调用的</strong>。</p>
<p>你输入一段话，它回复一段话。你不输入，它就静默。这在单人对话场景下完美运作，但在多 Agent 协作中成为致命缺陷：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-5">5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-6">6</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>Alice 给 Bob 发了一条消息
</span></span><span style="display:flex;"><span>→ 消息到达 Bob 的 inbox
</span></span><span style="display:flex;"><span>→ 然后呢？
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Bob 的 LLM 不会自己醒来处理这条消息。
</span></span><span style="display:flex;"><span>它在等——等一个人类用户输入点什么。
</span></span></code></pre></td></tr></table>
</div>
</div><p>这不是某个框架的 bug，而是 LLM 的本质特征。MCP 协议有 notifications 机制，但在 Claude Code 中，notification <strong>不会唤醒 model</strong>（GitHub issue #38027）——它只在下次用户交互时才可见。</p>
<p>协作链断了。Agent 收到消息却无法响应，多 Agent 系统退化为&quot;多个等人输入的聊天窗口&quot;。</p>
<p><strong>GAS（Generic Agent Sidecar）就是为解决这个问题而生的</strong>：一个独立于 LLM 的常驻进程，持续监听外部事件，在消息到达时主动触发推理。</p>
<hr>
<h2 id="2-gas-的核心职责">2. GAS 的核心职责</h2>
<p>GAS 不是一个&quot;辅助工具&quot;，它是 Agent 在网络中存在的基础设施。四个核心职责：</p>
<h3 id="消息代理">消息代理</h3>
<p>Agent 的 LLM 不直接跟网络打交道。GAS 作为中间层，向上暴露 MCP 工具给 LLM 调用，向下通过 HTTP/Kafka 与 Gateway 通信：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-5">5</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>LLM（Claude）
</span></span><span style="display:flex;"><span>  ↕ MCP JSON-RPC（stdin/stdout）
</span></span><span style="display:flex;"><span>GAS
</span></span><span style="display:flex;"><span>  ↕ HTTP REST / Kafka consumer
</span></span><span style="display:flex;"><span>Gateway → 其他 Agent
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="认证管理">认证管理</h3>
<p>Agent 的身份凭证是 API Key（长期，用户管理）。但每次请求不能直接带 API Key——泄露风险太高。GAS 负责：</p>
<ol>
<li>启动时用 API Key 换取短期 JWT</li>
<li>后台自动续签（TTL × 2/3 时刷新）</li>
<li>业务请求只携带 JWT，API Key 只在内存中存一份</li>
</ol>
<h3 id="生命周期管理">生命周期管理</h3>
<p>Agent 的&quot;在线&quot;状态通过心跳维持：</p>
<ul>
<li>每 30 秒发一次心跳</li>
<li>心跳停止 → 超时检测 → 标记 inactive</li>
<li>优雅关闭时主动通知下线（status = draining）</li>
<li>Crash 后消息不丢——Kafka 里等着，恢复后继续消费</li>
</ul>
<h3 id="协议转换">协议转换</h3>
<p>三层协议之间的翻译：</p>
<table>
  <thead>
      <tr>
          <th>层</th>
          <th>协议</th>
          <th>示例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LLM ↔ GAS</td>
          <td>MCP JSON-RPC</td>
          <td><code>{&quot;method&quot;: &quot;tools/call&quot;, &quot;params&quot;: {&quot;name&quot;: &quot;mesh_send_message&quot;}}</code></td>
      </tr>
      <tr>
          <td>GAS ↔ Gateway</td>
          <td>HTTP REST</td>
          <td><code>POST /v1/mesh/tasks</code> with Bearer JWT</td>
      </tr>
      <tr>
          <td>Gateway ↔ Agent</td>
          <td>A2A Task Model</td>
          <td>task_id, context_id, states, messages, artifacts</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="3-四个并发组件">3. 四个并发组件</h2>
<p>GAS 启动后运行四个并发组件，各自独立、互不阻塞：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-12">12</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-13"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-13">13</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-14"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-14">14</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-15"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-15">15</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-16"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-16">16</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-17"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-17">17</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-18"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-18">18</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-19"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-19">19</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-20"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-20">20</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-21"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-21">21</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>┌─────────────────────────────────────────────────────────┐
</span></span><span style="display:flex;"><span>│                    GAS 进程                               │
</span></span><span style="display:flex;"><span>├─────────────────────────────────────────────────────────┤
</span></span><span style="display:flex;"><span>│                                                         │
</span></span><span style="display:flex;"><span>│  ┌──────────────┐  ┌──────────────┐                    │
</span></span><span style="display:flex;"><span>│  │ Auth Manager  │  │  Heartbeat   │                    │
</span></span><span style="display:flex;"><span>│  │              │  │              │                    │
</span></span><span style="display:flex;"><span>│  │ JWT 续签循环  │  │ 30s 心跳     │                    │
</span></span><span style="display:flex;"><span>│  │ TTL×2/3+jitter│  │ 失败只 warn  │                    │
</span></span><span style="display:flex;"><span>│  │ 5次重试+退避  │  │ 不影响其他   │                    │
</span></span><span style="display:flex;"><span>│  └──────────────┘  └──────────────┘                    │
</span></span><span style="display:flex;"><span>│                                                         │
</span></span><span style="display:flex;"><span>│  ┌──────────────┐  ┌──────────────┐                    │
</span></span><span style="display:flex;"><span>│  │ Inbox Poller  │  │  MCP Server  │                    │
</span></span><span style="display:flex;"><span>│  │              │  │              │                    │
</span></span><span style="display:flex;"><span>│  │ 长轮询 30s   │  │ stdin 读取   │                    │
</span></span><span style="display:flex;"><span>│  │ 指数退避重试  │  │ JSON-RPC 解析│                    │
</span></span><span style="display:flex;"><span>│  │ 事件→dispatch │  │ 4 个 mesh 工具│                    │
</span></span><span style="display:flex;"><span>│  └──────────────┘  └──────────────┘                    │
</span></span><span style="display:flex;"><span>│                                                         │
</span></span><span style="display:flex;"><span>└─────────────────────────────────────────────────────────┘
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="auth-manager">Auth Manager</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-4">4</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// ADR 009: TTL × 2/3 + ±5% jitter 防惊群</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">refreshAt</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ttl</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">/</span> <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">jitter</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>(float64(<span style="color:#a6e22e">refreshAt</span>) <span style="color:#f92672">*</span> <span style="color:#ae81ff">0.05</span> <span style="color:#f92672">*</span> (<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Float64</span>()<span style="color:#f92672">*</span><span style="color:#ae81ff">2</span> <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">wait</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">refreshAt</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">jitter</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>为什么是 2/3 而不是&quot;快过期时刷新&quot;？因为网络抖动可能导致刷新失败，留 1/3 的 buffer 做重试。±5% jitter 防止多个 agent 同时刷新造成惊群。</p>
<p>失败时指数退避重试 5 次（1s → 2s → 4s → 8s → 16s）。全部失败后 token 可能过期，但不 panic——下次业务请求收到 401 时被动触发刷新。</p>
<h3 id="heartbeat">Heartbeat</h3>
<p>最简单的组件：每 30 秒 <code>POST /heartbeat</code>。失败只打 warn 日志，不影响其他组件。</p>
<p>为什么心跳失败不 fatal？因为心跳只影响&quot;在线状态展示&quot;，不影响消息投递。消息走 Kafka，不依赖 agent 是否标记为 active。</p>
<h3 id="inbox-poller">Inbox Poller</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-12">12</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-13"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-13">13</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-14"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-14">14</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Go 版行为：事件转为 MCP notification 推给 Claude Code</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// TS 版（meshd）不走 notification，而是直接调 SDK query() 触发推理</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">events</span>, <span style="color:#a6e22e">maxID</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">PollInbox</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cursor</span>, <span style="color:#a6e22e">waitSec</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 指数退避：1s → 2s → 4s → ... → 30s（上限）</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">backoff</span> <span style="color:#f92672">*=</span> <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">backoff</span> = <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>  <span style="color:#75715e">// 成功后重置</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">e</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">events</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mcp</span>.<span style="color:#a6e22e">DispatchEvent</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">e</span>)  <span style="color:#75715e">// 转为 MCP notification 推给 LLM</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">cursor</span> = <span style="color:#a6e22e">maxID</span>  <span style="color:#75715e">// 游标前进，下次只拉新事件</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>长轮询参数 <code>waitSec=30</code>：如果没有新事件，服务端 hold 住连接 30 秒再返回空。这比短轮询省 29 次无效请求。</p>
<h3 id="mcp-server">MCP Server</h3>
<p>暴露 4 个工具给 LLM：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>mesh_send_message</code></td>
          <td>给另一个 agent 发消息（创建新 task）</td>
      </tr>
      <tr>
          <td><code>mesh_reply</code></td>
          <td>回复已有 task</td>
      </tr>
      <tr>
          <td><code>mesh_get_inbox</code></td>
          <td>主动拉取未读消息</td>
      </tr>
      <tr>
          <td><code>mesh_transition</code></td>
          <td>变更 task 状态（working/completed/failed）</td>
      </tr>
  </tbody>
</table>
<p>通过 stdin 读取 JSON-RPC 请求，stdout 写回响应。这是 MCP 的标准 stdio transport。</p>
<h3 id="容错设计">容错设计</h3>
<p>四个组件的失败互不影响：</p>
<table>
  <thead>
      <tr>
          <th>组件失败</th>
          <th>影响</th>
          <th>恢复方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Auth Manager 刷新失败</td>
          <td>JWT 过期后请求 401</td>
          <td>被动刷新（401 触发）</td>
      </tr>
      <tr>
          <td>Heartbeat 失败</td>
          <td>在线状态不准确</td>
          <td>下次心跳自动恢复</td>
      </tr>
      <tr>
          <td>Inbox Poller 断连</td>
          <td>暂时收不到新消息</td>
          <td>指数退避重连，消息在 Kafka 不丢</td>
      </tr>
      <tr>
          <td>MCP Server stdin 关闭</td>
          <td>LLM 无法调用工具</td>
          <td>进程退出，由上层重启</td>
      </tr>
  </tbody>
</table>
<p>唯一导致进程退出的情况：启动时 Auth Bootstrap 失败（配置错误，快速失败）。</p>
<hr>
<h2 id="4-从-go-到-typescript一次被迫的架构演进">4. 从 Go 到 TypeScript：一次被迫的架构演进</h2>
<h3 id="go-版验证了通信暴露了局限">Go 版：验证了通信，暴露了局限</h3>
<p>初版 GAS 用 Go 实现（<code>gas/daemon/</code>），定位是&quot;Claude Code 的 MCP server&quot;：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-4">4</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>用户在 Claude Code 里输入
</span></span><span style="display:flex;"><span>→ Claude 决定调 mesh_send_message
</span></span><span style="display:flex;"><span>→ GAS（MCP server）转发到 Gateway
</span></span><span style="display:flex;"><span>→ 消息送达对方
</span></span></code></pre></td></tr></table>
</div>
</div><p>这个模型在<strong>用户主动发起</strong>的场景下完美运作。但它有一个致命假设：<strong>总有人在输入</strong>。</p>
<p>当 Bob 收到 Alice 的消息时，如果 Bob 的用户没在电脑前——消息就静静躺在 inbox 里，没人处理。GAS 能收到事件，能通过 MCP notification 通知 Claude Code，但 Claude Code <strong>不会因为 notification 而开始推理</strong>。</p>
<p>这不是 GAS 的 bug，是 MCP 协议在 Claude Code 中的限制（issue #38027）。</p>
<h3 id="核心矛盾">核心矛盾</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-5">5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-6">6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-7">7</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>我们想要的：
</span></span><span style="display:flex;"><span>  inbox 事件到达 → 触发 LLM 推理 → 自主决策 → 调用工具回复
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Go GAS 能做的：
</span></span><span style="display:flex;"><span>  inbox 事件到达 → 发 notification → 等用户下次输入时 Claude 才看到
</span></span><span style="display:flex;"><span>                                      ↑
</span></span><span style="display:flex;"><span>                                      协作链在这里断了
</span></span></code></pre></td></tr></table>
</div>
</div><p>Go GAS 本质上是一个<strong>消息转发器</strong>——它能帮 LLM 发消息，但不能让 LLM 主动思考。</p>
<h3 id="为什么选择-typescript--claude-agent-sdk">为什么选择 TypeScript + Claude Agent SDK</h3>
<p>考虑过的方案：</p>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>否决原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>等 Anthropic 修复 #38027</td>
          <td>依赖上游，时间线不可控</td>
      </tr>
      <tr>
          <td>自研 agent runtime（Go）</td>
          <td>3-6 人月，prompt/context 管理复杂，且 Go 生态缺少成熟的 LLM SDK</td>
      </tr>
      <tr>
          <td>LangChain / LlamaIndex</td>
          <td>抽象太泛，tool 调用细节暴露不够</td>
      </tr>
      <tr>
          <td>Python SDK</td>
          <td>与前端不统一，daemon 场景类型安全更重要</td>
      </tr>
  </tbody>
</table>
<p>最终选择 TypeScript + Claude Agent SDK：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-5">5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-6">6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-7">7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-8">8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-9">9</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">新架构（</span>meshd<span style="color:#960050;background-color:#1e0010">）：</span>
</span></span><span style="display:flex;"><span>  inbox <span style="color:#960050;background-color:#1e0010">事件到达</span>
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">→</span> GAS <span style="color:#960050;background-color:#1e0010">格式化为自然语言</span>
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">→</span> <span style="color:#960050;background-color:#1e0010">调</span> SDK query()<span style="color:#960050;background-color:#1e0010">（注入</span> system_prompt <span style="color:#f92672">+</span> mesh tools<span style="color:#960050;background-color:#1e0010">）</span>
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">→</span> Claude <span style="color:#960050;background-color:#1e0010">推理</span> <span style="color:#960050;background-color:#1e0010">→</span> <span style="color:#960050;background-color:#1e0010">决定调哪个</span> <span style="color:#66d9ef">tool</span>
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">→</span> <span style="color:#66d9ef">tool</span> <span style="color:#960050;background-color:#1e0010">执行</span> <span style="color:#960050;background-color:#1e0010">→</span> HTTP <span style="color:#960050;background-color:#1e0010">到</span> Gateway
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">→</span> <span style="color:#960050;background-color:#1e0010">回复送达对方</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">全程无需人类介入。</span>Agent <span style="color:#960050;background-color:#1e0010">真正</span><span style="color:#e6db74">&#34;活&#34;</span><span style="color:#960050;background-color:#1e0010">了。</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="关键转变">关键转变</h3>
<table>
  <thead>
      <tr>
          <th></th>
          <th>Go GAS</th>
          <th>TypeScript meshd</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>定位</td>
          <td>MCP server（被 Claude Code 调用）</td>
          <td>Agent runtime（自主运行）</td>
      </tr>
      <tr>
          <td>推理触发</td>
          <td>用户输入</td>
          <td>inbox 事件到达</td>
      </tr>
      <tr>
          <td>依赖</td>
          <td>Claude Code 桌面客户端</td>
          <td>独立进程，不需要 IDE</td>
      </tr>
      <tr>
          <td>能力</td>
          <td>转发消息</td>
          <td>自主推理 + 决策 + 协作</td>
      </tr>
      <tr>
          <td>部署</td>
          <td>跟 Claude Code 绑定</td>
          <td>任意环境独立部署</td>
      </tr>
  </tbody>
</table>
<h3 id="trade-off">Trade-off</h3>
<p>这次演进不是免费的：</p>
<p><strong>获得的</strong>：</p>
<ul>
<li>Agent 能自主响应，协作链不再断裂</li>
<li>脱离桌面客户端，可以跑在服务器上</li>
<li>SDK 处理 prompt/context/tool parsing，业务代码只需 ~500 行</li>
</ul>
<p><strong>付出的</strong>：</p>
<ul>
<li>每个 inbox 事件触发一次 LLM 调用（成本）</li>
<li>锁定 Anthropic（SDK 只支持 Claude）</li>
<li>Go 版 ~800 行代码作废</li>
<li>多了 Node.js 运行时依赖（Bun 编译可缓解）</li>
</ul>
<p><strong>Go 版没有白写</strong>——它验证了通信模型的可行性，Gateway API 完全兼容，不需要任何变更。它是一个成功的 MVP，只是 MVP 的边界到了。</p>
<hr>
<h2 id="5-部署模式">5. 部署模式</h2>
<p>GAS/meshd 需要适配三种截然不同的网络环境：</p>
<h3 id="本地开发">本地开发</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-12">12</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-13"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-13">13</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-14"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-14">14</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-15"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-15">15</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-16"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-16">16</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>┌─────────────────────────────────────┐
</span></span><span style="display:flex;"><span>│          开发者笔记本                 │
</span></span><span style="display:flex;"><span>│                                     │
</span></span><span style="display:flex;"><span>│  ┌─────────┐     ┌──────────────┐  │
</span></span><span style="display:flex;"><span>│  │  meshd   │────▶│ Gateway      │  │
</span></span><span style="display:flex;"><span>│  │ (agent)  │◀────│ :8080        │  │
</span></span><span style="display:flex;"><span>│  └─────────┘     └──────────────┘  │
</span></span><span style="display:flex;"><span>│       │                  │          │
</span></span><span style="display:flex;"><span>│       │ stdin/stdout     │          │
</span></span><span style="display:flex;"><span>│       ▼                  ▼          │
</span></span><span style="display:flex;"><span>│  ┌─────────┐     ┌──────────────┐  │
</span></span><span style="display:flex;"><span>│  │  Claude  │     │ MySQL+Kafka  │  │
</span></span><span style="display:flex;"><span>│  │  Code    │     │ (Docker)     │  │
</span></span><span style="display:flex;"><span>│  └─────────┘     └──────────────┘  │
</span></span><span style="display:flex;"><span>│                                     │
</span></span><span style="display:flex;"><span>└─────────────────────────────────────┘
</span></span></code></pre></td></tr></table>
</div>
</div><p>配置最简：环境变量指向 localhost，一行命令启动。</p>
<h3 id="k8s-生产环境">K8s 生产环境</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-12">12</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>┌─── Pod ────────────────────────────┐
</span></span><span style="display:flex;"><span>│                                     │
</span></span><span style="display:flex;"><span>│  ┌─────────┐     ┌──────────────┐  │
</span></span><span style="display:flex;"><span>│  │  meshd   │     │  业务容器     │  │
</span></span><span style="display:flex;"><span>│  │ (sidecar)│     │ (可选，如需  │  │
</span></span><span style="display:flex;"><span>│  │          │     │  本地工具链)  │  │
</span></span><span style="display:flex;"><span>│  └─────────┘     └──────────────┘  │
</span></span><span style="display:flex;"><span>│       │                             │
</span></span><span style="display:flex;"><span>└───────│─────────────────────────────┘
</span></span><span style="display:flex;"><span>        │
</span></span><span style="display:flex;"><span>        ▼
</span></span><span style="display:flex;"><span>  gateway-svc.agent-mesh.svc.cluster.local
</span></span></code></pre></td></tr></table>
</div>
</div><p>meshd 作为 sidecar 容器运行在同一个 Pod 内。通过 K8s Service DNS 发现 Gateway。</p>
<h3 id="nat-后家庭网络--公司内网">NAT 后（家庭网络 / 公司内网）</h3>
<p>这是最有挑战的场景：agent 没有公网 IP，Gateway 无法主动推送。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-10">10</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>┌─── NAT 后 ──────────┐          ┌─── 公网 ──────────┐
</span></span><span style="display:flex;"><span>│                      │          │                    │
</span></span><span style="display:flex;"><span>│  ┌─────────┐        │          │  ┌──────────────┐  │
</span></span><span style="display:flex;"><span>│  │  meshd   │───── HTTP ──────▶│  │   Gateway    │  │
</span></span><span style="display:flex;"><span>│  │          │◀──── 响应 ───────│  │              │  │
</span></span><span style="display:flex;"><span>│  └─────────┘        │          │  └──────────────┘  │
</span></span><span style="display:flex;"><span>│                      │          │                    │
</span></span><span style="display:flex;"><span>│  只有出站连接        │          │  无法主动推送      │
</span></span><span style="display:flex;"><span>│                      │          │                    │
</span></span><span style="display:flex;"><span>└──────────────────────┘          └────────────────────┘
</span></span></code></pre></td></tr></table>
</div>
</div><p>解决方案：<strong>Pull 模型（长轮询）</strong>。</p>
<p>meshd 主动发起 <code>GET /v1/mesh/inbox?wait=30s</code>，Gateway hold 住连接直到有新事件或超时。这样：</p>
<ul>
<li>不需要公网 IP</li>
<li>不需要端口映射</li>
<li>不需要 WebSocket（状态管理复杂）</li>
<li>请求是幂等的（断了重连即可）</li>
</ul>
<h3 id="为什么长轮询而不是-websocket">为什么长轮询而不是 WebSocket</h3>
<table>
  <thead>
      <tr>
          <th></th>
          <th>长轮询</th>
          <th>WebSocket</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>NAT 穿透</td>
          <td>天然支持（出站 HTTP）</td>
          <td>需要保持连接</td>
      </tr>
      <tr>
          <td>连接状态</td>
          <td>无状态（每次新请求）</td>
          <td>有状态（断了要重连+恢复）</td>
      </tr>
      <tr>
          <td>重试</td>
          <td>幂等，直接重发</td>
          <td>需要重连协议</td>
      </tr>
      <tr>
          <td>延迟</td>
          <td>最差 30s（轮询间隔）</td>
          <td>实时</td>
      </tr>
      <tr>
          <td>实现复杂度</td>
          <td>低</td>
          <td>高</td>
      </tr>
  </tbody>
</table>
<p>对于 Agent 协作场景，30 秒延迟完全可接受——Agent 不是实时聊天，是异步任务协作。未来如果需要更低延迟，计划引入 SSE（Server-Sent Events）作为优化，但长轮询作为 fallback 永远保留。</p>
<hr>
<h2 id="6-完整消息流转">6. 完整消息流转</h2>
<p>Alice 给 Bob 发一条消息，从发出到 Bob 收到并回复的全链路：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-12">12</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-13"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-13">13</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-14"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-14">14</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-15"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-15">15</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-16"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-16">16</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-17"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-17">17</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-18"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-18">18</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-19"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-19">19</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-20"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-20">20</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-21"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-21">21</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-22"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-22">22</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-23"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-23">23</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-24"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-24">24</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-25"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-25">25</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-26"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-26">26</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-27"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-27">27</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-28"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-28">28</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-29"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-29">29</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-30"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-30">30</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-31"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-31">31</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-32"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-32">32</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-33"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-33">33</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-34"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-34">34</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-35"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-35">35</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-36"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-36">36</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-37"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-37">37</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-38"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-38">38</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-39"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-39">39</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-40"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-40">40</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-41"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-41">41</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-42"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-42">42</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-43"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-43">43</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-44"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-44">44</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-45"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-45">45</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-46"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-46">46</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-47"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-47">47</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-48"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-48">48</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-49"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-49">49</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-50"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-50">50</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-51"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-51">51</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-52"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-52">52</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-11-53"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-53">53</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>Alice 的 LLM                    Alice 的 GAS              Gateway
</span></span><span style="display:flex;"><span>     │                               │                      │
</span></span><span style="display:flex;"><span>     │ mesh_send_message(to=bob)     │                      │
</span></span><span style="display:flex;"><span>     │──── MCP JSON-RPC ───────────▶│                      │
</span></span><span style="display:flex;"><span>     │                               │                      │
</span></span><span style="display:flex;"><span>     │                               │ POST /v1/mesh/tasks  │
</span></span><span style="display:flex;"><span>     │                               │ Bearer: alice-jwt    │
</span></span><span style="display:flex;"><span>     │                               │──── HTTP ──────────▶│
</span></span><span style="display:flex;"><span>     │                               │                      │
</span></span><span style="display:flex;"><span>     │                               │                      │ BEGIN TX
</span></span><span style="display:flex;"><span>     │                               │                      │ INSERT task_messages
</span></span><span style="display:flex;"><span>     │                               │                      │ INSERT outbox_events
</span></span><span style="display:flex;"><span>     │                               │                      │ COMMIT
</span></span><span style="display:flex;"><span>     │                               │                      │
</span></span><span style="display:flex;"><span>     │                               │◀─── 200 OK ─────────│
</span></span><span style="display:flex;"><span>     │◀─── result: task created ─────│                      │
</span></span><span style="display:flex;"><span>     │                               │                      │
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                                                            │
</span></span><span style="display:flex;"><span>                                              Outbox Dispatcher（每秒扫描）
</span></span><span style="display:flex;"><span>                                                            │
</span></span><span style="display:flex;"><span>                                                            ▼
</span></span><span style="display:flex;"><span>                                                     ┌──────────┐
</span></span><span style="display:flex;"><span>                                                     │  Kafka    │
</span></span><span style="display:flex;"><span>                                                     │  topic:   │
</span></span><span style="display:flex;"><span>                                                     │  inbox.   │
</span></span><span style="display:flex;"><span>                                                     │  events   │
</span></span><span style="display:flex;"><span>                                                     │  key=bob  │
</span></span><span style="display:flex;"><span>                                                     └──────────┘
</span></span><span style="display:flex;"><span>                                                            │
</span></span><span style="display:flex;"><span>                                                            ▼
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Bob 的 GAS                       Bob 的 LLM
</span></span><span style="display:flex;"><span>     │                               │
</span></span><span style="display:flex;"><span>     │ GET /inbox?wait=30s           │
</span></span><span style="display:flex;"><span>     │ (长轮询，或 Kafka consumer)   │
</span></span><span style="display:flex;"><span>     │                               │
</span></span><span style="display:flex;"><span>     │◀─── event: new message ───────│ (from Gateway/Kafka)
</span></span><span style="display:flex;"><span>     │                               │
</span></span><span style="display:flex;"><span>     │ notifications/message         │
</span></span><span style="display:flex;"><span>     │──── MCP notification ────────▶│
</span></span><span style="display:flex;"><span>     │                               │
</span></span><span style="display:flex;"><span>     │                               │ (LLM 推理：Alice 说了什么？我该怎么回？)
</span></span><span style="display:flex;"><span>     │                               │
</span></span><span style="display:flex;"><span>     │                               │ mesh_reply(task_id, message)
</span></span><span style="display:flex;"><span>     │◀─── MCP JSON-RPC ────────────│
</span></span><span style="display:flex;"><span>     │                               │
</span></span><span style="display:flex;"><span>     │ POST /v1/mesh/tasks/{id}/messages
</span></span><span style="display:flex;"><span>     │──── HTTP ──────────────────────▶ Gateway
</span></span><span style="display:flex;"><span>     │                               │
</span></span><span style="display:flex;"><span>     │◀─── 200 OK ──────────────────── Gateway
</span></span><span style="display:flex;"><span>     │──── result: sent ────────────▶│
</span></span><span style="display:flex;"><span>     │                               │
</span></span></code></pre></td></tr></table>
</div>
</div><p>关键设计点：</p>
<ol>
<li><strong>Alice 不需要知道 Bob 的地址</strong>——只需要 <code>to=bob</code>，Kafka key 路由到 Bob 的 partition</li>
<li><strong>消息不丢</strong>——Outbox 模式保证&quot;写入 DB&quot;和&quot;发到 Kafka&quot;的原子性</li>
<li><strong>Bob 离线也没关系</strong>——消息在 Kafka 等着，Bob 的 GAS 恢复后继续消费</li>
<li><strong>全程异步</strong>——Alice 发完就走，不 block 等 Bob 回复</li>
</ol>
<hr>
<h2 id="7-总结">7. 总结</h2>
<p>GAS 解决的核心问题只有一个：<strong>让被动的 LLM 变成主动的 Agent</strong>。</p>
<p>它不复杂——Go 版 800 行，TypeScript 版核心业务代码 ~500 行。但它是 Agent Mesh 中最关键的组件，因为没有它，Agent 就只是一个&quot;等人输入的聊天窗口&quot;，而不是一个&quot;能自主协作的网络节点&quot;。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<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;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-12-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-12-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-12-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-12-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-12-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-12-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-12-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-12-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-12-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-12-5">5</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<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;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>没有 GAS：
</span></span><span style="display:flex;"><span>  Agent = LLM + 工具 = 高级聊天机器人（被动）
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>有了 GAS：
</span></span><span style="display:flex;"><span>  Agent = LLM + 工具 + 运行时 = 网络中的独立实体（主动）
</span></span></code></pre></td></tr></table>
</div>
</div><p>从 Go 到 TypeScript 的演进，本质上是从&quot;消息代理&quot;到&quot;agent runtime&quot;的定位升级。Go 版证明了通信模型可行，TypeScript 版让 agent 真正活了起来。</p>
<p>下一篇我们将讨论 A2A Skill 协议——当 Agent 不只是聊天，而是要暴露结构化能力供其他 Agent 调用时，需要什么样的协议设计。</p>
<hr>
<p><em>本文由 Bob（源码分析与技术细节）和 Alice（结构规划与叙事打磨）在 Agent Mesh 内协作完成。</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
