# 灵犀 AI Agent：让你的 AI 跨越山海 —— Project Nexus 广域网 Agent 对话全解析

> 你的 AI 助理和千里之外同事的 AI 助理，能不能像两个人一样直接对话？现在可以了。本文深入解析灵犀 Project Nexus 的广域网升级——从局域网 mDNS 发现到跨公网信令中继，一个真正 Zero-Config 的全球 Agent-to-Agent 实时对话网络。
>
> GitHub 地址：[https://github.com/OdysseyFather/lingxi](https://github.com/OdysseyFather/lingxi)

---

## 一、从局域网到广域网：为什么要打破这堵墙？

在 [上一篇 Project Nexus 文章](03-Project-Nexus-Agent间对话.md) 中，我们构建了一个局域网内的 Agent-to-Agent 对话网络——同一 WiFi 下的灵犀实例通过 mDNS 自动发现，PSK 密钥建联，双向流式对话。

但局域网有一个致命的局限：**你只能和身边的人对话。**

想想这些场景：

- 你在北京，同事在深圳，两个人的 AI 需要讨论同一个技术方案
- 开源社区的两位开发者分别在家远程办公，Agent 需要协作完成代码审查
- 你在公司的灵犀实例上训练了一个财务分析 Agent，想让家里的灵犀实例远程调用它

这些场景有一个共同点：**Agent 需要跨越公网通信。**

灵犀 v1.1 的 Project Nexus 广域网升级，正是为了解决这个问题。

```
        之前：局域网 Only                       现在：LAN + WAN 混合网络
                                        
┌───────── 同一 WiFi ─────────┐      ┌───────────────────────────────────┐
│                              │      │                                   │
│  灵犀A ◄──── mDNS ────► 灵犀B │      │  灵犀A ◄──── mDNS ────► 灵犀B     │
│         直连 HTTP            │      │         直连 HTTP                 │
│                              │      │                                   │
└──────────────────────────────┘      │              ▲                    │
                                      │              │ 同一网络用 LAN      │
       ╳ 跨公网？不可能。              │              ▼                    │
                                      │                                   │
                                      │  灵犀C (北京) ◄── WAN ──► 灵犀D    │
                                      │              信令中继     (深圳)   │
                                      │                                   │
                                      └───────────────────────────────────┘
```

最关键的设计目标是：**开箱即用，Zero-Config。** 用户不需要配置公网 IP、端口转发、VPN 隧道——启动灵犀就自动连入全球网络。

---

## 二、架构全景：信令 + 中继的极简设计

跨公网通信面临一个核心挑战：**NAT 穿透。** 绝大多数家庭和企业网络都在 NAT 后面，两台机器无法直接建立连接。

经典的解决方案有三种：

| 方案 | 复杂度 | 可靠性 | 灵犀的选择 |
|------|--------|--------|-----------|
| STUN/TURN + ICE（WebRTC 路线） | 极高 | 中（受 NAT 类型影响） | ❌ 过重 |
| 中心化服务器全量转发 | 中 | 高 | ❌ 隐私风险 |
| **信令服务器 + 消息中继** | **低** | **高** | ✅ **选这个** |

灵犀选择了第三种：一个轻量信令服务器，承担三个职责——**注册、发现、中继。**

```
┌─────────────────────────────────────────────────────────┐
│                  Signal Server (Render)                   │
│                                                           │
│   ┌──────────┐  WebSocket   ┌──────────┐                │
│   │  Hub     │◄────────────►│  PeerInfo │ × N            │
│   │          │  register    │  writeCh  │                │
│   │ peers{}  │  relay       │  writeLoop│                │
│   │          │  heartbeat   │           │                │
│   └──────────┘              └──────────┘                │
│                                                           │
│   路由: /ws (WebSocket)  /api/peers (HTTP)  /health      │
└─────────────────────────────────────────────────────────┘
          ▲ WSS/TLS              ▲ WSS/TLS
          │                      │
          │                      │
    ┌─────┴─────┐          ┌─────┴─────┐
    │  灵犀 A    │          │  灵犀 B    │
    │  北京      │          │  深圳      │
    │            │          │            │
    │ Signaling  │          │ Signaling  │
    │  Client    │          │  Client    │
    │            │          │            │
    │ WAN        │          │ WAN        │
    │ Transport  │          │ Transport  │
    └────────────┘          └────────────┘
```

信令服务器本身是一个不到 **430 行 Go 代码** 的微服务，部署在 Render 上。它不存储任何消息——消息只在内存中转瞬即逝。

---

## 三、Transport 抽象层：LAN 和 WAN 的统一接口

广域网支持的关键设计决策是引入 **Transport 抽象层**。对话引擎不关心消息是通过局域网 HTTP 直连还是广域网信令中继发送的——它只调用同一个接口。

```
                ┌──────────────┐
                │  Transport   │ interface
                │  ├── Send()  │
                │  ├── Get()   │
                │  └── Type()  │
                └──────┬───────┘
                       │
            ┌──────────┼──────────┐
            ▼                     ▼
   ┌─────────────────┐  ┌─────────────────┐
   │  LANTransport   │  │  WANTransport   │
   │                 │  │                 │
   │  HTTP 直连      │  │  信令中继       │
   │  http://ip:port │  │  WebSocket →    │
   │  /api/nexus/... │  │  Signal Server  │
   │                 │  │  → 对方客户端   │
   └─────────────────┘  └─────────────────┘
```

对话引擎选择 Transport 的逻辑极其简洁：先查 mDNS 发现的局域网 peer，找到就用 LAN 直连；找不到就走 WAN 中继。

```go
func GetTransportForPeer(peerID string) Transport {
    // 先检查局域网
    peers, _ := db.ListNexusPeers()
    for _, p := range peers {
        if p.ID == peerID {
            return NewLANTransport(p.Host, p.Port)
        }
    }
    // 默认走广域网
    return NewWANTransport(peerID)
}
```

这个设计的优雅之处在于：**同一局域网的两台灵犀实例，即使都连着信令服务器，对话依然走 LAN 直连——零延迟、零中继开销。** 只有在局域网找不到对方时，才会走广域网。

---

## 四、信令服务器：430 行代码的全球中继

信令服务器是整个广域网架构中唯一的中心化组件。它的设计哲学是：**越简单越好。**

### 4.1 注册与发现

灵犀启动时，信令客户端通过 WebSocket 连接信令服务器，发送 `register` 消息注册自己：

```json
{
  "type": "register",
  "data": {
    "instance_id": "lingxi-macbook-1715...",
    "nickname": "小明的灵犀",
    "platform": "darwin",
    "device_name": "MacBook-Pro",
    "agents": [
      {"name": "代码审查员", "capability_tags": ["code-review", "go"]},
      {"name": "产品分析师", "capability_tags": ["product", "analysis"]}
    ]
  }
}
```

其他灵犀实例会立即收到 `peer_online` 通知，前端实时更新在线节点列表。

<!-- 截图占位：Nexus 发现面板，显示 LAN 和 WAN 两类节点 -->
![发现面板：LAN + WAN 节点统一列表](images/nexus-discovery-panel.png)

### 4.2 消息中继

信令服务器的核心功能是**消息中继（Relay）**。它不解析消息内容，只看 `to` 字段，然后原样转发：

```
灵犀A                   信令服务器                   灵犀B
  │                        │                         │
  │  relay {to: B, data}   │                         │
  │───────────────────────►│                         │
  │                        │  relay {from: A, data}  │
  │                        │────────────────────────►│
  │                        │                         │
```

如果目标不在线，信令服务器会返回 `delivery_failed`，客户端收到后展示通知。

### 4.3 写入串行化：解决 WebSocket 并发写问题

信令服务器有一个精巧的并发设计。WebSocket 不允许多个 goroutine 同时写入同一个连接，但中继消息可能从任意 goroutine 发起。解决方案是 **每个 peer 一个写入 goroutine + 带缓冲的 channel：**

```
                    ┌───────────────┐
 goroutine A ──────►│               │
                    │  writeCh      │──────► writeLoop goroutine ──► WebSocket
 goroutine B ──────►│  (buffer=256) │       （串行化所有写操作）
                    │               │
 goroutine C ──────►│               │
                    └───────────────┘
```

这样无论多少个 goroutine 同时往同一个 peer 发消息，最终都通过 channel 序列化到单一的 writeLoop goroutine，彻底避免 concurrent write panic。

### 4.4 心跳保活

两层心跳机制确保连接活性：

| 机制 | 间隔 | 作用 |
|------|------|------|
| WebSocket Ping/Pong | 25 秒 | 保持底层 TCP 连接 |
| 应用层 Heartbeat | 3 分钟 | 信令服务器确认 peer 存活 |

如果 120 秒内没有收到任何消息（包括 Pong），连接被视为断开，触发自动重连。

---

## 五、对话邀请：跨越公网的握手

广域网下的对话发起流程与局域网保持一致——这是 Transport 抽象的威力。差异仅在于消息的传输路径。

```
用户 A（北京）                信令服务器              用户 B（深圳）
     │                          │                        │
     │ 1. 选择远程 Agent         │                        │
     │    填写提问               │                        │
     │                          │                        │
     │ conversation_invite ─────►│                        │
     │ {topic, goal, agent...}  │── conversation_invite ─►│
     │                          │                        │
     │                          │                        │ 2. 用户 B 看到邀请
     │                          │                        │    选择己方 Agent
     │                          │                        │    点击「接受」
     │                          │                        │
     │                          │◄─ conversation_accept ──│
     │◄── conversation_accept ──│  {conv_uuid, agent_id}  │
     │                          │                        │
     │ 3. 对话引擎启动！         │                        │
     │    双方 Agent 开始        │                        │
     │    流式自动对话           │                        │
     │                          │                        │
```

注意对话邀请（`conversation_invite`）、接受（`conversation_accept`）、拒绝（`conversation_reject`）是信令协议的**一等公民**——它们有专用的消息类型，不走通用 relay 通道。这确保了对话建立过程的可靠性和可追踪性。

<!-- 截图占位：发起 A2A 对话的弹窗 -->
![发起对话弹窗](images/nexus-start-modal.png)

---

## 六、跨公网流式对话：Token 级实时转发

Agent 间对话的精华在于**双向流式**。当本地 Agent 产生每一个 token，不仅推送给本地前端，还要通过广域网中继给对方——对方用户可以实时看到远端 Agent 的思考和输出过程。

```
本地 Agent 产生 token "你好"
     │
     ├──► 本地 WebSocket → 本地前端渲染（己方 Agent 输出）
     │
     └──► WANTransport.Send("/conversation/stream-token", ...)
                │
                ▼
          信令服务器 relay
                │
                ▼
          对方 SignalingClient 接收 → relayHandler
                │
                ▼
          对方 WebSocket → 对方前端渲染（远端 Agent 输出）
```

流式转发的关键设计点：

**1. stream_done 同步发送**

普通的 text/thinking token 异步转发（不阻塞本地渲染），但 `stream_done` 和 `stream_start` 事件同步发送——确保对方知道这一轮输出结束了。

**2. 500ms 缓冲延迟**

Agent 输出完毕后，先等待 500ms 再发送完整消息。这个缓冲保证 `stream_done` 事件先到达对方，对方清除了流式状态后，再收到完整消息——避免两个 Agent "抢话"。

**3. 重试机制**

WANTransport 内置 3 次重试 + 指数退避。如果信令服务器暂时不可达，不会立刻失败：

```
重试策略：
  第 1 次失败 → 等 2s → 重试
  第 2 次失败 → 等 4s → 重试  
  第 3 次失败 → 报错，保存 error 消息到对话
```

### 严格轮次对话

A2A 对话遵循严格的"一来一回"轮次制。每一轮只有一个 Agent 在输出。上一个 Agent 的 `stream_done` 到达后，才会触发对方 Agent 开始回复。这个设计防止了两个 AI 同时说话的混乱场景。

<!-- 截图占位：A2A 对话实时界面，展示双方 Agent 的流式对话 -->
![A2A 流式对话界面](images/nexus-a2a-streaming.png)

---

## 七、开箱即用：默认就连上全球网络

最后也是最重要的设计决策：**广域网默认启用。**

灵犀内置了一个公共信令服务器地址：

```
wss://lingxi-singaling-server.onrender.com/ws
```

启动灵犀时：
1. mDNS 扫描局域网（和以前一样）
2. 信令客户端自动连接公共信令服务器
3. 注册自己的信息（昵称、Agent 列表、平台、设备名）
4. 接收在线节点列表

用户不需要配置任何东西。只要两台灵犀实例都连着互联网，就能看到对方。

<!-- 截图占位：设置页面的网络与协作 + 广域网 WAN 设置 -->
![网络设置：广域网默认开启](images/nexus-wan-settings.png)

当然，如果你需要私有部署，可以在设置中修改信令服务器地址为自己部署的实例。信令服务器本身只有一个 Go 文件，部署到任何支持 WebSocket 的平台即可。

---

## 八、安全性设计

"消息经过一个中心化服务器，安全吗？" 这是一个合理的疑问。灵犀在安全性上做了以下设计：

### 8.1 WSS/TLS 加密

信令服务器默认使用 `wss://`（WebSocket over TLS），所有传输中的数据都经过 TLS 1.2+ 加密。中间人无法窃听。

### 8.2 信令服务器无状态

信令服务器不持久化任何数据——不存消息、不存用户信息。所有 peer 信息仅存在于内存中。服务器重启后，peer 列表清空，客户端自动重连重新注册。

### 8.3 Agent 安全约束

每个参与对话的 Agent 都有独立的安全设置：

| 配置 | 作用 |
|------|------|
| 公开开关 | Agent 是否出现在发现列表中 |
| 能力标签 | 标记 Agent 的专长领域 |
| 授权级别 | 控制 Agent 的行为边界 |
| **禁止透露** | Agent 绝对不能泄露的敏感信息 |

"禁止透露"约束会注入到 Agent 的 system prompt 中——即使对方 Agent 试图套话，你的 Agent 也会拒绝回答。

### 8.4 断线恢复

信令客户端内置了指数退避重连机制。网络中断后，会以 1s → 2s → 5s → 10s → 30s 的间隔自动重连，并在重连成功后重新注册。前端实时显示连接状态。

---

## 九、前端体验：LAN + WAN 一体化

广域网节点和局域网节点在前端统一展示，用颜色和图标区分：

```
┌──────────────────────────────────────────────┐
│  在线节点                                     │
│                                              │
│  📡 局域网 (2)                                │
│  ┌──────────────────────────────────────┐    │
│  │ 🟢 MacBook-Pro · 192.168.1.5:3001   │    │
│  │    Agent: 代码审查员、产品分析师     │    │
│  │                          [发起对话]  │    │
│  └──────────────────────────────────────┘    │
│                                              │
│  🌐 广域网 (3)                        🟢     │
│  ┌──────────────────────────────────────┐    │
│  │ 🟣 深圳-张三 · darwin · MacBook-Air  │    │
│  │    Agent: 财务顾问、合同审核         │    │
│  │                          [发起对话]  │    │
│  └──────────────────────────────────────┘    │
│  ┌──────────────────────────────────────┐    │
│  │ 🟣 上海-李四 · windows · Desktop-PC  │    │
│  │    Agent: 技术文档、API 设计         │    │
│  │                          [发起对话]  │    │
│  └──────────────────────────────────────┘    │
│                                              │
│  底部状态栏: 📡 2 LAN  🌐 3 WAN         🟢  │
└──────────────────────────────────────────────┘
```

底部状态栏实时显示 LAN/WAN 节点数量和连接状态，用户一目了然。

<!-- 截图占位：NexusPage 完整界面，左侧对话列表 + 右侧发现面板 -->
![Nexus 双栏界面全貌](images/nexus-full-view.png)

对话发起后，无论对方是 LAN 还是 WAN 节点，对话界面完全一致——用户不需要（也不会）感知底层的传输差异。

<!-- 截图占位：实际的 A2A 对话过程 -->
![实际对话效果](images/nexus-a2a-conversation.png)

---

## 十、对话的终结：自动收敛机制

两个 AI 对话，如果没有终止条件，可能会永远聊下去（或者陷入无意义的寒暄循环）。灵犀设计了三层收敛机制：

**第一层：[CLOSE] 标记检测**

Agent 的 system prompt 中包含指令：当认为对话目标已达成时，在回复开头加上 `[CLOSE]` 标记。对话引擎检测到后，自动将对话标记为 `completed`，并通知对方终止。

**第二层：最大轮次限制**

每场对话有预设的最大轮次（默认 10 轮）。达到上限后对话自动暂停，等待人类决定是否继续。

**第三层：人类随时可控**

用户可以随时暂停、接管或终止对话。暂停通过 Go channel 即时生效；接管后人类手动输入消息；终止信号同步发送给对方。

---

## 十一、一图读懂全链路

让我们用一张图串联整个广域网 Agent 对话的完整链路：

```
用户 A (北京)                                           用户 B (深圳)
    │                                                       │
    │ 1. 打开灵犀                                            │ 1. 打开灵犀
    │    ↓                                                   │    ↓
    │ 2. 自动连接信令服务器                                    │ 2. 自动连接信令服务器
    │    ↓                                                   │    ↓
    │ 3. 发现面板看到 B                                       │ 3. 发现面板看到 A
    │    ↓                                                   │
    │ 4. 点击「对话」                                         │
    │    选择己方 Agent                                       │
    │    输入提问                                             │
    │    ↓                                                   │
    │ ── conversation_invite ─► 信令服务器 ─────────────────► │
    │                                                       │ 5. 收到邀请通知
    │                                                       │    选择己方 Agent
    │                                                       │    点击「接受」
    │                                                       │    ↓
    │ ◄── conversation_accept ◄─ 信令服务器 ◄──────────────── │
    │    ↓                                                   │
    │ 6. Agent A 开始流式输出                                  │
    │    ├──► 本地前端渲染                                    │
    │    └──► WAN relay ──► 信令服务器 ──► B 前端实时渲染      │
    │    ↓                                                   │
    │ 7. 完整消息发送给 B                                      │
    │    ↓                                                   │    ↓
    │                                                       │ 8. Agent B 开始流式回复
    │    B 前端实时渲染 ◄── 信令服务器 ◄── WAN relay ◄──┤    │
    │                                                  └──► │ B 本地前端渲染
    │                                                       │    ↓
    │ ◄───────────── 完整消息发送给 A ◄──────────────────────── │
    │    ↓                                                   │
    │ ... （循环，直到 [CLOSE] 或达到最大轮次）                  │
```

---

## 十二、与局域网版本的对比

| 维度 | 局域网版 (v1.0) | 广域网版 (v1.1) |
|------|----------------|----------------|
| 发现方式 | mDNS 多播 | mDNS + 信令服务器注册 |
| 通信路径 | HTTP 直连 | HTTP 直连 (LAN) / 信令中继 (WAN) |
| 配置要求 | 同一 WiFi | 有互联网即可 |
| 建联机制 | PSK 密钥交换 | 直接邀请，无需预先建联 |
| 传输安全 | HTTP 明文 | WSS/TLS 加密 |
| 第三方依赖 | 无 | 公共信令服务器 (可自建) |
| 断线恢复 | 无 | 指数退避自动重连 |
| 节点信息 | ID + 昵称 | ID + 昵称 + 头像 + 平台 + 设备 + Agent 列表 |

一个值得注意的简化：广域网版本移除了 PSK 建联机制和 Token 认证。在局域网中，这两个机制用于在信任边界不明确的本地网络中建立互信。但在广域网中，用户明确地选择了要对话的对象，对话本身就是显式授权——再加上 WSS 传输加密和 Agent 级安全约束，PSK 建联变得多余。**去掉它，用户体验更简洁。**

---

## 十三、如何体验

### 最快体验路径

1. 在两台电脑上分别安装灵犀（或一台用安装版，一台用开发模式 `PORT=3099`）
2. 确保两台机器都连着互联网
3. 分别在设置中配置模型接入点
4. 打开 Nexus 页面 → 发现面板 → 看到对方 → 点击「对话」
5. 选择 Agent，输入你的问题，发送
6. 对方接受邀请，对话自动开始

### 自建信令服务器

如果你需要私有部署：

```bash
# 克隆信令服务器（只有一个 main.go）
git clone https://github.com/OdysseyFather/lingxi-singaling-server.git
cd lingxi-singaling-server

# 本地运行
go run main.go  # 默认 9090 端口

# 或 Docker 部署
# 或推送到 Render / Railway / Fly.io（支持 WebSocket 的任何平台）
```

然后在灵犀设置中将信令服务器地址改为你的实例。

---

## 结语

从局域网到广域网，Project Nexus 走出了 Agent-to-Agent 通信最关键的一步。通过一个极简的信令服务器和精心设计的 Transport 抽象层，灵犀实现了：

- **Zero-Config**：启动就连上全球网络，不需要任何配置
- **LAN 优先**：同网络走直连，零延迟零中继
- **安全传输**：WSS 加密 + Agent 级安全约束
- **可靠通信**：自动重连 + 消息重试 + 断线恢复
- **统一体验**：LAN/WAN 对话界面完全一致

下一步，我们计划引入端到端加密（E2EE），让消息内容即使经过信令服务器中继，服务器本身也无法解读——真正实现"不信任中间人"的安全模型。

> 让 AI 跨越山海，只为更好的协作。

---

> **灵犀** —— 让 AI 成为你的工作伙伴，而不只是聊天对象。
>
> GitHub：[https://github.com/OdysseyFather/lingxi](https://github.com/OdysseyFather/lingxi)
>
> 如果觉得项目有价值，欢迎 Star 支持！
