Hermes Desktop 架构:Electron 如何连接本地 Python Agent

前言:Desktop 这层最容易被一句“Electron 套壳”带过去,但源码并不是这么组织的:窗口和本机能力属于 Electron,聊天状态属于 React,真正的 Agent 仍在独立 Python Backend。本文沿着一次请求从输入框走到模型、工具再流式返回,并用可交互动画把协议和模块逐步展开。


系列导航:这组文章按“总览 → 入口 → 运行时 → 扩展 → 状态 → 协作 → 安全”的顺序展开;第一次阅读可以从总览开始,带着具体问题也可以直接跳到专题。

  1. 01|Hermes Agent 架构总览:从 Agent Loop 到智能体平台
  2. 02|Hermes Desktop 架构:Electron 如何连接本地 Python Agent
  3. 03|Hermes Agent 运行时:AIAgent、模型协议与工具执行
  4. 04|Hermes Agent 扩展体系:Skill、Plugin 与 MCP
  5. 05|实战:同一个 Markdown 检查器的 Skill、Plugin 与 MCP 三种实现
  6. 06|Hermes Agent 的持续学习系统:Memory、Review、Skill 与 Curator
  7. 07|Hermes Agent 的长会话架构:Prompt Cache、压缩与 SessionDB
  8. 08|Hermes Agent 的多 Agent 与自动化:从 Goal 到 Kanban
  9. 09|Hermes Agent 安全架构:审批、沙箱、回滚与凭据隔离

Electron Desktop:不是把网页套进一个壳

前面的入口层里,我只把 Desktop 画成一个方框,这确实省略了很重要的一层。Hermes Agent 的桌面端不是把 Dashboard 塞进 Electron,也不是在窗口里嵌入 TUI;它有自己的 React 聊天界面,而真正的 Agent 仍运行在 Python 进程里。桌面架构的关键不是“用了 Electron”,而是把本机控制、用户体验和 Agent 工作分给了三个权威边界。

权威边界主要实现负责什么刻意不负责什么
Electron Mainapps/desktop/electron/main.ts窗口、进程生命周期、本机文件与 Git、安装更新、Backend 启停不执行 Agent Loop,不保存聊天 UI 状态
Preload Bridgeapps/desktop/electron/preload.ts通过 contextBridge 暴露窄而有类型的本机能力不把完整 Node.js / Electron API 交给网页上下文
React Rendererapps/desktop/src/聊天、导航、流式消息、终端与预览 Pane、临时交互状态不直接 import Python,也不拥有 Agent 语义
Python Backendhermes servetui_gateway/会话、模型调用、工具、审批、事件流与持久化不决定窗口布局和本机 UI 表现
flowchart LR
    User[用户] --> UI[React 渲染进程
React Renderer] UI --> Store[assistant-ui 与状态存储] UI --> Preload[Preload 类型化桥] Preload --> Main[Electron 主进程
Electron Main] Main --> Native[文件 Git 窗口与更新] UI <-->|基于 WebSocket 的 JSON-RPC
JSON-RPC over WebSocket| Serve[Python 服务进程
hermes serve] Serve --> Gateway[TUI 网关
tui_gateway] Gateway --> Agent[智能体运行时
AIAgent] Agent --> Tools[工具与状态层]

这张图里有两条完全不同的通道。Renderer 需要选择目录、读本机文件、操作剪贴板或创建原生通知时,走 window.hermesDesktop 暴露的 Preload IPC;需要创建会话、提交 Prompt、接收模型流和工具事件时,则绕过 Electron Main,直接通过 WebSocket 连接 Python Gateway。把两条通道分开,才能避免“所有调用都从 Electron 中转”的错误理解。

桌面能力为什么又能被 Agent 当成工具调用

这里还有一个看似反向的需求:终端 Pane 和预览 Pane 的真实状态在 Renderer,Python Agent 却可能调用 read_terminalopen_previewfocus_pane。Hermes 没有让 Backend 穿透进 Renderer 内存,而是把这类操作注册为仅 Desktop 会话可见的 desktop_ui Toolset,再用 Gateway 事件做一次请求—应答。

sequenceDiagram
    participant A as 智能体运行时
AIAgent participant T as desktop_ui 工具 participant G as TUI 网关
tui_gateway participant R as React 渲染进程
React Renderer participant P as 终端或预览 Pane A->>T: 调用 read_terminal T->>G: 终端读取请求
terminal.read.request G-->>R: WebSocket 事件
WebSocket event R->>P: 读取当前 Pane 状态 P-->>R: 文本与元数据 R->>G: 终端读取响应
terminal.read.respond G-->>T: 完成等待中的请求 T-->>A: 标准 Tool Result

桌面工具是否出现,判断依据是会话的 SessionSource,而不是简单检查 Python 进程有没有 HERMES_DESKTOP 环境变量。这一点很重要:当 Backend 在 SSH 主机或云端运行时,当前会话仍然可能来自 Desktop;UI 工具应该回到发起会话的桌面 Renderer,而 Terminal、文件和代码执行则应该留在远端运行环境。

Electron 如何启动并连接本地 Python

如果只用一句话概括:Electron Main 找到可用的 Hermes CLI,启动 hermes serve 子进程;React Renderer 再通过 JSON-RPC/WebSocket 连接它。这里没有 Electron 原生模块直接链接 CPython,也没有把 Python 函数暴露成 IPC 方法。

本地 Backend 的发现与启动

