从 CLI 到桌面 App,再到技能市场:我们给我的 Rust Hermes Agent 造了一个完整的生态
上两篇我们聊了 Rust 重写 Hermes Agent和学习闭环。这次,我们把它装进了一个桌面 App,并接上了一个技能市场。注意这个技能市场中,是可以承载我们在使用 Hermes Agent 的过程中,这匹马自己主动生成的技能。
故事从一个问题开始
上篇文章发出来之后,有朋友私信我:
“技能系统很酷,但我每次要用,得打开终端、敲命令、还要记参数……能不能有个界面?”
另一个问题同样被问到了:
“我的 Hermes自己创建的技能只有我自己用,有没有办法分享给别人?或者直接用别人沉淀好的技能?”
这几天的工作,其实就是针对这两个问题,就是最新两个迭代要回答的。
答案是:Hermes Desktop + SkillHub 技能市场。


Hermes Desktop:把 Agent 装进桌面应用
桌面端我直接使用了Tauri 2,那么我为什么这么选择呢?
我们没有做一个 Electron 应用。原因很简单——Electron 的本质是用 Chromium + Node.js 跑一个 Web 应用,打包出来随随便便 200MB 起步。
Tauri 的思路完全不同:
-
• 前端渲染用系统自带的 WebView(macOS 上是 WebKit,Windows 上是 WebView2) -
• 后端逻辑直接是 Rust -
• 打包产物:不到 10MB

