Stephen 技术博客

AI和数据平台的工程实践

← 返回文章列表
SERVER ↔ 多 HOST · 自注册 + MANAGED 云沙箱

Server ↔ 多 Host 架构

Server 只做协调、不跑 agent 代码;host 要么主动 outbound 连上来(自注册),要么由 server 按需 provision 云沙箱(managed)——每个 session 一个隔离环境

一句话定位:Omnigent Server 是一个轻量协调器,监听 6767 端口、可多副本水平扩展,自己不执行任何 agent 代码。 真正跑 runner(agent 子进程)的是 host,而 host 有两种来源: ① 自注册 host——用户机器上的 daemon 主动 outbound 建 WS 隧道连上来(server 不启动它们); ② Managed host——server 按需在 Modal / Daytona 上 provision 云沙箱,一个 session 一个沙箱、强隔离。 跨副本的唯一真相源是数据库里的 hosts 表,内存里的 HostRegistry 只保存本副本的活连接。

Omnigent Server ↔ 多 Host 架构(自注册 host + Managed 云沙箱) 🌐 Web UI / 客户端 New Chat → 选机器 (host picker) 订阅 SSE /stream Omnigent Server(可多副本) — 端口 6767 REST API POST /v1/sessions GET /v1/hosts (picker) Tunnel 端点 /v1/hosts/{id}/tunnel sender · receiver · ping 任务 resolve_host_launch 校验:拥有 host + 在线 + 拥有 session(否则 403/409) HostRegistry(内存 · 每副本) host_id → HostConnection(ws) pending_launches / stops / stats 仅本副本的活连接 managed_hosts.launch provision 沙箱 · 发 per-launch token 起 omnigent host · 等连回 online 一 session 一沙箱(无池化) hosts 表(跨副本真相源) host_id / owner / status / updated_at token_hash / sandbox_id(managed) 存活 = online 且 updated_at ≥ now-90s 自注册 host —— host 主动 outbound 连接(omnigent host),server 不启动它们 Host A(owner: alice · 用户机器) host daemon (connect.py) self._runners {} — 多 runner 子进程 runner ▸ session-1 (自己的 git worktree) runner ▸ session-2 (自己的 git worktree) 同 OS 用户 alice · 一 host 可多 session 凭证白名单注入(不泄漏个人密钥) Host B(owner: bob) host daemon runner ▸ session-3 (worktree) bob 只能看到/派发自己的 host (host = owner 的信任边界) Managed host —— server 按需 provision(Modal / Daytona),一 session 一沙箱 ☁️ 云沙箱 #1(session X) host daemon(沙箱内) runner ▸ session X(worktree) 隔离 VM / 容器 per-launch token(SHA-256 存哈希) session 删除 → 沙箱销毁 沙箱挂了 relaunch:复用 host_id,换 token ☁️ 云沙箱 #2(session Y) host daemon(沙箱内) runner ▸ session Y(worktree) 隔离 VM / 容器 每会话各一沙箱 → 强隔离 对用户透明(不进 picker) 建会话 / 选机器 起 session → 选 host → launch 取活连接 → 发 launch_runner 帧 register / deregister upsert / heartbeat / set_offline register_managed_host(token) WS 隧道 ⇅ (outbound) hello / launch / stop / ping 帧 30s ping · 90s 心跳判活 WS 隧道 ⇅ ① provision 沙箱 + 起 omnigent host (注入 token / host_id / server_url) 一 session 一沙箱 ② WS 连回(token 鉴权) → upsert_on_connect WS 连回 图例 WS 隧道(双向帧 + 心跳) server 主动 provision / 写库 HTTP / 内部调用 自注册=host 连上来 · managed=server 启

图 1 · Server 只做协调,runner 跑在 host 上;host 分自注册与 managed 云沙箱两类

1三种角色一句话

角色职责谁来启动
Server(6767 · 可多副本)协调:建 session、host picker、tunnel、鉴权;不跑 agent 代码独立部署
自注册 Host用户机器上的 daemon,承载多个 runner 子进程host 主动 outbound 连 server
Managed Host云沙箱内的 daemon,一个 session 一个,强隔离server 按需 provision
Runner真正执行 agent 的子进程,各自一个 git worktreehost daemon 起

2Server 内部:协调但不执行

Server 只有四块,全部围绕"把请求路由到正确的 host 连接"。

REST API

POST /v1/sessions 建会话 · GET /v1/hosts 给客户端做 host picker。

Tunnel 端点

/v1/hosts/{id}/tunnel —— host 的 WS 隧道落点,内部拆成 sender / receiver / ping 三个任务。

HostRegistry(内存 · 每副本)

host_id → HostConnection(ws),外加 pending_launches / stops / stats只保存本副本的活连接——所以它不是真相源,只是"我这台 server 现在连着谁"的快照。

resolve_host_launch

派发前的守门:校验请求者拥有该 host + host 在线 + 拥有该 session,否则 403 / 409

3hosts 表:跨副本的唯一真相源

因为 HostRegistry 只知道本副本的连接,多副本部署下"这个 host 到底在不在线"必须落到数据库。

hosts 表字段
  host_id / owner / status / updated_at
  token_hash / sandbox_id  (managed 专用)

存活判定:status = online 且 updated_at ≥ now - 90s
心跳判活:host 每 30s ping,server 以 90s 作为心跳窗口——超过 90s 没更新 updated_at 即视为掉线,即使某副本内存里还挂着连接。

4两类 Host 对照

① 自注册 Host — host 连上来

  • 用户机器上的 host daemon (connect.py) 主动 outbound 建 WS 隧道,server 从不启动它们。
  • 一个 host 可挂多个 runner(self._runners{}),每个 runner 服务一个 session、各占独立 git worktree。
  • runner 跑在同一 OS 用户下(如 alice),靠凭证白名单注入,不泄漏个人密钥。
  • host = owner 的信任边界:bob 只能看到 / 派发自己的 host。

② Managed Host — server 起云沙箱

  • server 按需在 Modal / Daytona 上 provision 一个隔离 VM / 容器,一个 session 一个沙箱、无池化
  • 注入 per-launch token(server 只存 SHA-256 哈希)、host_idserver_url;沙箱内 daemon 用 token WS 连回upsert_on_connect
  • 生命周期绑定 session:session 删除 → 沙箱销毁;沙箱挂了 relaunch 时复用 host_id、换新 token
  • 对用户透明——不进 host picker。

5一次 launch 的端到端时序

  1. 客户端 → REST:New Chat 建会话,并在 host picker 里选一台机器。
  2. REST → resolve_host_launch:起 session → 选 host → 发起 launch,先过所有权 / 在线校验。
  3. 分叉:自注册 host —— 取内存里的活连接,通过 WS 隧道发 launch_runner 帧给 host daemon。
  4. 分叉:managed host —— managed_hosts.launch ① provision 沙箱 + 起 omnigent host(注入 token / host_id / server_url),写库 register_managed_host
  5. host / 沙箱 → server:daemon 建立(自注册)或 ② 用 token 连回(managed)WS 隧道,upsert 进 hosts 表,状态 online。
  6. runner 起来:host daemon 在独立 git worktree 里拉起 runner 子进程,session 开始跑,客户端订阅 SSE /stream 看输出。
设计取舍一句话:把"协调"与"执行"彻底分开——server 无状态可水平扩展,真相收敛到 hosts 表;自注册 host 用 outbound 连接绕过用户机器的入站防火墙,managed host 用一会话一沙箱换取强隔离。两条路复用同一套 WS 隧道 + 心跳协议。
Omnigent · Server ↔ 多 Host 架构 · 配图见图 1