backend-command.ts 会按候选梯子解析 Backend:显式指定的源码根目录、开发态当前 checkout、已经完成的托管安装、用户指定或 PATH 中的 hermes、可以 import Hermes 的系统 Python,最后才是安装引导。候选项不是“找到文件就算成功”,还要经过能力探测;旧运行时不支持 serve 时,才兼容回退到 dashboard --no-open

flowchart TD
    Start[Electron 启动] --> Resolve[解析 Backend 候选梯子]
    Resolve --> Probe[探测 CLI 与 serve 能力]
    Probe --> Spawn[启动 hermes serve]
    Spawn --> Bind[绑定 127.0.0.1 端口 0]
    Bind --> Ready[读取 ready 信息与实际端口]
    Ready --> HTTP[HTTP 就绪探测]
    HTTP --> WS[WebSocket 握手探测]
    WS --> Connect[Renderer 建立 JSON-RPC 连接]
    Probe -->|仅旧版本兼容| Legacy[兼容旧版仪表板
dashboard --no-open]

规范启动参数是 hermes serve --host 127.0.0.1 --port 0,可再附加 Profile。端口设为 0 让操作系统分配临时空闲端口,Electron 从 ready 文件或子进程输出得到实际端口,完成 HTTP 和 WebSocket 两级探测后才把连接交给 Renderer。启动时还会注入独立的 HERMES_HOME、工作目录、会话令牌、父进程标记等环境;退出时则终止整个进程组,避免 MCP 等孙进程残留。

一条消息怎样从 React 走进 AIAgent

共享包里的 JsonRpcGatewayClient 把请求编码成标准 JSON-RPC 2.0:{ jsonrpc, id, method, params }。会话创建和恢复分别调用 session.createsession.resume,用户发送消息调用 prompt.submit,停止生成调用 session.interrupt。Backend 的普通响应按 id 兑现 Promise,流式内容则通过名为 event 的通知持续推给 Renderer。

sequenceDiagram
    participant U as React 输入框
    participant C as JSON-RPC 网关客户端
JsonRpcGatewayClient participant W as WebSocket 通道
/api/ws participant G as TUI 网关
tui_gateway participant A as 智能体运行时
AIAgent participant X as 工具执行器
Tool Executor U->>C: 提交用户消息 C->>W: 提交提示
prompt.submit W->>G: JSON-RPC 分发 G->>A: 运行对话
run_conversation A->>X: 可选工具调用 X-->>A: 工具结果 A-->>G: token、reasoning、tool 事件 G-->>C: event 通知流 C-->>U: 增量刷新消息与状态

动画:一次 Electron 请求如何走完 Agent Turn

上面的时序图适合快速定位参与者,下面这个动画则把“输入什么、走什么协议、模块做什么、返回什么”放在同一条路径里。点击任一节点可以暂停并查看该阶段;工具分支是可选的,模型直接回答时会跳过它。

请求进入客户端
请求与推理可选工具循环流式返回

“连接本地 Python”其实有四种不同含义

场景连接方式适合解决什么是否为 Desktop 主路径
桌面端连接 Agent BackendElectron 启动 hermes serve;Renderer 走 WebSocket JSON-RPC会话、模型、工具和事件流
模型用 Python 编排工具execute_code 启动脚本,通过生成的 hermes_tools.py RPC 回调 Registry循环、过滤、批处理,减少多轮 Tool Call是 Agent 工具路径之一
业务程序嵌入 AgentPython 代码直接 import 并创建 AIAgent服务端集成、测试或自定义宿主否,这是程序化集成路径
运行普通 Python 脚本Terminal 工具执行 python script.py项目脚本、数据处理、构建任务只是 Terminal 的一种命令

execute_code 如何让本地 Python 反过来调用 Hermes 工具

这条路线叫 Programmatic Tool Calling。模型不是连续产生十几个离散 Tool Call,而是写一段 Python,让脚本 import 自动生成的 hermes_tools.py。Stub 把 web_searchread_filepatchterminal 等受控工具请求发回父 Agent,父进程仍通过 Registry、审批和实际执行后端完成调用。脚本中间结果留在 Python 进程里,最终只有标准输出回到模型上下文。

flowchart LR
    Model[模型生成 Python] --> Runner[代码执行运行器
execute_code Runner] Runner --> Script[隔离的 Python 脚本] Script --> Stub[生成的 hermes_tools.py] Stub --> Channel{RPC 通道} Channel -->|本机 POSIX| UDS[Unix 套接字
Unix Socket] Channel -->|本机 Windows| TCP[回环 TCP
Loopback TCP] Channel -->|远程环境| FileRPC[文件 RPC] UDS --> Registry[父进程 Tool Registry] TCP --> Registry FileRPC --> Registry Registry --> Result[结果返回脚本] Script --> Stdout[仅 stdout 回到模型]

当前实现中,本地 POSIX 优先用 Unix Domain Socket,Windows 使用回环 TCP;Docker、SSH 等远程环境则使用可搬运请求与结果的文件 RPC。这个设计的重点不是让任意 Python 绕过安全边界,而是把“多步工具编排”移到代码层:可调用工具仍取会话允许集与沙箱白名单的交集,父 Agent 仍然是唯一的工具执行权威。

参考资料与源码入口

题图来源:《海、地平线与帆船》,创作者 PublicDomainPictures,来自 Pixabay;依据 Pixabay Content License(Pixabay 内容许可)使用。

下一篇:Hermes Agent 运行时:AIAgent、模型协议与工具执行

Hermes Agent 安全架构:审批、沙箱、回滚与凭据隔离 ZFS Mirror vs mdadm RAID1:Linux 双盘镜像搭建、故障演练与性能实测 REK2 搭建6节点K8S教程(四):K8S可视化管理工具
View Comments