更重要的是:我们的整个 Agent 栈(hermes-agent、hermes-tools、hermes-llm……)本来就是 Rust 写的。Tauri 的后端天然复用全部 13 个 crate,没有任何额外的桥接层。
apps/hermes-desktop/ src-tauri/ ← Rust 后端(直接 use hermes_agent::AIAgent) src/lib.rs ← Tauri Commands 定义 src/ ← React + TypeScript 前端 App.tsx ← 全部 UI 逻辑
前端:React + TypeScript + Tailwind CSS + Vite。标准现代 Web 技术栈,没有任何奇特选择。
架构:Commands 桥接两端
Tauri 的核心机制是 Commands——前端通过 invoke() 调用 Rust 函数,拿到类型安全的返回值。
我们定义了一套完整的 Command API:
// 聊天核心#[tauri::command]async fn chat(state: State<'_, AppState>, message: String) -> Result<ChatResponse, String>// 会话管理#[tauri::command]async fn list_sessions(state: State<'_, AppState>) -> Result<Vec<SessionItem>, String>#[tauri::command]async fn load_session(state: State<'_, AppState>, session_id: String) -> Result<Vec<DisplayLine>, String>// 配置读写#[tauri::command]async fn get_settings(state: State<'_, AppState>) -> Result<SettingsView, String>#[tauri::command]async fn save_settings(state: State<'_, AppState>, payload: SettingsPayload) -> Result<String, String>// 技能管理#[tauri::command]async fn list_skills(state: State<'_, AppState>) -> Result<Vec<SkillListItem>, String>#[tauri::command]async fn install_market_skill(state: State<'_, AppState>, slug: String) -> Result<SkillInstallResult, String>
这个设计有个关键点:AppState 里的 AIAgent 是真实的 Rust Agent 实例,不是什么 HTTP 调用或者进程间通信——前端和 Agent 运行在同一个进程里,调用开销接近于零。
struct Session { agent: hermes_agent::AIAgent, // 真实的 Agent 实例 history: Vec<Message>, model_label: String,}
我的UI 设计,三个面板,一个 App,前面基本上也讲过了
整个 UI 分三个主视图,通过左侧边栏切换:
① 聊天界面(Chat)
左侧是历史会话列表,右侧是对话区域。几个设计细节值得说:
-
• 工具调用日志折叠展示:Agent 执行工具时的日志会被归组成一个可折叠的 <details>块,默认收起。不想看细节的用户不会被干扰,想 debug 的用户点开就有。
// 工具日志渲染逻辑if (item.kind === "tools") { return (<details className="group rounded-md border border-dashed border-primary/30"> <summary>⚡ 工具执行日志({item.logs.length} 条)</summary> {/* 展开后显示每条工具调用 */} </details> );}
-
• Markdown 渲染:助手的回复通过 react-markdown + remark-gfm渲染,代码块有语法高亮样式,表格、列表都正常展示。 -
• 深色 / 浅色模式:一键切换,状态持久化到 localStorage。
② 设置界面(Settings)

模型名称、Base URL、API Key、流式输出开关、记忆系统开关、会话持久化……所有配置都在这里,保存后直接写入 config.yaml,无需重启。
③ 技能中心(Skills)
这个面板是这次迭代的重头戏,下面单独展开。
SkillHub 技能市场:让技能流动起来
我们所面对的问题是技能孤岛
之前的技能系统是完全本地的:Agent 自己创建技能,存在 ~/.hermes/skills/ 目录,只有你自己能用。
这带来了两个问题:
-
1. 冷启动困难:新用户装完之后技能库是空的,要等 Agent 自己慢慢积累 -
2. 重复造轮子:同样的 “如何调试 Docker 网络问题” 技能,无数人都在自己创建
SkillHub 要解决的就是这个问题:让技能成为可流通的资产。
实现:SkillHubClient
我们在 hermes-skills crate 里加入了 SkillHubClient,封装了和技能市场 API 的全部交互:
pub struct SkillHubClient { base_url: String, client: reqwest::Client,}impl SkillHubClient { // 拉取分类列表 pub async fn categories(&self) -> Result<Vec<String>, String> // 获取推荐技能 pub async fn recommended(&self, limit: u32) -> Result<Vec<RemoteSkillSummary>, String> // 分页浏览(支持按分类筛选) pub async fn list_skills(&self, category: Option<&str>, page: u32, page_size: u32) -> Result<RemotePage<RemoteSkillSummary>, String> // 全文搜索 pub async fn search_skills(&self, query: &str, category: Option<&str>, ...) -> Result<RemotePage<RemoteSkillSummary>, String> // 获取技能详情(含完整 SKILL.md 内容) pub async fn skill_detail(&self, id_or_slug: &str) -> Result<RemoteSkillDetail, String>}
让后离线优先,Seeded Fallback 机制
网络不稳定怎么办?SkillHub 服务器挂了怎么办?
我们设计了一套 优雅降级 机制,通过 CatalogDataSource 枚举追踪数据来源:
pub enum CatalogDataSource { Remote, // 来自 SkillHub 服务器 Seeded, // 来自内置精选(离线可用) Mixed, // 服务器数据不足,混合了内置精选补充}
每个请求都有 _with_fallback 版本:
pub async fn recommended_with_fallback( &self, limit: u32,) -> (Vec<RemoteSkillSummary>, CatalogDataSource) { match self.recommended(limit).await { Ok(remote) if !remote.is_empty() => { // 远程数据够用,直接返回 // 如果远程数量少于 seeded,混合补充 if remote.len() < seeded.len() { (merge(remote, seeded), CatalogDataSource::Mixed) } else { (remote, CatalogDataSource::Remote) } } // 网络失败?用内置精选,用户完全无感 _ => (seeded_recommended(limit), CatalogDataSource::Seeded), }}
离线状态下,技能市场仍然可以浏览和使用内置精选。等网络恢复,自动切回远程数据。
技能市场 UI
技能中心界面分两个 Tab,界面前面已经给过了:
「已安装」Tab:展示本地技能列表,可以查看详情、删除、直接编辑 SKILL.md 内容。
「市场」Tab:
-
• 顶部:分类过滤 + 搜索框 -
• 推荐区:横向滚动的精选技能卡片 -
• 列表区:分页浏览全部技能,每个卡片展示名称、描述、分类标签、Star 数、安装量 -
• 点击技能卡片:弹出详情面板,展示完整 SKILL.md 内容,一键安装
// 安装技能只需要一行const result = await invoke<SkillInstallResult>("install_market_skill", { slug });
安装本质上是把技能的 SKILL.md 内容写入本地 ~/.hermes/skills/<name>/SKILL.md,Agent 下次启动时自动发现并加载。
这次迭代的,其实我对整个核心做了质量提升
除了新功能,这次迭代也做了大量质量提升工作。
用户画像过滤
user_profile 工具现在支持按置信度过滤:低置信度(observed,0.7)的事实在空间不足时会被自动淘汰,只有用户亲口确认(stated/corrected,1.0)的高置信度事实才会稳定保留。
这意味着 Agent 对你的画像会越来越准——不确定的猜测自然消退,确定的认知持续强化。
更好的 Agent 回调
AgentCallbacks 现在支持更细粒度的事件:工具调用开始、工具调用结束、流式 token 回调,方便桌面端做实时 UI 更新(比如工具执行时显示 loading 状态)。
然后,我们整体架构现在是什么样子
经过这几轮迭代,整个系统的形态已经非常清晰:
┌─────────────────────────────────────────────────────────┐│ 用户接入层 ││ hermes CLI │ Hermes Desktop │ Gateway (Telegram…) │└──────────────────────────────┬──────────────────────────┘ │┌──────────────────────────────▼──────────────────────────┐│ Agent 核心层 ││ hermes-agent (AIAgent 主循环) ││ hermes-llm │ hermes-tools │ hermes-mcp │ hermes-skills│└──────────────────────────────┬──────────────────────────┘ │┌──────────────────────────────▼──────────────────────────┐│ 数据与知识层 ││ SQLite (会话历史 + FTS5) │ SKILL.md (本地技能库) ││ MEMORY.md / USER.md │ SkillHub (远程技能市场) │└─────────────────────────────────────────────────────────┘
三层架构,职责清晰:
-
• 最上层负责接入——你可以用 CLI、桌面 App、或者 Telegram Bot -
• 中间层是 Agent 大脑——工具调用、MCP、技能检索,一套核心逻辑服务所有接入层 -
• 最下层是知识沉淀——会话历史、记忆、技能,都是持久化的资产
无论你从哪个入口进来,你的记忆、你的技能、你的画像都是共享的。在桌面 App 里让 Agent 学会了一个 Docker 排错技巧,下次打开终端用 CLI 时,这个技能照样可用。
使用咱们这个 Hermes Agent Rust一次完整体验是什么样的
让我描述一个完整的使用场景:
第一次打开 Hermes Desktop,你在设置页填入 API Key,选好模型,保存。
进入聊天,你说:”帮我分析一下这个 Python 项目的性能瓶颈”。Agent 开始工作:调用 terminal 执行 python -m cProfile,调用 read_file 读源码,调用 web_search 查相关文档……工具日志自动折叠,你只看到最终的分析报告。
完成后,Agent 主动提议把这套分析流程保存为技能。你同意,技能创建完毕。
打开技能中心,在市场 Tab 搜索 “code review”,发现有个评分很高的代码审查技能,一键安装。下次让 Agent 做代码审查,它会先调出这个技能里的检查清单。
三个月后,你换了台电脑。cargo build --release 编译一个二进制,把 ~/.hermes/ 目录拷贝过去,所有的记忆、技能、画像全部迁移完毕。无需重新配置任何服务。
写在最后
这几个迭代做完,我意识到我们在做的事情其实已经超出了”写一个 AI Agent”的范畴。
我们在构建的是 AI Agent 的基础设施:
-
• CLI:给开发者用的原子工具 -
• Desktop App:给更广泛用户用的交互界面 -
• SkillHub:让知识在用户之间流动的生态
这套基础设施有一个核心信念:AI Agent 产生的知识,应该成为可复用的资产,绝对不仅仅是随会话消失的文字。
技能是资产。记忆是资产。画像是资产。这些资产在你的设备上,不依赖任何云服务,可以迁移,可以分享,可以越用越多。
下一步?我们在考虑 SkillHub 的社区共建机制——让用户把自己沉淀的好技能提交到市场,让这个知识网络真正活起来。
三篇文章聊完了这个项目从 0 到 1 的历程。如果你对 Rust + AI Agent 这个方向感兴趣,欢迎来 hermes-rs 看看. 注意,这个项目目前仅仅对我的小群(公众号菜单-联系我-加群)内开源。
觉得有收获的话,「在看」和「转发」是对我最大的鼓励。
夜雨聆风